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.
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.