Исправление ошибки 'Unexpected token < in JSON at position 0'
Вы вызываете response.json(), и ваше приложение падает с ошибкой SyntaxError: Unexpected token '<' in JSON at position 0. Символ < точно указывает на то, что произошло: ваш код ожидал JSON, но получил HTML.
Эта ошибка парсинга JSON постоянно встречается в современных frontend-стеках — browser fetch, Node.js, API-роуты Next.js и serverless-функции. Понимание причин её возникновения и быстрая отладка сэкономят вам часы разочарований.
Ключевые выводы
- Ошибка “Unexpected token <” означает, что вы парсите HTML как JSON — символ
<обычно происходит из<!DOCTYPE html>или HTML-тега. - Распространённые причины включают неправильные URL, редиректы аутентификации, серверные ошибки, возвращающие HTML-страницы, и отсутствующие заголовки Content-Type.
- Отладка начинается с проверки HTTP-статуса, затем заголовка Content-Type, затем сырого тела ответа с помощью
response.text(). - Создавайте защитные обёртки для fetch, которые валидируют ответы перед парсингом, чтобы отлавливать эти проблемы с понятными сообщениями об ошибках.
Почему ваш API возвращает HTML вместо JSON
Ошибка означает, что JSON.parse() встретил HTML-документ там, где ожидался валидный JSON. Символ < в позиции 0 обычно является открывающим символом <!DOCTYPE html> или HTML-тега.
Несколько сценариев вызывают это несоответствие Content-Type:
Неправильные или опечатки в URL эндпоинтов. Опечатка в URL вашего fetch возвращает страницу 404 — которая является HTML, а не JSON.
Редиректы аутентификации. Истёкшие токены или отсутствующие заголовки авторизации вызывают редиректы на страницы входа. Ваш fetch получает HTML страницы входа.
Серверные ошибки, возвращающие HTML-страницы ошибок. Ошибка 500 от вашего API-шлюза или облачного провайдера часто возвращает стилизованную HTML-страницу ошибки вместо JSON-ответа с ошибкой.
Dev-серверы, возвращающие fallback HTML для неизвестных роутов. Многие SPA возвращают HTML-оболочку для несовпадающих путей, хотя некоторые современные dev-серверы возвращают структурированные payload ошибок.
Отсутствующие или неправильные заголовки 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.
Распространённые реальные подводные камни
Неправильные базовые URL API в продакшене. Переменные окружения, указывающие на неправильные домены или отсутствующие завершающие слэши, вызывают 404, которые возвращают HTML.
API-шлюзы и CDN, перехватывающие запросы. Сервисы вроде Cloudflare, AWS API Gateway или Vercel могут возвращать свои собственные HTML-страницы ошибок при лимитах запросов, таймаутах или неправильных конфигурациях.
Next.js App Router и редиректы middleware. Middleware или редиректы авторизации часто возвращают HTML, хотя некоторые пути редиректов вместо этого выдают небольшие JSON payload.
Серверный код без JSON-заголовков. Ваш API-обработчик возвращает данные, но забывает установить content type ответа:
// ❌ Отсутствует Content-Type
res.send({ data: 'value' })
// ✅ Явный JSON-ответ
res.json({ data: 'value' })
Проблемы CORS. Браузеры блокируют неудачные preflight-запросы до запуска вашего кода, но неправильно настроенные серверы или прокси всё ещё могут вернуть HTML-страницу ошибки, которую получит ваш fetch-вызов.
Защитный паттерн 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, которые валидируют ответы перед парсингом, чтобы отлавливать эти проблемы с понятными сообщениями об ошибках.
Часто задаваемые вопросы
Продакшен-окружения часто имеют разные конфигурации. Проверьте, что переменная окружения с базовым URL API установлена правильно, убедитесь, что ваши токены аутентификации валидны, и подтвердите, что любые прокси или CDN между вашим фронтендом и API настроены правильно. Продакшен-серверы также могут иметь более строгие политики CORS, которые вызывают сбой запросов.
Да. Оберните ваши fetch-вызовы в защитную функцию, которая проверяет статус ответа и заголовок Content-Type перед вызовом response.json(). Это позволит вам обработать ошибку корректно и показать пользователям понятное сообщение вместо падения приложения. Всегда валидируйте ответы перед их парсингом.
Используйте вкладку 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.