How to fix SyntaxError: Unexpected token < in JSON at position 0 in React

ReactINTERMEDIATEMEDIUMUpdated Dec 10, 2025· 105 views

React throws this parser error when it tries to JSON.parse() a response that is actually HTML, which usually happens because the front-end asset server (webpack dev server or CRA proxy) answers the request instead of your API.

Quick fix

Fixes SyntaxError: Unexpected token < in JSON at position 0

fetch('/api/threads')
  .then(async (response) => {
    const text = await response.text();
    console.warn('Response text:', text);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return JSON.parse(text);
  })
  .then((data) => setThreads(data))
  .catch((error) => console.error('JSON parse failed', error));
  1. Log the raw response text before parsing
  2. Confirm the request URL hits the real API endpoint
  3. Have your proxy forward JSON responses without rewriting
  4. Guard the fetch response before calling .json()
react — error
fetch('/api/threads')
.then(async (response) => {
const text = await response.text();
console.warn('Response text:', text);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return JSON.parse(text);
})
.then((data) => setThreads(data))
.catch((error) => console.error('JSON parse failed', error));
SyntaxError: Unexpected token < in JSON at position 0

What this error means

JavaScript reports this error when JSON.parse() or response.json() sees a "<" character instead of one of the permitted JSON tokens. In React apps this almost always means the HTTP response was an HTML page—often the index.html served by the webpack dev server or a 404/500 error page—so the parser aborts before it can deserialize the JSON you expected.

Typical symptoms

  • Browser console highlights the fetch/$.ajax error line with "SyntaxError: Unexpected token < in JSON at position 0"
  • Network tab shows a 200 response whose body starts with "<!doctype html>" or other markup
  • Requests to /api routes keep retrying inside a useEffect or polling loop
  • The fetch/axios promise rejects without the JSON payload you expect
  • Content-Type header on the response is text/html even though your code assumes application/json

Common causes

  1. React is requesting the wrong host/port (webpack dev server on :3000) because the API URL is relative or proxy is misconfigured
  2. The backend route is missing, returns a generic HTML error page, or redirects to the SPA shell
  3. A proxy (in CRA/webpack) is intercepting the call and serving index.html instead of forwarding to the API
  4. The API returns HTML when authentication fails, validation throws, or the server crashes
  5. A load balancer, CDN, or middleware is modifying the response to an HTML error page before React can parse it
  6. Content-Type on the response is not application/json, so fetch() still tries to parse markup

How to fix "SyntaxError: Unexpected token < in JSON at position 0"

1Log the raw response text before parsing

If you call fetch() or $.ajax().then(r => r.json()), the parser error hides the HTML payload. Log the response.text() (or xhr.responseText) to prove it is markup before anything else runs:

javascript
fetch('/api/threads')
  .then(async (response) => {
    const text = await response.text();
    console.warn('Response text:', text);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return JSON.parse(text);
  })
  .then((data) => setThreads(data))
  .catch((error) => console.error('JSON parse failed', error));

If the console shows <!doctype html> or your React app shell, the parser error is masking an HTML response.

2Confirm the request URL hits the real API endpoint

Check the browser's Network tab and make sure the “Request URL” matches the port where your API is running (e.g. http://localhost:3001/api/threads). React running on :3000 will hit http://localhost:3000 unless you:

- Pass an absolute URL from props or environment variables
- Configure proxy in CRA's package.json
- Use setupProxy.js to forward /api to your backend
- Use process.env.NEXT_PUBLIC_API_BASE_URL in Next.js

If the request is landing on /?=... (the webpack dev server refresh endpoint) instead of /api/threads, configure your URL or proxy so the backend returns JSON instead of HTML.

3Have your proxy forward JSON responses without rewriting

In Create React App or a webpack dev server, the proxy should forward the path rather than serving index.html. Example setupProxy.js:

javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:3001',
      changeOrigin: true,
      pathRewrite: {
        '^/api': '/api'
      },
      onProxyReq(proxyReq) {
        proxyReq.setHeader('Accept', 'application/json');
      }
    })
  );
};

If you rely on Next.js rewrites, ensure they route to the API to avoid hitting the React shell.

4Guard the fetch response before calling .json()

Only parse JSON when the response is successful and reports the right content-type:

javascript
const response = await fetch('/api/threads');

if (!response.ok) {
  const body = await response.text();
  throw new Error(`API error: ${response.status} - ${body}`);
}

const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
  throw new Error(`Expected JSON but got ${contentType}`);
}

const data = await response.json();

That way you can early-exit when the server returns HTML and log the exact payload rather than letting JSON.parse throw.

5Ensure the backend always returns JSON

If you control the API, send JSON even for errors and set the header explicitly:

javascript
app.get('/api/threads', (req, res) => {
  try {
    res.json({ threads: getThreads() });
  } catch (error) {
    res.status(500).json({ error: 'server failure', details: error.message });
  }
});

Avoid rendering HTML error templates for AJAX endpoints. If Express hits a route that does not exist, configure a JSON fallback to prevent the React client from sniffing markup.

Advanced notes

Webpack dev server nuance: When running React on :3000 and Express on :3001, CRA’s proxy or webpackDevServer proxy must forward /api requests to the backend instead of replying with the SPA shell. In addition to the proxy shown above, you can also hardcode the backend base URL as an environment variable like REACT_APP_API_URL=http://localhost:3001/api and call fetch(${process.env.REACT_APP_API_URL}/threads).

Debugging tips: Look in the Network tab’s “Response” panel for the failing call. If you see markup, copy it into the console and run JSON.parse(text) to confirm the parser will fail with the same message. Some CDNs or middleware rebroadcast an HTML login page (with <form> etc.) when authentication fails; that is what the unexpected < denotes.

Frequently asked questions

What does "SyntaxError: Unexpected token < in JSON at position 0" mean?
json() sees a "<" character instead of one of the permitted JSON tokens. html served by the webpack dev server or a 404/500 error page—so the parser aborts before it can deserialize the JSON you expected.
How do I fix "SyntaxError: Unexpected token < in JSON at position 0"?
React throws this parser error when it tries to JSON.parse() a response that is actually HTML, which usually happens because the front-end asset server (webpack dev server or CRA proxy) answers the request instead of your API.
What causes "SyntaxError: Unexpected token < in JSON at position 0"?
React is requesting the wrong host/port (webpack dev server on :3000) because the API URL is relative or proxy is misconfigured The backend route is missing, returns a generic HTML error page, or redirects to the SPA shell A proxy (in CRA/webpack) is intercepting the call and serving index.html instead of forwarding to the API
Is the "SyntaxError: Unexpected token < in JSON at position 0" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading