← Alle Artikel

Unexpected Token < in JSON an Position 0: Du hast HTML bekommen

Der Fehler „Unexpected token <" bedeutet, dass JSON.parse eine HTML-Seite (ein 404, ein Login-Redirect oder eine falsche URL) erhalten hat, kein JSON. Hier ist das Warum, mit kaputten/reparierten fetch-Beispielen.

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON —— bedeutet fast immer eines: dein Code hat JSON.parse() (oder res.json()) auf eine HTML-Seite, kein JSON angewendet. Das < an Position 0 ist der Anfang von <!DOCTYPE html> oder <html>. Hier ist genau, warum das passiert und wie du es behebst.

Wie der Fehler aussieht

// V8 (Chrome / Node / Edge)
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
// Älteres V8
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 ist der entscheidende Hinweis: das allererste Byte ist schon falsch, also war die Antwort von Anfang an nie JSON.

Warum es passiert: du hast HTML bekommen

Ein fetch- oder AJAX-Aufruf hat ein HTML-Dokument anstelle des erwarteten JSON zurückgegeben. Übliche Quellen:

  • Eine 404 / 500 Fehlerseite, die dein Framework oder Web-Server als HTML ausliefert
  • Eine Login- oder Auth-Weiterleitung auf eine HTML-Seite (Session abgelaufen)
  • Eine falsche URL —— du triffst die App/SPA-Route statt des API-Endpoints
  • Ein Proxy, CDN oder Captive Portal, der seine eigene HTML-Seite zurückgibt

Kaputtes Beispiel

const res  = await fetch('/api/user');   // Server hat eine 404-HTML-Seite zurückgegeben
const data = await res.json();           // ❌ Unexpected token '<' …
// res.json() versucht "<!DOCTYPE html>…" als JSON zu parsen

Behobenes Beispiel

const res = await fetch('/api/user');

// 1) Status vor dem Parsen prüfen
if (!res.ok) {
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
}

// 2) Optional: bestätigen, dass es wirklich JSON ist
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();           // ✅ sicher

Zwei weniger offensichtliche Quellen, die man kennen sollte

  • WAF- oder Rate-Limit-geblockte Antworten. Eine WAF, ein CDN oder eine DDoS-Schutzschicht kann den Aufruf abfangen und ihre eigene HTML-Challenge- / Block-Seite zurückgeben —— Cloudflare-Interstitial, AWS-WAF-Ablehnungsseite, die Captive-Portal-Wand öffentlicher WLANs. Der Status ist oft 403 / 429 / 503 mit HTML-Body. Untersuche die Antwort im Network-Tab und schau, ob Header wie CF-Ray oder X-Amz-Cf-Id dabei sind, um das zu erkennen.
  • Der SPA-History-Fallback, der index.html zurückgibt. Static-Hosts und Dev-Server schreiben jeden unbekannten Pfad oft auf /index.html um, damit der Client-Router übernimmt. Wenn dein Frontend versehentlich /api/user als relativen Pfad fetcht und der Proxy nicht konfiguriert ist, gibt der Server stillschweigend die HTML-Hülle deiner SPA mit 200 zurück —— und du bekommst eine verwirrende „200 OK aber HTML"-Version desselben Fehlers.

So behebst du es —— Schritt für Schritt

  1. Logge den rohen Body:console.log(await res.clone().text()). Wenn er mit <!DOCTYPE oder <html> beginnt, ist es HTML.
  2. Prüfe res.ok / res.status bevor du res.json() aufrufst —— ein 4xx/5xx liefert typischerweise eine HTML-Fehlerseite.
  3. Bestätige die URL im Network-Tab. Ein relativer Pfad kann sich auf die index.html deiner SPA statt auf die API auflösen.
  4. Prüfe die Authentifizierung —— eine Weiterleitung zur Login-Seite bedeutet, dass dein Token/deine Session abgelaufen ist.
  5. Inspiziere Content-Type —— verzweige danach, um Nicht-JSON-Antworten zu behandeln, statt sie blind zu parsen.

Häufig gestellte Fragen

Was bedeutet „Unexpected token < in JSON at position 0"?

Der Parser hat < als erstes Zeichen gefunden, was niemals gültiges JSON ist. Die Antwort war HTML (eine <!DOCTYPE>- oder <html>-Seite), nicht das JSON, das dein Code erwartet hat.

Warum gibt mein fetch HTML statt JSON zurück?

Häufige Ursachen: 404/500-Fehlerseite, Auth-Weiterleitung auf eine Login-Seite, oder falsche URL, die zu deinem Frontend statt zur API auflöst. Prüfe res.ok und logge await res.text(), um zu sehen, was zurückkam.

Wie verhindere ich, dass es meine App kaputt macht?

Guard vor dem Parsen: prüfe res.ok und den Content-Type-Header, und wickle JSON.parse()/res.json() in try/catch, sodass eine HTML-Antwort zu einem behandelten Fehler statt zu einer Exception wird.

Jetzt beheben

Wenn du einen Response-Body hast, bei dem du unsicher bist, füge ihn in JSON Fix ein —— es sagt dir sofort, ob es gültiges JSON oder etwas anderes (wie HTML) ist. Alles läuft in deinem Browser.