SEO voor single page applications: waarom SPA's niet ranken, hoe SSR, statische generatie en prerendering dat oplossen en hoe je app leesbaar wordt voor AI-crawlers.

Vat dit artikel samen met AI
Kort samengevat: een single page application (SPA) laadt één HTML-schil en bouwt elke weergave met JavaScript in de browser op. Google kan JavaScript renderen, maar het renderen is vertraagd en kwetsbaar, en de crawlers achter ChatGPT, Claude en Perplexity voeren het helemaal niet uit. Om te ranken en geciteerd te worden heeft elke weergave van een SPA een eigen URL nodig, een echte link met een href, een unieke titel en beschrijving, een correcte statuscode en vooral de hoofdcontent in de HTML die de server verstuurt, via server-side rendering, statische generatie of prerendering.
SEO voor single page applications is het geheel van technieken waarmee een JavaScript-app die met React, Vue, Angular of Svelte is gebouwd net zo makkelijk te crawlen is als een klassieke website. Het probleem is goed gedocumenteerd: Google legt in zijn basis van JavaScript-SEO uit dat het JavaScript-pagina's in drie fasen verwerkt (crawlen, renderen, indexeren), en elke zwakte van een SPA zit ergens in die pijplijn. Deze gids richt zich op SPA's; lees voor JavaScript-sites in het algemeen onze gids over JavaScript-SEO.
Een traditionele website geeft voor elke URL een compleet HTML-document terug. Een SPA geeft voor elke URL vrijwel hetzelfde document terug: een lege <div id="root">, een paar metatags en een grote JavaScript-bundel. De browser downloadt de bundel, roept een API aan en tekent daarna de pagina. Wanneer de bezoeker op een link klikt, verwisselt JavaScript de content zonder opnieuw te laden.
Voor een mens met een snelle telefoon voelt het resultaat direct aan. Voor een crawler is het eerste antwoord een lege schil: geen kop, geen tekst, geen links. Alles hangt ervan af of de bot het JavaScript uitvoert, en wanneer. Dat ene feit verklaart de meeste SEO-problemen van SPA's:
/#/pricing), en het deel na de # wordt nooit naar de server gestuurd, waardoor alle weergaven samenvallen tot één URL.<title>, en als de router die niet bijwerkt, delen alle pagina's hem.href: knoppen en onClick-handlers navigeren voor gebruikers, maar geven crawlers niets om te volgen.Googlebot haalt eerst de ruwe HTML op en leest de links en instructies die daarin staan. Pagina's die status 200 teruggeven, komen daarna in een wachtrij voor de Web Rendering Service, een headless Chromium. Google stelt dat een pagina “enkele seconden in deze wachtrij kan blijven, maar het kan ook langer duren”. Na het renderen wordt de resulterende HTML opnieuw ontleed op content en links en daarna geïndexeerd.
Vier details uit de documentatie van Google zijn belangrijk voor SPA's:
<a>-elementen met een href zijn betrouwbare links. Links die door JavaScript worden toegevoegd, zijn prima zolang ze dat formaat volgen.pushState) te gebruiken in plaats van #-routes. Het schafte zijn oude AJAX-crawlschema (de #!-URL's) in oktober 2015 af.noindex in de oorspronkelijke HTML wint. Google kan het renderen overslaan als het noindex ziet, dus het later met JavaScript verwijderen werkt niet.Het praktische gevolg: Google kan een SPA met rendering aan de clientzijde indexeren, maar je voegt aan elke pagina een tweede stap, een vertraging en een faalpunt toe. Een time-out, een geblokkeerde API-aanroep of een JavaScript-fout tijdens het renderen laat de geïndexeerde versie leeg. Controleer wat Google ziet met de URL-inspectietool in Search Console, en lees onze gids over hoe indexering door Google werkt als pagina's buiten de index blijven.
Meestal niet. In een studie die in december 2024 werd gepubliceerd over het verkeer van zijn netwerk, vonden Vercel en MERJ dat “geen van de grote AI-crawlers momenteel JavaScript rendert”: GPTBot, OAI-SearchBot en ChatGPT-User (OpenAI), ClaudeBot (Anthropic), PerplexityBot, Meta-ExternalAgent en Bytespider lezen allemaal alleen de ruwe HTML. Ze downloaden wel JavaScript-bestanden (ongeveer 11,5% van de verzoeken van ChatGPT en 23,8% van die van Claude) maar voeren ze niet uit. De uitzonderingen zijn Gemini, dat steunt op de renderinfrastructuur van Google, en AppleBot, dat een crawler op basis van een browser gebruikt.
Voor een SPA met rendering aan de clientzijde betekent dit dat de AI-crawlers die ChatGPT, Claude en Perplexity voeden een lege pagina zien. Je product kan op Google ranken en toch onzichtbaar zijn in AI-antwoorden, omdat de assistent er geen woord van heeft gelezen. Hetzelfde geldt voor de meeste bots voor linkvoorbeelden op sociale netwerken en berichtenapps, die de Open Graph-tags in de ruwe HTML lezen. Controleer zodra de content in de HTML staat ook of je robots.txt de AI-crawlers toestaat die je wilt.
De oplossing is echte HTML te versturen in het eerste antwoord. Google noemt dynamic rendering (alleen aan bots een geprerenderde versie serveren) “een noodoplossing en geen aanbevolen oplossing” in zijn documentatie over dynamic rendering en verwijst in plaats daarvan naar server-side rendering, statische rendering of hydratatie. De onderstaande definities volgen het naslagwerk Rendering on the Web van web.dev.
| Strategie | Hoe het werkt | Content in de eerste HTML | Het best voor | Beperkingen |
|---|---|---|---|---|
| Client-side rendering (CSR) | De browser bouwt de pagina op uit JavaScript en een API | Nee | Dashboards en pagina's achter een login | Vertraagde indexering bij Google, onzichtbaar voor de meeste AI-crawlers |
| Server-side rendering (SSR) | De server rendert HTML bij elk verzoek, waarna JavaScript die hydrateert | Ja | Catalogi, overzichten, content die vaak verandert of gepersonaliseerd is | Serverkosten, tragere reactietijd als er niet wordt gecachet |
| Statische generatie (SSG) | HTML wordt tijdens de build voor elke URL gegenereerd en via een CDN geserveerd | Ja | Marketingpagina's, blog, documentatie, landingspagina's | Herbouwen nodig als de content verandert (incrementele opties bestaan) |
| Prerendering tijdens de build | Een headless browser legt de beginstatus van elke route vast als statische HTML | Ja | Bestaande SPA's met een beperkt aantal openbare routes | Data bevroren op het moment van de build, routes moeten worden opgesomd |
| Dynamic rendering | Bots krijgen een geprerenderde momentopname, gebruikers de SPA | Ja, alleen voor opgesomde bots | Tijdelijke oplossing voor een verouderde app | Officieel een noodoplossing, onderhoudskosten, risico dat bots andere content krijgen |
De meeste moderne frameworks laten je strategieën per route mengen: statisch voor marketingpagina's, server-side gerenderd voor de catalogus, aan de clientzijde gerenderd voor de app met inlog. Die hybride opzet geeft crawlers volledige HTML waar het zoekverkeer zit en behoudt de SPA-ervaring waar die waarde toevoegt.
| Bibliotheek | Framework of functie voor HTML bij het eerste laden |
|---|---|
| React | Next.js (servercomponenten, SSR en statische generatie), React Router in frameworkmodus, Gatsby |
| Vue | Nuxt (standaard universele rendering, statische generatie beschikbaar) |
| Angular | Angular SSR (serverrendering en prerendering tijdens de build) |
| Svelte | SvelteKit (standaard serverrendering, prerendering per route) |
| Elk van hen | Astro (standaard statische HTML, interactieve eilanden alleen geladen waar nodig) |
Sites die door AI-websitebouwers zijn gegenereerd verdienen een aparte vermelding: veel exporteren een React-app met rendering aan de clientzijde. Als je je site met een van deze bouwers hebt gemaakt, zie dan onze notities over SEO en GEO voor Lovable voordat je aanneemt dat Google en ChatGPT hem kunnen lezen.
Gebruik de History API (/pricing, nooit /#/pricing), één URL per stuk content en dezelfde URL, of de bezoeker nu rechtstreeks komt of via navigatie in de app. Elke URL moet de juiste content laden wanneer je hem in een nieuw tabblad opent, wat de test is die een crawler uitvoert.
Elk navigatie-element dat naar een indexeerbare pagina leidt, moet een <a href="/path"> zijn. Linkcomponenten van frameworks (Link, NuxtLink, routerLink) geven deze markup af; eigen klikhandlers op div- of button-elementen doen dat niet. Goede links dragen ook je sitearchitectuur: als een pagina niet is gelinkt, vinden crawlers hem zelden.
Elke route heeft een eigen titel, metabeschrijving, canonical en Open Graph-tags nodig, waar mogelijk op de server gegenereerd. Onze pagina over metatags somt op wat je moet instellen. Houd één canonical tag aan: Google raadt aan hem in de HTML in te stellen, en als JavaScript hem wijzigt, mag het resultaat de oorspronkelijke niet tegenspreken.
Ontbrekende producten en verwijderde artikelen mogen niet met 200 en een foutmelding antwoorden. Geef met SSR een echte 404 of 410 terug vanaf de server. In een pure SPA accepteert Google twee terugvalopties: met JavaScript doorverwijzen naar een URL die een 404 teruggeeft, of een noindex-robotsmetatag aan de foutweergave toevoegen.
Koppen, lopende tekst, prijzen, FAQ en gestructureerde JSON-LD-data horen in het serverantwoord te staan, niet pas na een klik of scroll te laden. Content achter tabbladen die op verzoek worden gerenderd, of achter knoppen als “meer laden”, wordt vaak gemist.
Splits de bundel per route, stel uit wat niet nodig is voor de eerste weergave en vermijd het opnieuw renderen van de hele pagina tijdens de hydratatie. Let op dat veldgegevens van Chrome (CrUX) de statistieken van navigatie binnen de app toewijzen aan de URL die de bezoeker als eerste laadde, dus test je belangrijkste sjablonen afzonderlijk.
Vergelijk drie versies van elk belangrijk sjabloon: de ruwe HTML (bekijk de bron of haal de pagina op met een opdrachtregeltool), de gerenderde HTML van Google (URL-inspectie, daarna “Gecrawlde pagina bekijken”) en de pagina met JavaScript uitgeschakeld. Als de hoofdtekst alleen in de tweede versie verschijnt, zien AI-crawlers hem niet. Onze gratis crawlbaarheidsaudit voor AI en de indexeerbaarheidscheck voeren deze controles uit op elke URL.
| Symptoom | Waarschijnlijke oorzaak | Oplossing |
|---|---|---|
| Search Console toont veel pagina's als “Gecrawld, momenteel niet geïndexeerd” | Lege of bijna identieke HTML vóór het renderen | SSR, SSG of prerendering voor openbare routes |
Elk resultaat van een zoekopdracht met site: toont dezelfde titel | Titel niet per route bijgewerkt | Beheer van de head per route op de server |
| Alleen de homepage is geïndexeerd | Hash-routering of links zonder href | History API en echte <a>-links |
| Foutpagina's staan in de index | Soft 404's (overal status 200) | 404 van de server, of JavaScript-doorverwijzing naar een 404-URL, of noindex |
| Rankt op Google maar wordt nooit geciteerd door ChatGPT of Perplexity | Content pas zichtbaar na het uitvoeren van JavaScript | Content in het eerste HTML-antwoord |
| Oude content in de gerenderde weergave van Google | Gecachte JavaScript- of CSS-bestanden | Bestandsnamen met vingerafdruk |
Jarenlang was rendering aan de clientzijde een beheersbaar risico, omdat Google JavaScript uiteindelijk rendert. Nu een deel van de ontdekking in ChatGPT, Claude, Gemini en Perplexity plaatsvindt, zijn de kosten veranderd: een pagina die pas bestaat na het uitvoeren van JavaScript, ontbreekt in de bronnen die de meeste assistenten kunnen lezen. Crawlbaarheid in de ruwe HTML is de eerste voorwaarde van generatieve zoekmachineoptimalisatie geworden.
Zodra de HTML compleet is, volgt de rest de gebruikelijke regels: pagina's die één vraag helder beantwoorden, met onderbouwde feiten en gestructureerde data, zijn de pagina's die assistenten citeren. De functies voor GEO-optimalisatie van Sorank helpen je die content te schrijven en te publiceren, en AI-vermeldingstracking laat zien of ChatGPT, Claude en Gemini je merk citeren na de oplossing voor het renderen.
De kernregel van SEO voor single page applications past in één zin: alles wat je door Google wilt laten ranken en door AI wilt laten citeren, moet in de HTML staan die de server verstuurt. Bewaar de SPA voor de ervaring na het inloggen, render openbare pagina's op de server of tijdens de build en test wat bots ontvangen vóór en na elke release.
Een single page application wordt door Google niet bestraft, maar de standaardopzet, rendering aan de clientzijde met één HTML-schil, maakt indexering trager en minder betrouwbaar. De meeste AI-crawlers voeren geen JavaScript uit, dus zij zien een lege pagina. Met server-side rendering, statische generatie of prerendering op openbare routes kan een SPA net zo goed ranken als elke klassieke website.
React zelf is neutraal: wat telt, is waar de HTML wordt geproduceerd. Een React-app die alleen in de browser wordt gerenderd, heeft de gebruikelijke SPA-problemen, terwijl dezelfde componenten op de server gerenderd met Next.js of React Router in frameworkmodus complete HTML aan crawlers leveren. Kies een framework dat server-side of statische rendering ondersteunt voor de pagina's die je geïndexeerd wilt hebben.
Alleen als tijdelijke maatregel. Google beschrijft dynamic rendering als een noodoplossing en geen aanbevolen oplossing, omdat het onderhouden van een aparte versie voor bots kosten en risico toevoegt. Server-side rendering, statische generatie of prerendering tijdens de build geven aan elke bezoeker dezelfde HTML en zijn de oplossingen voor de lange termijn.
ZIJ PROBEERDEN SORANK VOOR JOU
