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 endpoint
- 一个代理、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这类 header 能识别出来。 - SPA 历史回退返回
index.html。 静态主机和开发服务器经常把每一个未知路径重写到/index.html,让客户端路由接手。如果你的前端不小心以相对路径 fetch 了/api/user而代理没配好,服务器就会安静地返回你 SPA 的 HTML 壳,状态是200—— 你就拿到一个让人疑惑的「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 header,并把 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」 —— 完整的语法错误参考