✦ Oplossingen · Server- en developerfouten

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

‘Unexpected token <’, ‘is not valid JSON’ of ‘Unexpected end of JSON input’? Je krijgt HTML of niets in plaats van JSON. Zo vind en los je het op.

Zo ziet de foutmelding eruit

  • SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
  • SyntaxError: Unexpected token < in JSON at position 0
  • SyntaxError: Unexpected end of JSON input
  • Failed to execute 'json' on 'Response': Unexpected end of JSON input
  • SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
  • JSON Parse error: Unrecognized token '<'

Waarom gebeurt dit?

Je JavaScript verwacht JSON (gegevens in tekstvorm, zoals {"naam":"Jan"}) en probeert die te lezen met JSON.parse() of response.json(). Wat er binnenkwam was geen geldige JSON. Het teken waar het misgaat, staat in de melding. Een < op positie 0 betekent bijna altijd: de server stuurde een HTML-pagina terug in plaats van JSON. Recente versies van Chrome en Node.js tonen daarbij het begin van die tekst, zoals "<!DOCTYPE "....

Waarom stuurt een server HTML? Omdat er iets anders misging en de server een foutpagina terugstuurde: een 404 omdat de URL niet klopt, een 500 omdat het script op de server crashte, een inlogpagina omdat je sessie verlopen is, of een HTML-pagina van een firewall of onderhoudsmelding. Bij PHP komt het ook vaak voor dat een waarschuwing (Warning, Notice, Deprecated) vóór de JSON wordt afgedrukt; die begint meestal met <br />, dus ook met een <.

Unexpected end of JSON input betekent dat het antwoord leeg was of halverwege ophield. Denk aan een server die bij een fout niets terugstuurt, een antwoord met status 204 (No Content), een script dat stopt voordat het iets afdrukt, of een JSON.parse('') op een lege waarde uit localStorage.

Zo herken je dit probleem

  • De fout staat in de console (F12) als SyntaxError, meestal bij een regel met response.json() of JSON.parse.
  • In het tabblad Network zie je bij het verzoek een status als 404, 500, 302 of 401, of een antwoord dat met <!DOCTYPE html> of <br /> begint.
  • Wordt het verzoek helemaal geblokkeerd met een melding over ‘CORS policy’, dan is dat een andere fout: zie CORS-fout.
  • Wordt de JSON wel goed gelezen maar ontbreekt er daarna een veld, dan krijg je eerder Cannot read properties of undefined.

Zo los je het op, stap voor stap

Menunamen kunnen per versie of toestel iets anders heten. Loop de stappen op volgorde af en stop zodra het werkt.

  1. Bekijk het echte antwoord in het tabblad Network

    Open F12, ga naar Network, herlaad of herhaal de actie en klik op het verzoek. Kijk naar de statuscode, de header Content-Type en het tabblad Response. Daar zie je meteen wat er wél terugkwam: een foutpagina, een inlogscherm, een PHP-waarschuwing of niets. Dat antwoord vertelt je wat de echte fout is.

  2. Controleer de URL

    Een verkeerde of relatieve URL is een veelvoorkomende oorzaak. fetch('api/producten') zonder / ervoor wordt relatief aan de huidige pagina opgevraagd en kan op een andere plek uitkomen. Op veel sites stuurt de server een onbekende URL door naar de homepage of een 404-pagina in HTML. Gebruik het volledige pad en open het adres eens rechtstreeks in de browser.

    // relatief, kan op een andere plek uitkomen:
    fetch('api/producten')
    // absoluut pad:
    fetch('/api/producten')
  3. Controleer status en Content-Type vóór je JSON leest

    Lees de gegevens pas als JSON als het antwoord goed is en ook echt JSON is. Zo krijg je een duidelijke foutmelding met de echte status, in plaats van een vage SyntaxError. Lees bij twijfel eerst als tekst, zodat je in de console ziet wat er binnenkwam.

    const res = await fetch('/api/producten');
    const type = res.headers.get('content-type') || '';
    if (!res.ok || !type.includes('application/json')) {
      const tekst = await res.text();
      throw new Error('Status ' + res.status + ': ' + tekst.slice(0, 200));
    }
    const data = await res.json();
  4. Vang lege antwoorden op

    Bij Unexpected end of JSON input was het antwoord leeg. Lees dan geen JSON bij status 204, en controleer bij JSON.parse eerst of er iets is om te lezen. Vang de fout af met try/catch waar een lege of kapotte waarde kan voorkomen, zoals bij localStorage.

    if (res.status === 204) return null;
    
    let instellingen = {};
    try {
      instellingen = JSON.parse(localStorage.getItem('instellingen') || '{}');
    } catch (e) {
      instellingen = {};
    }
  5. Server (PHP): stuur alleen JSON

    Zet de juiste header en zorg dat er niets anders wordt afgedrukt: geen spaties of lege regels vóór <?php, geen echo voor debuggen en geen waarschuwingen. Zet display_errors uit op een productieserver en schrijf fouten naar het log. Een witregel of waarschuwing vóór de header geeft ook headers already sent.

    <?php
    ini_set('display_errors', '0');
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(['ok' => true, 'items' => $items]);
    exit;
  6. Server: geef ook fouten terug als JSON

    Laat je API bij een fout een JSON-antwoord met een passende statuscode sturen, in plaats van een HTML-foutpagina. Dan kan je JavaScript de fout netjes tonen. Controleer in het foutenlog van de server waarom het verzoek mislukte; bij een 500 staat daar de echte oorzaak.

    <?php
    http_response_code(400);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(['ok' => false, 'fout' => 'Ongeldige invoer']);
    exit;
  7. Kijk naar sessie, inloggen en tussenlagen

    Krijg je een HTML-inlogpagina terug, dan is je sessie verlopen of worden de cookies niet meegestuurd; bij een ander domein heb je credentials: 'include' nodig. Een firewall, Cloudflare of een onderhoudsmodus kan ook een HTML-pagina teruggeven, bijvoorbeeld een uitdaging of een 429 Too Many Requests. Het tabblad Response laat zien welke het is.

Let op: Zet display_errors niet aan op een live server om de fout te zien. Behalve dat het je JSON breekt, toont het bestandspaden en soms gegevens aan iedere bezoeker. Lees de fout in het serverlog.

Wanneer schakel je hulp in?

  • De API is van jezelf, geeft een 500 terug en het serverlog zegt je niets.
  • Het werkt lokaal wel maar live niet, en je vermoedt een firewall, doorverwijzing of andere serverinstelling.
  • Na een PHP-update komen er waarschuwingen in je JSON-antwoorden en je wilt de code bijwerken zonder iets te breken; zie ook 500 na een PHP-update.
  • Het gaat om een koppeling met een externe dienst die soms HTML terugstuurt en je wilt die robuust afhandelen.

Lukt het niet? Wij helpen

Kom je er met deze stappen niet uit, of wil je het liever niet zelf doen? We kijken online mee of komen langs. Hulp op afstand kost €20 per uur; komen we langs, dan is de prijs op aanvraag. Je weet vooraf waar je aan toe bent.

Aanvraag

Hulp aanvragen

Schrijf kort wat er misgaat en wat je al hebt geprobeerd. Kies of we bellen, online meekijken of langskomen.

Veelgestelde vragen

Vragen over dit probleem

Wat betekent ‘Unexpected token < in JSON at position 0’?

Het eerste teken van het antwoord is een <, dus de server stuurde HTML in plaats van JSON. Meestal is dat een foutpagina, een inlogpagina of een PHP-waarschuwing.

Wat betekent ‘Unexpected end of JSON input’?

Het antwoord was leeg of hield halverwege op. Kijk in het tabblad Network of er wel iets terugkwam en welke statuscode erbij hoort.

Hoe zie ik wat de server echt terugstuurt?

Open F12, ga naar Network, klik op het verzoek en kijk bij Response. Of lees het antwoord in je code eerst met res.text() en log het.

Waarom zie ik ‘Unexpected token W’ of een andere letter?

Dan begint het antwoord met gewone tekst, bijvoorbeeld een PHP-waarschuwing zonder HTML-opmaak of een foutmelding als platte tekst. Het principe is hetzelfde: er staat iets anders dan JSON.