SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON —— cela signifie presque toujours une seule chose : ton code a appelé JSON.parse() (ou res.json()) sur une page HTML, pas du JSON. Le < en position 0 est l'ouverture de <!DOCTYPE html> ou <html>. Voici exactement pourquoi ça arrive et comment le corriger.
À quoi ressemble l'erreur
// V8 (Chrome / Node / Edge)
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
// V8 plus ancien
SyntaxError: Unexpected token < in JSON at position 0
// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
// Safari
SyntaxError: JSON Parse error: Unexpected identifier "<"Position 0 est l'indice clé : le tout premier octet est déjà mauvais, donc la réponse n'était jamais du JSON à la base.
Pourquoi ça arrive : tu as reçu du HTML
Un appel fetch ou AJAX a renvoyé un document HTML au lieu du JSON attendu. Sources habituelles :
- Une page d'erreur 404 / 500 servie en HTML par ton framework ou serveur web
- Une redirection login ou auth vers une page HTML (session expirée)
- Une mauvaise URL —— tu tapes sur la route app/SPA au lieu du endpoint API
- Un proxy, CDN ou captive portal qui renvoie sa propre page HTML
Exemple cassé
const res = await fetch('/api/user'); // le serveur a renvoyé une page HTML 404
const data = await res.json(); // ❌ Unexpected token '<' …
// res.json() essaie de parser "<!DOCTYPE html>…" comme du JSONExemple corrigé
const res = await fetch('/api/user');
// 1) Vérifie le statut avant de parser
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
// 2) Optionnel : confirme que c'est vraiment du JSON
const type = res.headers.get('content-type') ?? '';
if (!type.includes('application/json')) {
const text = await res.text();
throw new Error(`Expected JSON, got: ${text.slice(0, 80)}…`);
}
const data = await res.json(); // ✅ sûrDeux sources moins évidentes qu'il vaut la peine de connaître
- Réponses bloquées par WAF ou rate-limit. Un WAF, CDN ou couche de protection DDoS peut intercepter l'appel et renvoyer sa propre page HTML de challenge / blocage —— l'interstitiel Cloudflare, la page de refus AWS WAF, le mur captive-portal d'un Wi-Fi public. Le statut est souvent
403/429/503avec un corps HTML. Inspecte la réponse dans l'onglet Network et regarde si des headers commeCF-RayouX-Amz-Cf-Idsont présents pour le détecter. - Le fallback d'historique SPA qui renvoie
index.html. Les hébergeurs statiques et serveurs de dev réécrivent souvent tout chemin inconnu vers/index.htmlpour que le router client prenne le relais. Si ton front-end fetch/api/useren chemin relatif par accident et que le proxy n'est pas configuré, le serveur renvoie silencieusement la coquille HTML de ta SPA avec un200—— tu obtiens alors une version déroutante « 200 OK mais c'est du HTML » de la même erreur.
Comment le corriger —— étape par étape
- Log le corps brut :
console.log(await res.clone().text()). S'il commence par<!DOCTYPEou<html>, tu as du HTML. - Vérifie
res.ok/res.statusavant d'appelerres.json()—— un 4xx/5xx renvoie typiquement une page HTML d'erreur. - Vérifie l'URL dans l'onglet Network. Un chemin relatif peut se résoudre vers l'index.html de ta SPA au lieu de l'API.
- Vérifie l'authentification —— une redirection vers la page de login signifie que ton token/session a expiré.
- Inspecte
Content-Type—— branche dessus pour gérer les réponses non-JSON au lieu de les parser aveuglément.
Foire aux questions
Que signifie « Unexpected token < in JSON at position 0 » ?
Le parser a rencontré < comme premier caractère, ce qui n'est jamais du JSON valide. La réponse était du HTML (une page <!DOCTYPE> ou <html>), pas le JSON que ton code attendait.
Pourquoi mon fetch renvoie-t-il du HTML au lieu de JSON ?
Causes courantes : page d'erreur 404/500, redirection d'auth vers une page de login, ou mauvaise URL qui se résout vers ton front-end au lieu de l'API. Vérifie res.ok et log await res.text() pour voir ce qui est revenu.
Comment empêcher ça de casser mon app ?
Guard avant de parser : vérifie res.ok et le header Content-Type, et enveloppe JSON.parse()/res.json() dans un try/catch pour qu'une réponse HTML devienne une erreur gérée plutôt qu'une exception.
Corrige-le maintenant
Si tu as un corps de réponse dont tu n'es pas sûr, colle-le dans JSON Fix —— il te dit instantanément si c'est du JSON valide ou autre chose (comme du HTML). Tout tourne dans ton navigateur.
- JSON Fix —— valide et répare le JSON dans ton navigateur
- Comment corriger les erreurs « Unexpected Token » de JSON.parse —— chaque variante de token
- Unexpected token u in JSON at position 0 —— l'erreur jumelle de
undefined - Corriger « [object Object] is not valid JSON » —— la référence complète des erreurs de syntaxe