Unexpected token '<', "<!DOCTYPE "... is not valid JSON
여러분의 코드가 HTML 문서에 JSON.parse(또는 res.json())를 호출했습니다. 0번 위치의 꺾쇠는 <!DOCTYPE html>의 시작입니다. JSON이 잘못된 게 아니라, 애초에 JSON을 받지 못한 것입니다. 고쳐야 할 것은 파서가 아니라 요청입니다.
이렇게 적힌 것을 보게 될 수도 있습니다
- Unexpected token < in JSON at position 0
- Unexpected token '<', "<html><hea"... is not valid JSON
- SyntaxError: Unexpected token < in JSON at position 0
V8은 2022년에 JSON 오류 메시지 대부분을 다시 썼습니다. 검색 결과에 나오는 건 여전히 옛 문구가 대부분이지만, 지금의 어떤 런타임도 그 문구를 내보내지 않습니다.
JSON을 붙여넣고 어디서 깨지는지 정확히 확인하기
붙여 넣은 것은 여러분의 브라우저를 떠나지 않습니다. connect-src 허용 목록 덕분에 이는 약속이 아니라 브라우저가 강제하는 보장입니다. 직접 확인하기
실제 원인
어느 쪽이 답인 경우가 많은지, 그 순서대로 정리했습니다.
-
01 서버가 오류 페이지를 돌려줌
404, 500, 게이트웨이 오류가 HTML로 렌더링된 경우입니다. 많은 프레임워크가 명시적으로 JSON을 요구하지 않으면 API 라우트에도 HTML 오류 페이지를 돌려줍니다. 본문보다 상태 코드를 먼저 확인하세요.
깨지는 예
const data = await fetch('/api/user').then((r) => r.json());되는 예
const res = await fetch('/api/user', { headers: { Accept: 'application/json' } }); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); const type = res.headers.get('content-type') ?? ''; if (!type.includes('application/json')) { throw new Error(`Expected JSON, got ${type}: ${(await res.text()).slice(0, 200)}`); } const data = await res.json(); -
02 싱글 페이지 앱이 모르는 경로를 index.html로 바꿔치기함
개발 중에 가장 흔한 원인입니다. Vite, Create React App을 비롯한 대부분의 개발 서버는 알아보지 못하는 경로에 index.html을 내줍니다. 그래서 API 라우트의 오타나 빠진 프록시 설정 하나로 자기 앱이 HTML로, 상태 200으로 돌아옵니다. res.ok만 확인하는 걸로 부족한 이유가 이것입니다.
-
03 URL이 엉뚱한 데로 해석됨
fetch("api/user") 같은 상대 경로는 현재 라우트를 기준으로 해석되므로, /settings/profile에서는 /settings/api/user를 요청합니다. 앞에 슬래시를 붙이거나 절대 URL을 쓰세요.
깨지는 예
fetch('api/user')되는 예
fetch('/api/user') -
04 캡티브 포털, VPN, 프록시가 요청을 가로챔
호텔 와이파이, 회사 프록시, Cloudflare 봇 챌린지는 자기 HTML 페이지를 돌려줍니다. 일부 사용자만, 그것도 일부 네트워크에서만 겪는 오류로 나타납니다.
-
05 PHP가 JSON 앞에 경고를 출력함
응답 본문 앞에 notice나 warning이 출력되면 문서가 {가 아니라 <br />로 시작합니다. JSON은 HTML 뒤에 그대로 있습니다. 운영 환경에서는 display_errors를 끄세요.
다른 런타임에서의 같은 실수
밑바탕의 문제는 똑같고, 다른 건 문구뿐입니다. 동료가 이 중 하나를 들고 왔다면, 그 사람이 보고 있는 건 여러분이 보는 것과 같습니다.
| Python | Expecting value: line 1 column 1 (char 0) |
|---|---|
| C# (Newtonsoft) | Unexpected character encountered while parsing value: <. Path '', line 0, position 0. |
| C# (System.Text.Json) | '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0. |
| Java (Jackson) | Unexpected character ('<' (code 60)): expected a valid value |
자주 묻는 질문
- 왜 "Unexpected token < in JSON at position 0"에서 문구가 바뀌었나요?
- V8이 2022년에 문제가 된 입력의 일부를 포함하도록 JSON 오류 메시지를 다시 썼습니다. Chrome 104 이상과 Node 18 이상이 새 문구를 출력합니다. 옛 문자열은 여전히 검색 결과와 스택오버플로 답변 대부분이 보여 주는 것이지만, 지금의 어떤 런타임도 그것을 내보내지 않습니다.
- 제 JSON이 깨진 건가요?
- 아닙니다. JSON은 아예 등장하지 않습니다. 파서가 읽고 있는 건 HTML 문서입니다. 요청이 돌려주는 것을 고치면 JSON을 건드리지 않아도 오류가 사라집니다.
- 상태 코드가 200인데 왜 HTML인가요?
- 모르는 라우트에 index.html로 폴백하는 개발 서버는 HTML 본문을 200으로 돌려줍니다. 상태뿐 아니라 Content-Type 헤더도 확인하세요.