BugHunt

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

You called res.json() or JSON.parse() on something that is not JSON — almost always an HTML error page from the server.

JavaScriptType errors

What it means

The first character JSON.parse saw was <, which starts HTML. Your request hit a 404 page, a login redirect, or a dev-server fallback, and the server sent HTML with a normal-looking response. Older browsers word it "Unexpected token < in JSON at position 0". A token of 'o' ("[object Object]") means you parsed something that was already an object; "Unexpected end of JSON input" means the body was empty.

Common causes

1. The URL is wrong and returns an HTML 404

fetch does not throw on 404. The HTML error page goes straight into res.json().

Breaks

const res = await fetch("/api/user");
const data = await res.json();

Works

const res = await fetch("/api/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

2. Parsing a value that is already an object

res.json() and axios already parse. Parsing again stringifies to "[object Object]" first.

Breaks

const data = await res.json();
const user = JSON.parse(data);

Works

const user = await res.json();

3. An empty response body

A 204 No Content or an empty 200 has nothing to parse.

Breaks

const res = await fetch(url, { method: "DELETE" });
await res.json();

Works

const res = await fetch(url, { method: "DELETE" });
const data = res.status === 204 ? null : await res.json();

How to find it in your own code

Replace res.json() with res.text() for a moment and log the result — you will see the HTML page or empty string immediately, and its title usually explains the problem. Then check res.ok before parsing, every time.

Still not sure why yours breaks?

Paste it into the visualizer and watch it run line by line, with every variable at every step. Free, and it runs in your browser.

Other common errors