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
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
useEffectbij 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.htmlDe 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')
PYEen 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.htmlZorg 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/
doneElke 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 -20Hoe 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.
nosnippetofmax-snippet:0op 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.



