If response.json() throws Unexpected token '<', inspect the response before changing your JSON parser. The first character is often the start of <!doctype html>: your request received an HTML page.
This can happen when an API route returns a 404 page, an authentication flow redirects to a login page, or a frontend fallback serves index.html for an unknown path. A 200 status alone does not prove the response is JSON.
Read the response once
async function getJson(url) {
const response = await fetch(url);
const contentType = response.headers.get("content-type") ?? "";
const raw = await response.text();
if (!response.ok || !contentType.toLowerCase().includes("json")) {
throw new Error(
`Expected JSON; got HTTP ${response.status}, ${contentType || "unknown content type"} at ${response.url}. Body: ${raw.slice(0, 160)}`
);
}
try {
return JSON.parse(raw);
} catch (error) {
throw new Error(
`Invalid JSON at ${response.url}: ${error.message}`,
{ cause: error }
);
}
}
const products = await getJson("/api/products");
Read the body with text() once. Calling response.json() afterward on the same response fails because the body has already been consumed. Parsing the captured string with JSON.parse(raw) avoids that trap.
What to check next
- Look at HTTP status, final URL, and Content-Type in the Network tab. A login redirect can change the final URL; a missing route can return an HTML error page.
- If the response is HTML, fix the endpoint, authentication, routing, or server response. Editing a formatter cannot turn an HTML page into the intended API data.
- If it really is JSON but parsing fails, inspect the returned text around the position in the error. Common mistakes include trailing commas, single-quoted strings, and an unquoted property name. Those are not valid JSON even though they can look like JavaScript object literals.
- Do not automatically “repair” incomplete or truncated data. Retry the request or fix the producer; guessing the missing portion can silently change the meaning.
For a quick, browser-based inspection of an actual JSON payload, QuickTiny's JSON Formatter shows parse errors and handles some unambiguous formatting mistakes. I maintain QuickTiny. Check sensitive payloads against your own data-handling policy before using any tool.
The key diagnostic step is simple: verify what the server sent before assuming the parser is broken.
Top comments (0)