SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON —— 幾乎總是代表一件事:你的程式碼對一個 HTML 頁面(不是 JSON)呼叫了 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 呼叫回傳了 HTML 文件,而不是你預期的 JSON。常見來源:
- 你的框架或 Web 伺服器以 HTML 形式提供的一個 404 / 500 錯誤頁
- 跳到 HTML 登入頁的 登入或驗證重新導向(工作階段過期)
- 用了錯誤的 URL —— 打到了 app/SPA 路由而非 API 端點
- 一個代理、CDN 或 captive portal 回傳了自己的 HTML 頁
壞掉的例子
const res = await fetch('/api/user'); // 伺服器回了 404 HTML 頁
const data = await res.json(); // ❌ Unexpected token '<' …
// res.json() 試圖把 "<!DOCTYPE html>…" 當成 JSON 來解析修好的例子
const res = await fetch('/api/user');
// 1) 解析前先檢查狀態
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(); // ✅ 安全另外兩個比較不顯眼但值得知道的來源
- WAF 攔截或速率限制的回應。 一個 WAF、CDN 或 DDoS 防護層可能攔住呼叫並回傳自己的 HTML 挑戰 / 攔截頁 —— Cloudflare 的中間頁、AWS WAF 的拒絕頁、公共 Wi-Fi 的 captive-portal 牆。狀態碼常是
403/429/503加上 HTML body。在 Network 面板中檢查回應,看是否有CF-Ray或X-Amz-Cf-Id這類標頭可以辨識。 - SPA 歷史回退回傳
index.html。 靜態主機與開發伺服器常將每個未知路徑改寫成/index.html,好讓客戶端路由器接手。如果你的前端不小心用相對路徑 fetch 了/api/user,而代理沒設定好,伺服器會靜靜地以200回傳你 SPA 的 HTML 殼 —— 你會拿到一個令人困惑的「200 OK 但是 HTML」版本的同一個錯誤。
怎麼修 —— 一步一步
- 把原始 body 印出來:
console.log(await res.clone().text())。如果開頭是<!DOCTYPE或<html>,就是 HTML。 - 在呼叫
res.json()之前檢查res.ok/res.status—— 4xx/5xx 通常會回傳 HTML 錯誤頁。 - 在 Network 面板確認 URL。相對路徑可能解析成你 SPA 的 index.html,而不是 API。
- 檢查驗證 —— 重新導向到登入頁代表你的 token/工作階段過期了。
- 檢查
Content-Type—— 依此分支處理,避免對非 JSON 回應盲目解析。
常見問題
「Unexpected token < in JSON at position 0」是什麼意思?
解析器把 < 當成第一個字元,這在任何合法 JSON 中都不可能。回應其實是 HTML(一個 <!DOCTYPE> 或 <html> 頁面),而非你程式碼預期的 JSON。
為什麼我的 fetch 回傳的是 HTML 而非 JSON?
常見原因:404/500 錯誤頁、跳轉到登入頁的驗證重新導向,或 URL 寫錯,被解析到前端而非 API。檢查 res.ok 並列印 await res.text() 看看回了什麼。
怎麼避免它讓我的應用崩潰?
解析前加 guard:檢查 res.ok 與 Content-Type 標頭,把 JSON.parse()/res.json() 包在 try/catch 裡,讓 HTML 回應變成可處理的錯誤,而非例外。
立刻修復
如果有一段回應 body 你拿不準,貼到 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」 —— 完整語法錯誤參考