SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON —— ほぼ常に意味するのは一つ:あなたのコードが JSON ではなく HTML ページ に対して JSON.parse()(または res.json())を呼んだ、ということです。位置 0 の < は <!DOCTYPE html> か <html> の開始です。なぜ起きるかと、どう直すかを正確に説明します。
エラーの見え方
// V8 (Chrome / Node / Edge)
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
// より古い V8
SyntaxError: Unexpected token < in JSON at position 0
// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
// Safari
SyntaxError: JSON Parse error: Unexpected identifier "<"位置 0 が鍵:最初のバイトが既に間違っているため、レスポンスは最初から JSON ではありませんでした。
なぜ起きるか:HTML を受け取った
fetch または AJAX 呼び出しが、期待していた JSON ではな く HTML ドキュメントを返しました。一般的な原因:
- フレームワークや Web サーバが HTML として配信する 404 / 500 エラーページ
- HTML のサインインページへの ログイン/認証リダイレクト(セッション切れ)
- URL の誤り —— API エンドポイントではなく app/SPA ルートに当たっている
- プロキシ、CDN、キャプティブポータル が独自の HTML ページを返した
壊れた例
const res = await fetch('/api/user'); // サーバが 404 HTML ページを返した
const data = await res.json(); // ❌ Unexpected token '<' …
// res.json() が "<!DOCTYPE html>…" を JSON として parse しようとする修正後の例
const res = await fetch('/api/user');
// 1) parse 前にステータスを確認
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
// 2) オプション:本当に JSON か確認
const type = res.headers.get('content-type') ?? '';
if (!type.includes('application/json')) {
const text = await res.text();
throw new Error(`Expected JSON, got: ${text.slice(0, 80)}…`);
}
const data = await res.json(); // ✅ 安全知っておくと役立つ、目立たない 2 つの原因
- WAF がブロック/レート制限したレスポンス。 WAF、CDN、DDoS 防御層が呼び出しを横取りし、自身の HTML チャレンジ/ブロックページを返すことがあります —— Cloudflare のインタースティシャル、AWS WAF の拒否ページ、公衆 Wi-Fi のキャプティブポータル。ステータスは
403/429/503と HTML ボディの組み合わせがよくあります。Network タブでレスポンスを確認し、CF-RayやX-Amz-Cf-Idのようなヘッダで気づけます。 - SPA のヒストリーフォールバックが
index.htmlを返す。 静的ホストや開発サーバは、クライアントルータに引き継がせるため未知のパスをすべて/index.htmlに書き換えるのが一般的です。フロントエンドがうっかり/api/userを相対パスで fetch し、プロキシ設定がないと、サーバは SPA の HTML シェルを200で静かに返します —— 「200 OK だけど HTML」というややこしい版の同じエラーになります。
修正の手順
- 生のボディをログ:
console.log(await res.clone().text())。<!DOCTYPEや<html>で始まっていれば、それは HTML。 res.json()を呼ぶ前にres.ok/res.statusを確認 —— 4xx/5xx は通常 HTML のエラーページを返します。- Network タブで URL を確認。 相対パスは API ではなく SPA の index.html に解決されることがあります。
- 認証を確認 —— ログインページへのリダイレクトはトークン/セッションが切れた合図。
Content-Typeを検査 —— それで分岐し、非 JSON レスポンスを盲目的に parse しないように。
よくある質問
「Unexpected token < in JSON at position 0」とは?
パーサが < を最初の文字として見つけた。これは合法な JSON では決してありません。レスポンスはコードが期待した JSON ではなく HTML(<!DOCTYPE> か <html> ページ)でした。
なぜ fetch が JSON ではなく HTML を返すのか?
よくある原因:404/500 のエラーページ、ログインページへの認証リダイレクト、または API ではなくフロントエンドに解決される誤った URL。res.ok を確認し、await res.text() をログして何が返ってきたか確認。
アプリが落ちないようにするには?
parse 前にガード:res.ok と Content-Type ヘッダをチェックし、JSON.parse()/res.json() を try/catch で包む。HTML レスポンスは例外でなく扱えるエラーになります。
今すぐ直す
自信のないレスポンスボディがあれば、JSON Fix に貼り付けてください —— 有効な JSON か、別の何か(HTML など)かをすぐに判定します。すべてブラウザで動作。
- JSON Fix —— ブラウザで JSON を検証・修復
- JSON.parse の Unexpected Token エラーの直し方 —— すべての token バリエーション
- Unexpected token u in JSON at position 0 ——
undefinedの兄弟エラー - 「[object Object] is not valid JSON」の修正 —— 構文エラー総合リファレンス