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

Your code expected JSON, but the server sent back an HTML page, usually a 404 page or your app’s own index.html.

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

Also appears as

  • SyntaxError: Unexpected token < in JSON at position 0
  • SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
  • SyntaxError: JSON Parse error: Unrecognized token '<'

Prompt to fix it

My app fails with "Unexpected token '<', "<!DOCTYPE "... is not valid JSON". A request expected JSON and got an HTML page. Find the fetch call that fails, work out which URL it really hits in development and in production, and what HTML comes back: the index.html fallback, a 404 page, a login redirect, or an error page. Fix the URL or the server route so it returns JSON, and set any missing API URL environment variable. Add a check that reads the body as text and reports the status when a response isn’t JSON. Don’t just wrap JSON.parse in a try/catch that hides it.

Paste into Layerwise or any AI coding tool.

What it means

HTML pages begin with <!DOCTYPE html>, so the < is the first character of a web page. Your code called response.json() or JSON.parse(), the response was HTML instead of data, and the parser failed on the very first character.

The HTML is usually a page you’d recognize. Single-page apps are often set up to answer unknown URLs with index.html so client-side routing works, and the Vite dev server does this by default. When no API route exists at the URL you called, that fallback quietly answers instead, often with status 200. Other times it’s a 404 page, a sign-in page, or the host’s error page.

To see which one, open the failing request in the Network tab and look at its Response. The page title usually tells you. The fix is to call the right URL or make the server return JSON, and reading the body as text before parsing makes the next mismatch obvious.

Common causes

  • The API path is wrong, or the route was never deployed, so the host’s single-page fallback returned index.html.
  • The frontend calls a relative URL like /api/items, which the Vite dev proxy forwards locally but the static host answers in production.
  • The server returned an HTML error page, such as a 404, 500, or gateway error, and the code parsed it without checking response.ok.
  • A sign-in check redirected the request to a login page, and fetch followed the redirect.
  • The API URL environment variable is missing in production, so the code requests a path like /undefined/items.

How to fix it

  1. In the Network tab, select the failing request and open Response to see which HTML page came back and from which URL.
  2. Compare that URL with where your API actually lives. Fix the path, or set the API base URL environment variable on your host and redeploy.
  3. If the dev setup proxies /api through Vite, route /api to the same backend in production, or call the backend’s full URL.
  4. Check response.ok and the Content-Type header before calling response.json(), and log the text body when either looks wrong.
  5. Make the server return JSON for its errors too, with Content-Type: application/json.

Example fix

export async function getJson<T>(url: string): Promise<T> {
  const response = await fetch(url);
  const type = response.headers.get('Content-Type') ?? '';
  if (!response.ok || !type.includes('application/json')) {
    const text = await response.text();
    throw new Error(
      `Expected JSON from ${url}, got ${response.status} ${type}: ${text.slice(0, 200)}`
    );
  }
  return response.json() as Promise<T>;
}
All errors
SyntaxError: Unexpected end of JSON input

Unexpected end of JSON input

Your code tried to read JSON from text that was empty or cut off, so the parser ran out of input before the data was complete.

404: NOT_FOUND This page doesn’t exist It may have been moved, removed, or never existed. 404 NOT_FOUND sfo1::ntg68-1793091406762-e6fdeff1eb27

404 on page refresh

Your single-page app works from the home page, but the host has no file for deeper URLs, so reloads and shared links hit a 404.

Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

Failed to load module script

The browser asked for your app’s JavaScript and got an HTML page back, so it refused to run it and the page stayed blank.

Access to fetch at 'https://api.example.com/items' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Blocked by CORS policy

The browser hid a response from another site because that server never said your site is allowed to read it.

Build with an agent that checks its work

The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.