修复 'Unexpected token < in JSON at position 0' 错误
当你调用 response.json() 时,应用程序崩溃并抛出 SyntaxError: Unexpected token '<' in JSON at position 0 错误。这个 < 字符准确地告诉你发生了什么:你的代码期望接收 JSON,但实际收到的是 HTML。
这个 JSON 解析错误在现代前端技术栈中频繁出现——包括浏览器 fetch、Node.js、Next.js API 路由和无服务器函数。理解它为什么发生以及如何快速调试,将为你节省数小时的挫折时间。
核心要点
- “Unexpected token <” 错误意味着你正在将 HTML 当作 JSON 解析——这个
<通常来自<!DOCTYPE html>或 HTML 标签。 - 常见原因包括错误的 URL、身份验证重定向、返回 HTML 页面的服务器错误,以及缺失的 Content-Type 头。
- 调试时首先检查 HTTP 状态码,然后检查 Content-Type 头,最后使用
response.text()检查原始响应体。 - 构建防御性的 fetch 包装器,在解析前验证响应,以便通过清晰的错误消息捕获这些问题。
为什么你的 API 返回 HTML 而不是 JSON
这个错误意味着 JSON.parse() 在期望有效 JSON 的地方遇到了 HTML 文档。位置 0 处的 < 通常是 <!DOCTYPE html> 或 HTML 标签的开始字符。
以下几种场景会导致这种 Content-Type 不匹配:
错误或拼写错误的端点 URL。 fetch URL 中的拼写错误会返回 404 页面——这是 HTML,而不是 JSON。
身份验证重定向。 过期的令牌或缺失的认证头会触发重定向到登录页面。你的 fetch 接收到的是登录页面的 HTML。
服务器错误返回 HTML 错误页面。 来自 API 网关或云服务提供商的 500 错误通常返回样式化的 HTML 错误页面,而不是 JSON 错误响应。
开发服务器为未知路由提供回退 HTML。 许多单页应用(SPA)对不匹配的路径返回 HTML 外壳,尽管一些现代开发服务器会返回结构化的错误负载。
缺失或不正确的 Content-Type 头。 忘记设置 Content-Type: application/json 的服务器代码可能默认返回 HTML。
Fetch API 错误的实用调试流程
当 JSON 解析失败时,按照以下顺序识别根本原因:
步骤 1:检查 HTTP 状态码
在解析之前,验证响应状态。4xx 或 5xx 状态通常表示响应不会是 JSON:
const response = await fetch('/api/data')
if (!response.ok) {
console.error(`HTTP ${response.status}: ${response.statusText}`)
const text = await response.text()
console.error('Response body:', text.substring(0, 200))
throw new Error(`Request failed with status ${response.status}`)
}
步骤 2:验证 Content-Type 头
检查服务器声称发送的内容类型:
const contentType = response.headers.get('content-type')
if (!contentType || !contentType.includes('application/json')) {
const text = await response.text()
throw new Error(`Expected JSON, received: ${contentType}. Body: ${text.substring(0, 100)}`)
}
const data = await response.json()
步骤 3:记录原始响应体
当解析失败时,使用 response.text() 而不是 response.json() 来查看实际接收到的内容:
async function fetchWithDebug(url) {
const response = await fetch(url)
const text = await response.text()
try {
return JSON.parse(text)
} catch (error) {
console.error('Failed to parse JSON. Raw response:', text.substring(0, 500))
throw error
}
}
Discover how at OpenReplay.com.
常见的实际问题
生产环境中不正确的 API 基础 URL。 环境变量指向错误的域名或缺少尾部斜杠会导致返回 HTML 的 404 错误。
API 网关和 CDN 拦截请求。 Cloudflare、AWS API Gateway 或 Vercel 等服务可能会为速率限制、超时或配置错误返回自己的 HTML 错误页面。
Next.js App Router 和中间件重定向。 中间件或认证重定向通常返回 HTML,尽管某些重定向路径会发出小型 JSON 负载。
服务器代码缺少 JSON 头。 你的 API 处理程序返回数据但忘记设置响应内容类型:
// ❌ 缺少 Content-Type
res.send({ data: 'value' })
// ✅ 明确的 JSON 响应
res.json({ data: 'value' })
CORS 问题。 浏览器在你的代码运行之前会阻止失败的预检请求,但配置错误的服务器或代理仍可能返回你的 fetch 调用接收到的 HTML 错误页面。
防御性 Fetch 模式
用验证包装你的 fetch 调用,以便尽早捕获这些问题:
async function safeFetch(url, options = {}) {
const response = await fetch(url, options)
if (!response.ok) {
const body = await response.text()
throw new Error(`HTTP ${response.status}: ${body.substring(0, 100)}`)
}
const contentType = response.headers.get('content-type')
if (!contentType?.includes('application/json')) {
const body = await response.text()
throw new Error(`Invalid content-type: ${contentType}`)
}
return response.json()
}
总结
“Unexpected token <” 错误始终意味着你正在将 HTML 当作 JSON 解析。调试时首先检查状态码,然后检查 Content-Type 头,最后检查原始响应体。大多数情况可以追溯到错误的 URL、认证重定向或返回 HTML 页面的服务器错误。构建防御性的 fetch 包装器,在解析前验证响应,以便通过清晰的错误消息捕获这些问题。
常见问题
生产环境通常有不同的配置。检查你的 API 基础 URL 环境变量是否设置正确,验证你的身份验证令牌是否有效,并确认前端和 API 之间的任何代理或 CDN 是否配置正确。生产服务器可能还有更严格的 CORS 策略,导致请求失败。
可以。将你的 fetch 调用包装在一个防御性函数中,在调用 response.json() 之前检查响应状态和 Content-Type 头。这样你可以优雅地处理错误,向用户显示有意义的消息,而不是崩溃。始终在解析响应之前验证它们。
使用浏览器的网络(Network)选项卡检查实际响应。如果响应显示 HTML 内容且状态为 404 或 500,则服务器正在返回错误页面。如果状态为 200 但内容是 HTML,检查你的服务器代码以确保它设置了正确的 Content-Type 头并返回 JSON。
response.json() 方法尝试将响应体解析为 JSON。如果响应体包含 HTML 或任何非 JSON 内容,解析就会失败。response.text() 方法只是将原始响应作为字符串返回而不进行解析,这就是为什么无论内容类型如何它都能工作。使用 text() 进行调试以查看你实际接收到的内容。
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.