Naar de inhoud
POLARGATE
react

Waarom AI-assistenten je React-SPA niet kunnen lezen

AI-crawlers lezen ruwe HTML en voeren geen JavaScript uit, dus je React-SPA geeft ze niets. Meet het zelf met curl en los het op met SSG, SSR of prerender.

Gepubliceerd 2026-08-19 · Door Pedro Ciordia

Iemand haalt een papieren kaart uit een archieflade om die op een verlichte scanner te leggen, terwijl een laptop ernaast een lange lijst records toont

Een AI-crawler vraagt je HTML op en leest wat er terugkomt

Je opent een React single page application in de browser en ziet een complete pagina. Die pagina zit niet in het bestand dat de server heeft verstuurd. De server stuurde een lege huls: een <div id="root">, een scripttag en een paar metatags. De browser haalt de bundle op, voert die uit, roept je API aan en tekent alles wat je ziet.

Wat een bezoeker leest, en wat een AI-crawler ontvangt

Wat een bezoeker leest, en wat een AI-crawler ontvangtEen JavaScript-site levert een volledige pagina aan een browser die de code uitvoert, en een vrijwel lege huls aan AI-crawlers die dat niet doen. Een vooraf gerenderde site levert beiden dezelfde volledige HTML.Een JavaScript-siteBezoeker in een browservoert de code uit, leest alles11.421Dezelfde siteGPTBot, ClaudeBot,Perplexityvoert de code niet uit14Vooraf gerenderdBezoeker in een browserdezelfde volledige HTML11.421Vooraf gerenderdDezelfde crawlersdezelfde volledige HTML11.421
Tekens bodytekst van een echte nieuwspagina, voor en na. Een assistent beveelt aan wat hij kon lezen, dus het tweede getal bepaalt of je in het antwoord staat.

GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User en PerplexityBot doen dat allemaal niet. Ze vragen de URL op, lezen de bytes die terugkomen en gaan verder. Geen JavaScript-engine, geen wachten op hydratie, geen API-aanroepen namens jou. Wat niet in de ruwe HTML staat, bestaat voor hen niet.

Googlebot is de uitzondering die iedereen op het verkeerde been zet: die rendert wel JavaScript, met vertraging en binnen een budget. Daardoor kan een client-side site redelijk scoren in Google en tegelijk een lege pagina zijn in elke assistent waar je koper echt iets aan vraagt. Bij een tweetalige hotelsite die we in juni 2026 overnamen, had Google slechts 4 van ongeveer 30 pagina's geïndexeerd voordat wij hem prerenderden.

Wat er in de ruwe HTML ontbreekt, is meestal meer dan mensen denken:

  • Tekst die pas na hydratie verschijnt, inclusief je H1 en je eerste alinea.
  • Teksten die je tijdens runtime met een useEffect bij een API of database ophaalt.
  • Panelen van tabs en accordeons die alleen worden gemount als ze actief zijn.
  • JSON-LD die een client-side helper injecteert in plaats van tijdens de build wordt weggeschreven.
  • Alles achter een spinner, een cookiemelding of een scrolltrigger.

Test je eigen site in twee minuten

Geloof hierin niemand op zijn woord, ons ook niet. Richt curl op je eigen pagina met een crawler-user-agent en tel wat er terugkomt.

Haal de pagina eerst op zoals GPTBot hem ziet:

curl -s -A "GPTBot/1.2 (+https://openai.com/gptbot)" https://example.com/ -o crawler.html
wc -c crawler.html

De omvang in bytes liegt, dus meet tekst en geen bytes. Bij een site die wij auditten was de huls 3.559 bytes groot en bevatte die geen enkel leesbaar teken. Dit telt de tekens die een model daadwerkelijk kan lezen:

python3 - <<'PY'
import re
h = open('crawler.html').read()
h = re.sub(r'(?is)<script.*?</script>', ' ', h)
h = re.sub(r'(?is)<style.*?</style>', ' ', h)
t = re.sub(r'(?s)<[^>]+>', ' ', h)
print(len(' '.join(t.split())), 'characters of readable text')
PY

Een echte contentpagina komt uit op duizenden tekens. Een lege huls blijft steken op een paar honderd, grotendeels je meta description. Controleer daarna of de pagina zelf vertelt wat ze is:

grep -Eio '<title>[^<]*|<h1[^>]*>[^<]*' crawler.html
grep -c 'application/ld+json' crawler.html

Zorg vervolgens dat er aan de rand niets stilletjes bots weigert. Firewallregels en botbescherming geven een 403 terug of een JavaScript-challenge die geen enkele AI-fetcher kan oplossen, en je analytics laat dat nooit zien:

for ua in "Mozilla/5.0" "GPTBot/1.2" "OAI-SearchBot/1.0" "ClaudeBot/1.0" "PerplexityBot/1.0"; do
  printf '%-22s ' "$ua"
  curl -s -o /dev/null -w '%{http_code}\n' -A "$ua" https://example.com/
done

Elke regel hoort 200 te zijn. Loop tot slot de hele site af in plaats van één gelukkige URL, en zet de slechtste pagina's bovenaan:

curl -s https://example.com/sitemap.xml | grep -oE '<loc>[^<]+' | cut -c6- |
while read -r url; do
  n=$(curl -s -A "OAI-SearchBot/1.0" "$url" | sed -E 's/<[^>]+>/ /g' | tr -s ' ' | wc -c)
  echo "$n  $url"
done | sort -n | head -20

Hoe een echte nul eruitziet

In de zomer van 2026 hebben wij de webshop van een Spaans merk voor tafelen en wonen opnieuw gebouwd. Toen we de oude site maten zoals jij hierboven doet, was de uitkomst niet "dunne content": over de 65 publieke URL's serveerden de pagina's 0 tekens leesbare tekst aan AI-crawlers. Productnamen, prijzen, serieomschrijvingen, verzendvoorwaarden, alles kwam pas na het uitvoeren van JavaScript.

Na de herbouw gaf dezelfde meting op 4 augustus 2026 105.212 tekens over diezelfde 65 URL's. Er is niets voor robots geschreven: het is dezelfde tekst die een mens leest, verplaatst van runtime naar het HTML-bestand. Dat is de hele truc, en het is de enige meting in dit artikel die wij zelf hebben uitgevoerd.

De drie manieren om het op te lossen

Statische generatie, de juiste standaard

Tijdens de build render je elke route naar een echt HTML-bestand. Met Vite en React doet vite-react-ssg dat zonder je stack te verlaten: je exporteert je routes, geeft getStaticPaths mee voor dynamische segmenten, en de build schrijft dist/prijzen/index.html met de tekst er al in. De site blijft na hydratie gewoon een SPA, dus navigeren voelt nog steeds direct.

Gebruik dit wanneer de routelijst tijdens de build bekend is: marketingsites, dienstenpagina's, blogs, documentatie, catalogi. Content die dagelijks verandert is geen bezwaar, een geplande deploy hook bouwt hem opnieuw. De catalogus van die webshop draait zo, met een dagelijkse ververs.

Server-side rendering, als de pagina van het verzoek afhangt

Next.js, Remix of een Vite-SSR-opzet genereren de HTML per verzoek. Dat heb je nodig als de pagina echt niet vooraf bekend kan zijn: live voorraad over duizenden artikelen, zoekresultaten, alles achter een login. De prijs is een draaiende server, cacheontwerp en meer manieren om 's nachts stuk te gaan. Kies geen SSR omdat het moderner klinkt dan statische generatie. Kies het als de inhoud afhangt van wie het vraagt.

Prerenderen voor crawlers aan de rand, de tussenoplossing

Als een grote SPA nu niet kan worden omgebouwd, kan edge middleware crawlers een echte HTML-body serveren terwijl mensen de app ongewijzigd houden. Wij doen dit op een corporate site met ongeveer 700 nieuwsberichten: de middleware herkent de bot, geeft de H1, de artikeltekst en de JSON-LD terug, en de rest krijgt de SPA.

Twee voorwaarden maken dit veilig. De tekst die je aan bots serveert moet dezelfde zijn als die een mens ziet, geverifieerd met een automatische pariteitstest in CI, anders is het cloaking. En behandel het als een brug, niet als eindstation, want je onderhoudt vanaf dat moment twee renderpaden.

Welke kies je

  • Route bekend tijdens de build en content die hooguit dagelijks verandert: statische generatie.
  • Content die van het verzoek of de gebruiker afhangt: server-side rendering.
  • Grote bestaande SPA die je dit kwartaal niet kunt herbouwen: prerenderen aan de rand, met pariteitstest.

Wat het niet oplost

  • Een llms.txt-bestand. Consumentenassistenten vragen het niet op en Google negeert het. Het is nuttig voor code-agents, niet voor zichtbaarheid.
  • JSON-LD op zichzelf. In een Ahrefs-onderzoek uit 2026 met 1.885 pagina's tegenover 4.000 controlepagina's leverde extra schema geen noemenswaardige winst op in AI Overviews, AI Mode of ChatGPT. Zichtbare tekst is de hoofdlaag, gestructureerde data spiegelt die.
  • Schrijven "voor AI": pagina's opknippen in fragmenten, aparte markdownkopieën voor modellen, verzonnen schematypes. De richtlijnen van Google uit 2026 zeggen dat dit niet helpt, en pagina's alleen voor AI zijn een cloakingrisico.
  • nosnippet of max-snippet:0 op pagina's die je juist geciteerd wilt zien. Dat haalt precies de kwalificatie weg die je probeert te verdienen.

Verifieer, en blijf verifiëren

Herhaal de meting na elke deploy en zet er een versie van in CI, zodat een refactor de tekst niet ongemerkt weer uit de HTML haalt. Kijk in je serverlogs en niet in je analytics: crawlers voeren geen JavaScript uit, dus GA4 en Plausible zien ze nooit, en de logs van Vercel of Cloudflare gefilterd op user agent zijn het enige eerlijke bewijs dat de bots zijn teruggekomen.

Wil je liever dat iemand het samen met jou meet en oplost, dan doet Polargate dat binnen een betaalde Discovery Sprint met een vaste prijs. Voer in elk geval eerst die curl-commando's uit. Het getal dat eruit komt is geen mening.

FAQ

Vragen, beantwoord

Hoe controleer ik of ChatGPT mijn website kan zien?
Haal de pagina op met een crawler-user-agent en tel tekst, geen bytes. Draai curl met -A "GPTBot/1.2" op je URL, bewaar de respons, haal de tags eruit en tel de overgebleven tekens. Een echte contentpagina geeft duizenden leesbare tekens. Een lege React-huls geeft een paar honderd, grotendeels je meta description. Herhaal dit voor elke URL in je sitemap, want één pagina bewijst niets.
Indexeert Google React single page applications?
Ja, maar traag en vaak onvolledig. Googlebot rendert JavaScript wel, met vertraging en binnen een crawlbudget, dus client-side pagina's worden laat of helemaal niet geïndexeerd. Bij een tweetalige hotelsite die Polargate in juni 2026 overnam, had Google slechts 4 van ongeveer 30 pagina's geïndexeerd voordat wij prerenderden. AI-assistenten zijn nog strenger: die renderen nooit JavaScript.
SSG of SSR: wat heb ik nodig voor AI-zichtbaarheid?
Statische generatie voor bijna alles. Als de routelijst tijdens de build bekend is en de content hooguit dagelijks verandert, schrijft SSG een echt HTML-bestand per pagina en kost het niets om te draaien. Kies SSR alleen als de pagina van het verzoek of de gebruiker afhangt: live voorraad over duizenden artikelen, zoekresultaten, weergaven achter een login. Beide maken je tekst zichtbaar, SSR is alleen duurder in beheer.
Maakt een llms.txt-bestand mijn site zichtbaar voor AI?
Nee. Consumentenassistenten vragen llms.txt niet op, Google negeert het en geen enkel onderzoek laat meer citaties zien. Het is nuttig voor code- en agenttooling, niet voor AI-zoekopdrachten. Wat bepaalt of een assistent je kan citeren, is of je zichtbare tekst in de ruwe HTML-respons staat. Los eerst het renderen op en besteed daarna tijd aan extra's zoals gestructureerde data.
Is andere HTML serveren aan AI-crawlers cloaking?
Alleen als de inhoud verschilt. Een geprerenderde body aan bots serveren terwijl mensen de app krijgen mag, zolang beide dezelfde tekst zien. Polargate levert dit met een automatische pariteitstest in CI die de crawlerrespons vergelijkt met de gerenderde pagina en de build laat falen zodra ze uiteenlopen. Bots content geven die mensen niet zien is wel cloaking en kan een handmatige maatregel opleveren.

Meer lezen

INITIATE

Start de motor

Vertel ons in een paar korte vragen wat je bouwt. Een senior engineer antwoordt binnen twee werkdagen schriftelijk met een eerste inschatting van scope, planning en prijs.