{}JSON FYI

SyntaxError: Unexpected token '<' in JSON — HTML returned instead of JSON

Why your JSON parser sees '<' at position 0, how to detect that your server sent an HTML error page instead of JSON, and the exact fetch pattern that prevents it.

·5 min read

The error "SyntaxError: Unexpected token '<' in JSON at position 0" means the very first byte of the string you handed to JSON.parse is a less-than sign. JSON cannot start with '<'. The server sent HTML — an error page, a login redirect, or a misconfigured endpoint — instead of JSON.

Why this happens

HTTP responses have a Content-Type header. When the server returns JSON it should send Content-Type: application/json. But when something goes wrong server-side (authentication error, 404, 500, nginx misconfiguration), many frameworks return an HTML error page that starts with <!DOCTYPE html> or <html>. If your client calls .json() on that response without checking the status code first, you get the '<' error.

The fix: always check response.ok

// ✗ common mistake — calls .json() on any response
const data = await fetch('/api/users').then(r => r.json());

// ✓ check HTTP status before parsing
const res = await fetch('/api/users');
if (!res.ok) {
  const text = await res.text();   // read the actual error body
  throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
}
const data = await res.json();

How to debug it in < 30 seconds

  • Open DevTools → Network tab → find the failing request → click it → check the Response tab. If you see HTML there, the problem is server-side.
  • Check the HTTP status code. 401 (auth), 403 (forbidden), 404 (wrong URL), 500 (server crash), and 502/503 (gateway errors) all commonly return HTML.
  • Add a temporary console.log(await res.text()) before the .json() call to see the raw body.

Server-side checklist

  • Confirm the route exists and the URL is correct — a missing route returns a 404 HTML page.
  • Check that authentication middleware is configured to return JSON errors, not HTML redirects.
  • Verify the server is actually running — a proxy with no upstream returns HTML.
  • In development, check for port mismatches (app on :3000, fetching from :3001).

Paste your JSON into the validator →

Get the exact line, column, and a fix hint in seconds — no upload, no signup.

Open JSON Validator →

Frequently asked questions

Can this happen in Node.js (not just browsers)?+

Yes — any time you use node-fetch, axios, got, or the built-in fetch in Node 18+ and call .json() without checking the status. The error message may differ slightly (axios wraps it) but the root cause is the same.

Why does the browser DevTools Network tab show JSON but my code still fails?+

DevTools may display a cached or re-issued request. Check the exact request your code makes, not a refreshed browser fetch. Also verify the Content-Type header in the response.

How do I make all my fetches safe by default?+

Create a wrapper function that checks res.ok and throws a descriptive error, then use that everywhere instead of raw fetch. This pattern is standard in most production codebases.

Related tools & guides