Fix “Unexpected token < in JSON at position 0” Error

When you fetch data from an API and parse it with JSON.parse() or response.json(), one of the most common errors you’ll hit is:

SyntaxError: Unexpected token < in JSON at position 0

The position 0 is the clue: the very first character of the string is <. That character opens an HTML tag, which means the string you are parsing is HTML, not JSON. This guide explains why it happens and how to fix it.

What the error actually means

A valid JSON string can only start with a few characters: {, [, ", a digit, t/f/n, or -. The character < never starts valid JSON. So when JSON.parse() sees < at position 0, it knows immediately that the input isn’t JSON — it is almost always the beginning of an HTML document like <!DOCTYPE html> or <html>.

The most common causes

1. The API returned an error page

When a server returns a 404 or 500, many frameworks send back an HTML error page. Your code assumes the response is JSON, but it’s actually HTML:

const res = await fetch('/api/user/9999'); // user doesn't exist -> 404
const data = await res.json(); // ❌ Unexpected token < in JSON at position 0

2. The server redirected to a login page

If an endpoint requires authentication and you’re not logged in, the server often redirects to an HTML login page instead of returning a JSON 401:

const res = await fetch('/api/account'); // redirected to /login (HTML)
const data = await res.json(); // ❌

3. The URL is wrong

A typo in the URL can return a generic HTML page (or a single-page app’s index.html) instead of a JSON endpoint:

const res = await fetch('/api/users'); // ❌ should be /api/user

4. A proxy or CDN blocked the request

A WAF, CDN, or corporate proxy can intercept the request and return an HTML “blocked” or “challenge” page.

How to fix it

Check the status and Content-Type before parsing:

const res = await fetch('/api/user');
if (!res.ok) {
  throw new Error(`Request failed: ${res.status}`);
}
const contentType = res.headers.get('content-type');
if (!contentType?.includes('application/json')) {
  throw new Error(`Expected JSON, got ${contentType}`);
}
const data = await res.json();

Log the raw body to see what you actually received:

const res = await fetch('/api/user');
const text = await res.text();
console.log(text.slice(0, 200)); // inspect the first 200 characters

If the output starts with <!DOCTYPE html> or <html>, you’ve found the problem: the server sent HTML.

Fix the root cause — correct the URL, handle 401s by logging in first, or configure the server to return JSON error responses instead of HTML pages.