What Does "Unexpected token < in JSON at position 0" Mean?
This error means JSON.parse() tried to parse a string that starts with `<` — which is an HTML tag, not JSON. Your code expected a JSON response but received an HTML page instead.
SyntaxError: Unexpected token < in JSON at position 0The `<` at position 0 is almost always the `<` from an HTML `<!DOCTYPE html>` declaration or an `<html>` tag.
Why Does This Happen?
Cause 1: The server returned an error page
Your API returned a 404 Not Found, 500 Internal Server Error, or 401 Unauthorized HTML error page instead of JSON. Many web servers and frameworks serve HTML error pages by default even when the client expects JSON.
Cause 2: Wrong API URL
The endpoint URL is wrong. You hit a web page instead of the API route. The server returns the webpage HTML, and your code tries to parse it as JSON.
Cause 3: Redirect to login page
You made an authenticated API request but your session expired. The server redirects to a login page (HTML), and your fetch follows the redirect and gets HTML back.
Cause 4: Proxy or CDN returning its own error page
A Cloudflare, Nginx, or load balancer error page is returned instead of your API response.
Cause 5: Development server misconfiguration
In local development, the API proxy is misconfigured. Requests that should go to your backend hit the frontend server instead, which returns the HTML index page.
How to Diagnose
Open the browser Network tab (F12 > Network). Find the failing request. Click it and check:
- Status code — is it 404, 500, 302, or 200?
- Response tab — do you see HTML? Copy it and paste it into your browser to see what page it is.
- URL — is the request going to the right endpoint?
Fix 1: Check response.ok Before Parsing
Always check the HTTP status before calling .json():
const response = await fetch('/api/users');
if (!response.ok) {
const text = await response.text(); // read as text, not JSON
console.error('Server returned:', response.status, text);
throw new Error(`HTTP error ${response.status}`);
}
const data = await response.json();This gives you the actual HTML error page in the console so you know exactly what went wrong.
Fix 2: Always Set the Accept Header
Tell the server you expect JSON, not HTML:
const response = await fetch('/api/users', {
headers: {
'Accept': 'application/json',
},
});Many Express and Django apps will return a JSON error response (instead of HTML) when the request has `Accept: application/json`.
Fix 3: Correct the API URL
Double-check the URL. Common mistakes:
- Missing the `/api` prefix — `/users` instead of `/api/users`
- Wrong port — `http://localhost:3000` instead of `http://localhost:8000`
- Extra slash or typo in the path
Fix 4: Handle Authentication Redirects
If the error only happens after login expires, catch 401 and redirect to login:
const response = await fetch('/api/data', {
credentials: 'include', // send cookies
});
if (response.status === 401) {
window.location.href = '/login';
return;
}
const data = await response.json();Fix 5: Use a Safe JSON Parse Wrapper
Wrap JSON parsing to catch and log the raw text when it fails:
async function fetchJSON(url, options) {
const response = await fetch(url, options);
const text = await response.text();
try {
return JSON.parse(text);
} catch {
console.error('JSON parse failed. Raw response:', text.slice(0, 500));
throw new Error(`Server returned non-JSON response (status ${response.status})`);
}
}Quick Checklist
- Open Network tab, check the response body — is it HTML?
- Check the status code — 404, 500, or 302 all indicate a problem before parsing
- Verify the URL is exactly correct
- Add `Accept: application/json` to your request headers
- Always check `response.ok` before calling `.json()`
Use JSONKit's JSON Validator at /json-validator to confirm your JSON is valid before sending it, and to inspect any JSON response you receive.