ISR op Schaal: 25.000+ Pagina's met Incremental Static Regeneration op Vercel
ISR op Schaal: 25.000+ Pagina's met Incremental Static Regeneration op Vercel Uitvoeren
Vorig jaar hebben we een Next.js-site met meer dan 25.000 statisch gegenereerde pagina's op Vercel uitgebracht. Productpagina's, blogartikelen, landing pages op locatie, dynamische categoriefilters -- alles wat je nodig hebt. De belofte van Incremental Static Regeneration is verleidelijk: de snelheid van statische sites met de versheid van server-gerenderde inhoud. En eerlijk gezegd? Het levert grotendeels. Maar bij 25.000+ pagina's gedraagt ISR zich anders dan op je 50-pagina's marketingsite. De randgevallen worden je belangrijkste gevallen. De kosten stijgen. De cache-invalidatieproblemen die theoretisch leken in de docs worden echt, heel erg echt.
Dit is het artikel dat ik wenste dat het bestond voordat we begonnen. Alles hier komt uit productie-ervaring -- echte metrics, echte factuurverrassing en echte architectuurbeslissingen die we hebben genomen (en soms betreurd).

Inhoudsopgave
- Wat ISR Werkelijk Onder de Motorkap Doet
- Waarom 25.000 Pagina's Alles Verandert
- Bouwstrategie: Wat Pre-renderen versus Uitstellen
- Revalidatiepatronen Die Werkelijk Werken
- Vercel-Specifieke Addertjes en Limieten
- Echte Productiekosten op Schaal
- Monitoring en Debugging van ISR in Productie
- Architectuurbeslissingen: ISR versus Alternatieven
- Performancebenchmarks van Onze Implementatie
- FAQ
Wat ISR Werkelijk Onder de Motorkap Doet
Voordat we in schaallingsproblemen duiken, zorg ervoor dat we op dezelfde pagina staan over wat ISR doet. Wanneer je revalidate: 60 instelt in een Next.js-pagina, hier is de werkelijke flow:
Eerste verzoek na deploy: Als de pagina bij het bouwen is pre-gerenderd, serveert Vercel het van de edge cache. Zo niet (je retourneerde
fallback: 'blocking'of gebruiktedynamicParams: truein App Router), dan wordt het server-side gerenderd, wordt het resultaat gecacht en vervolgens geserveerd.Volgende verzoeken binnen het revalidatievenster: Geserveerd vanuit cache. Snel. Geen compute.
Eerste verzoek nadat het revalidatievenster verloopt: De verouderde pagina wordt onmiddellijk geserveerd (dit is het "stale-while-revalidate" gedeelte), en een achtergrondrevalidatie wordt geactiveerd. De volgende bezoeker krijgt de verse pagina.
Dit is conceptueel eenvoudig. Maar bij 25.000 pagina's wordt die achtergrondrevalidatiestap een brandslang.
// App Router (Next.js 14/15)
export const revalidate = 60; // seconden
export async function generateStaticParams() {
// Bij 25k pagina's wil je waarschijnlijk niet allemaal hier retourneren
const topPages = await getTop500Pages();
return topPages.map((page) => ({ slug: page.slug }));
}
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await getProduct(params.slug);
return <ProductTemplate product={product} />;
}
De Stale-While-Revalidate Afweging
Wat mensen verwarrt: ISR serveert altijd verouderde inhoud aan het verzoek dat revalidatie activeert. Dit is een feature, geen bug -- het betekent dat geen bezoeker ooit op een render hoeft te wachten. Maar het betekent ook dat je inhoud altijd minstens één verzoek achter is. Voor een 25.000-pagina-site waar sommige pagina's eenmaal per week worden bezocht, kan die "één verzoek achter" betekenen dat iemand inhoud ziet die dagen oud is nadat het revalidatievenster verliep, omdat niemand bezocht om revalidatie te activeren.
Waarom 25.000 Pagina's Alles Verandert
Op kleine schaal is ISR in feite magie. Op grote schaal veranderen drie dingen:
Bouwtijden Worden een Bottleneck
Als je probeert alle 25.000 pagina's bij het bouwen pre-te-renderen, kijk je naar bouwtijden die je tot nadenken zullen stemmen. Elke pagina moet zijn gegevens ophalen, React naar HTML renderen en de statische activa genereren. Zelfs bij 200ms per pagina (wat optimistisch is als je een CMS API raakt), ben je naar 5.000 seconden -- meer dan 83 minuten. Vercel's Pro-plan heeft een bouwtijdslimit van 45 minuten. Enterprise krijgt meer, maar je brandt nog steeds rekentegoeden op.
Cache-invalidatie Wordt een Echt Probleem
Met 25.000 pagina's kun je niet zomaar "alles herbouwen" wanneer inhoud verandert. Je hebt chirurgische invalidatie nodig. Vercel's revalidatePath() en revalidateTag() API's helpen, maar ze hebben hun eigen eigenaardigheden op schaal die we zullen behandelen.
Achtergrondrevalidatie Laadpieken
Stel je voor dat 5.000 pagina's allemaal revalidate: 60 hebben en ze krijgen allemaal gelijktijdig verkeer. Dat zijn 5.000 serverloze functieaanroepen die elke minuut op de achtergrond plaatsvinden. Je CMS API moet dat aan kunnen.

Bouwstrategie: Wat Pre-renderen versus Uitstellen
Dit is de enige belangrijkste architectuurbeslissing voor grote ISR-sites. Hier is het raamwerk dat we gebruiken:
| Paginacategorie | Aantal (Ons Geval) | Strategie | Reden |
|---|---|---|---|
| Pagina's met veel verkeer (top 500) | 500 | Pre-renderen bij bouwen | Deze krijgen onmiddellijk na deploy veel verkeer. Geen cold-start straf. |
| Pagina's met middelmatig verkeer | 4.500 | Uitstellen met fallback: 'blocking' |
Eerste bezoeker wacht ~300ms, dan wordt het gecacht. Acceptabel. |
| Long-tail pagina's | 20.000 | Uitstellen met fallback: 'blocking' |
De meeste zullen uren/dagen na deploy niet worden bezocht. Geen zin om pre-te-renderen. |
Het sleutelinsicht: pre-rendeer geen pagina's die niemand het eerste uur na deploy gaat bezoeken. Je verspilt bouwminuten en geld.
// generateStaticParams - retourneer alleen je pagina's met veel verkeer
export async function generateStaticParams() {
// We gebruiken analytics-gegevens om de populaire pagina's te bepalen
const topPages = await fetch('https://api.example.com/pages/top?limit=500', {
headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` },
}).then(r => r.json());
return topPages.map((page: { slug: string }) => ({
slug: page.slug,
}));
}
Met deze aanpak gingen onze builds van timing-out naar ~8 minuten. Dat is een enorm verschil. We hebben vergelijkbare optimalisatiestrategieën geschreven in de context van ons Next.js-ontwikkelaarswerk -- de principes gelden breed.
De `dynamicParams`-Instelling Doet Ertoe
In App Router betekent dynamicParams = true (de standaard) dat pagina's die niet door generateStaticParams worden geretourneerd, on-demand worden gerenderd en gecacht. Met dynamicParams = false retourneer je een 404 voor pagina's die niet pre-zijn-gerenderd. Voor een 25.000-pagina-site wil je bijna zeker true.
export const dynamicParams = true; // Sta on-demand rendering toe voor pagina's niet in generateStaticParams
Revalidatiepatronen Die Werkelijk Werken
Op Tijd Gebaseerde Revalidatie
De eenvoudigste aanpak. Stel revalidate in op een aantal seconden. Maar welk aantal?
Hier is waar we na maanden afstemmen op uitkwamen:
| Inhoudstype | Revalidatieperiode | Waarom |
|---|---|---|
| Productprijzen | 60 seconden | Prijzen veranderen frequent, klanten merken verouderde prijzen op |
| Productbeschrijvingen | 3600 seconden (1 uur) | Verandert zelden, niet tijdgevoelig |
| Blogartikelen | 86400 seconden (24 uur) | Verandert bijna nooit na publicatie |
| Categorie-/aanbiedingspagina's | 300 seconden (5 minuten) | Nieuwe producten verschijnen, maar kleine vertraging is OK |
| Locatiepagina's | 86400 seconden (24 uur) | Adresgegevens veranderen nauwelijks |
De fout die we vroeg maakten: alles op 60 seconden instellen. Dit sloeg onze CMS (Contentful, in ons geval) API met revalidatieverzoeken en we bereikten snelheidslimits tijdens verkeerspieken.
On-Demand Revalidatie
Dit is de betere aanpak voor meeste inhoudupdates. In plaats van polling met op tijd gebaseerde revalidatie, activeer je revalidatie wanneer inhoud werkelijk verandert:
// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-revalidation-secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
}
const body = await request.json();
// Tag-gebaseerde revalidatie -- dit is de weg
if (body.tag) {
revalidateTag(body.tag);
return NextResponse.json({ revalidated: true, tag: body.tag });
}
// Path-gebaseerde revalidatie als terugval
if (body.path) {
revalidatePath(body.path);
return NextResponse.json({ revalidated: true, path: body.path });
}
return NextResponse.json({ error: 'No tag or path provided' }, { status: 400 });
}
Stel vervolgens een webhook in je CMS in om dit eindpunt te raken wanneer inhoud wordt gepubliceerd. We combineren dit met een langere op tijd gebaseerde revalidatie (zoals 24 uur) als veiligheidskabel.
Tag-Gebaseerde Revalidatie op Schaal
Dit is waar Next.js 14+ werkelijk schittert voor grote sites. Je kunt je fetch-verzoeken van tags voorzien en per tag invalideren:
async function getProduct(slug: string) {
const res = await fetch(`https://api.cms.com/products/${slug}`, {
next: {
tags: [`product-${slug}`, 'products', 'all-content'],
revalidate: 86400 // 24 uur veiligheidskabel
},
});
return res.json();
}
Nu, wanneer een enkel product wordt bijgewerkt, roep je revalidateTag('product-blue-widget') aan en alleen die pagina wordt opnieuw gegenereerd. Wanneer je een bulk prijsupdate doet, roep je revalidateTag('products') aan en alle productpagina's worden opnieuw gegenereerd bij hun volgende bezoek.
De addertje: als je revalidateTag('products') aanroept op een site met 25.000 productpagina's, worden ze niet allemaal onmiddellijk opnieuw gegenereerd. Het markeert ze allemaal als verouderd. Ze worden opnieuw gegenereerd bij volgende bezoek. Dit is belangrijk -- het betekent dat sommige pagina's misschien pas na dagen werkelijk bijwerken als ze weinig verkeer hebben.
Vercel-Specifieke Addertjes en Limieten
We runnen dit sinds begin 2024 op Vercel. Hier zijn de dingen die de docs niet genoeg benadrukken:
ISR Cache-opslag
Vercel slaat ISR-pagina's op in hun Edge Network cache. Vanaf 2025 heeft Vercel Data Cache enkele limieten die je moet kennen:
- Pro-plan: Inbegrepen ISR-cache is ruimschoots, maar er zijn kosten voor cache-lees-/schrijfbewerkingen bij zeer hoog volume
- Enterprise: Aangepaste limieten, maar je betaalt ervoor
- Cache-vermeldingen leven niet eeuwig: Zelfs met
revalidate: falsekunnen cache-vermeldingen die niet recent zijn benaderd, door Vercel uit de cache worden verwijderd. We hebben gezien dat pagina's na ongeveer 30 dagen zonder verkeer uit cache verdwijnen op het Pro-plan.
Serverloze Functieduur
Achtergrondrevalidatie wordt als een serverloze functie uitgevoerd. Op Vercel Pro is de standaard timeout 60 seconden (je kunt configureren tot 300 seconden). Als je pagina langer dan dat nodig heeft om opnieuw te genereren -- zeg, omdat je CMS traag is of je doet zware beeldverwerking -- mislukt de revalidatie stilzwijgend en blijft de verouderde pagina worden geserveerd.
We hebben dit geraakt met pagina's die gegevens van drie verschillende API's ophaalde. De fix was om een cachlaag toe te voegen (Redis via Upstash) tussen onze Next.js app en de traagste API.
Gelijktijdige Revalidatielimieten
Vercel publiceert geen harde getallen hierover, maar we zagen throttling wanneer meer dan ~1.000 ISR-revalidaties gelijktijdig werden geactiveerd (bijv. nadat revalidateTag op een veel gebruikte tag werd aangeroepen). De revalidaties staan in de wachtrij en worden in plaats daarvan over enkele minuten verwerkt. Plan hiervoor.
Cold Starts
Pagina's die lange tijd niet zijn bezocht (en uit edge cache zijn verwijderd), ondervinden een cold start bij volgende bezoek. In onze benchmarks:
- Warm cache hit: 15-40ms TTFB
- Verouderde revalidatie (geserveerd vanuit cache): 15-40ms TTFB (hetzelfde, omdat verouderd wordt geserveerd)
- Cold revalidatie (geen cache, blocking): 400-1200ms TTFB afhankelijk van API-reactietijden
Echte Productiekosten op Schaal
Laten we over geld praten. Dit is waar mensen verrast worden.
Onze 25.000-pagina-site op Vercel Pro ($20/maand basis) met ISR:
| Kostencomponent | Maandelijks | Opmerkingen |
|---|---|---|
| Vercel Pro-abonnement | $20 | Basisplan |
| Serverloze Functieuitvoering | $180-$340 | Varieert per verkeer. ISR-revalidaties tellen als functieaanroepen. |
| Edge-bandbreedte | $90-$150 | 25k pagina's met afbeeldingen helpt |
| Vercel Data Cache | $40-$80 | Cache-lees-/schrijfbewerkingen voor ISR |
| Totaal Vercel | $330-$590/mnd | Afhankelijk van verkeersmaand |
| Contentful (CMS) | $489/mnd | Hun Team-plan. API-aanroepen van ISR-revalidatie duwde ons snel voorbij de gratis laag. |
| Upstash Redis (caching) | $30/mnd | Toegevoegd om CMS API-aanroepen te verminderen |
| Totaal | $849-$1.109/mnd | Voor een site met ~2M paginaweergaven/maand |
Is dit duur? Vergeleken met een traditionele serveropstelling is het concurrerend. Vergeleken met een statische site op een CDN is het prijzig. De ISR-revalidatiefunctieaanroepen zijn de grootste variabele kosten -- elke keer dat een pagina opnieuw wordt gegenereerd, wordt die serverloze functie voor 1-5 seconden uitgevoerd.
We hebben met clients gewerkt die op Astro gebaseerde benaderingen hebben onderzocht voor inhoudszware sites waar de kosten van ISR de voordelen beginnen te overtreffen. Voor sites waar inhoud zelden verandert, kan een volledige statische opbouw met Astro aanzienlijk goedkoper in het hosten zijn.
Monitoring en Debugging van ISR in Productie
ISR-fouten zijn standaard stil. De verouderde pagina blijft worden geserveerd, en je weet misschien niet dat je revalidatie al dagen mislukt. Hier is onze monitoringopstelling:
Aangepast Revalidatieloggen
// lib/with-regeneration-logging.ts
export async function fetchWithLogging(
url: string,
options: RequestInit & { next?: { tags?: string[]; revalidate?: number } }
) {
const start = Date.now();
try {
const res = await fetch(url, options);
const duration = Date.now() - start;
// Log naar je monitoringservice
if (duration > 5000) {
console.warn(`[ISR] Slow fetch: ${url} took ${duration}ms`);
// Stuur naar Datadog/Sentry/enz.
}
return res;
} catch (error) {
console.error(`[ISR] Fetch failed: ${url}`, error);
// Dit is kritiek -- als fetch mislukt, revalidatie mislukt
throw error;
}
}
Vercel's Ingebouwde Hulpmiddelen
Vercel's dashboard toont ISR-cache hit-rates en revalidatieaantallen. In het tabblad Analytics, zoek naar:
- Cache-status in de functielogboeken:
HIT,MISS,STALE - ISR-revalidatieduur in de serverloze functiemetrieken
- Foutpercentages op je ISR-routes
De `x-vercel-cache`-Header
Elke respons van Vercel bevat deze header:
HIT-- Geserveerd van edge cache, versSTALE-- Geserveerd van edge cache, revalidatie geactiveerd op achtergrondMISS-- Niet in cache, op-demand gerenderd
We stellen een eenvoudige monitor in die elk uur 100 willekeurige pagina's controleert en waarschuwt als meer dan 10% MISS retourneert -- dat zou op cache-verwijderingsproblemen wijzen.
Architectuurbeslissingen: ISR versus Alternatieven
Na het runnen van ISR op deze schaal voor meer dan een jaar, hier is mijn eerlijke mening over wanneer het wel en niet te gebruiken:
Gebruik ISR Wanneer:
- Je hebt 5.000-100.000 pagina's die met verschillende frequenties veranderen
- Inhoudsversheid gemeten in minuten (niet seconden) is acceptabel
- Je bent al gecommitteerd aan Next.js
- Je team begrijpt cache-invalidatie (het is geen optionele kennis op deze schaal)
Overweeg Alternatieven Wanneer:
- Je hebt realtime-inhoud nodig (gebruik SSR of client-side ophalen in plaats daarvan)
- Je site verandert zelden (volledige statische builds zijn eenvoudiger en goedkoper)
- Je hebt 500.000+ pagina's (ISR begint moeite te hebben bij zeer hoog paginanummers -- overweeg een gedistribueerde bouwbenadering)
- Kostprijs is het primaire bezwaar (zelf-gehosted Next.js met je eigen CDN kan 60-70% goedkoper zijn)
Voor clients met complexe inhoudsarchitecturen raden we vaak een headless CMS-opzet aan die je de flexibiliteit geeft om tussen ISR, SSR en volledig statisch te schakelen afhankelijk van het inhoudstype.
De Hybride Benadering Die We Werkelijk Gebruiken
We gebruiken niet ISR voor alles op onze 25k-pagina-site. Hier is de verdeling:
- ISR: Productpagina's, categoriepagina's, locatiepagina's (22.000 pagina's)
- SSR: Zoekresultaten, gebruikersdashboard, winkelwagen
- Statisch: Over, contact, juridische pagina's (gegenereerd bij bouwen, geen revalidatie)
- Client-side: Realtime voorraadhoeveelheden, gebruikerspecifieke prijsstelling
Deze hybride benadering verlaagde onze serverloze functiekosten met ongeveer 40% vergeleken met onze initiële "ISR alles" strategie.
Performancebenchmarks van Onze Implementatie
Hier zijn echte getallen uit onze productie-implementatie, gemeten over Q1 2025:
| Metriek | ISR Cache Hit | ISR Cache Miss (Blocking) | Volledig SSR (Geen Cache) |
|---|---|---|---|
| TTFB (p50) | 22ms | 480ms | 620ms |
| TTFB (p95) | 58ms | 1.100ms | 1.450ms |
| TTFB (p99) | 120ms | 2.800ms | 3.200ms |
| LCP (p50) | 1.1s | 1.8s | 2.2s |
| CLS | 0.02 | 0.02 | 0.05 |
| Core Web Vitals Pass Rate | 96% | 78% | 64% |
Het verschil tussen een cache hit en een miss is dramatisch. Dit is waarom je pre-renderingstrategie zo belangrijk is -- je wilt dat je pagina's met veel verkeer altijd warm zijn.
Een interessante bevinding: onze Core Web Vitals-scores verbeterden met 12% toen we van revalidate: 60 naar revalidate: 3600 gingen op inhoud die zelden verandert. Minder revalidaties betekende meer consistente cache hits, wat meer consistente prestaties betekende.
FAQ
Hoeveel pagina's kan ISR op Vercel aan voordat prestaties afnemen?
We hebben 25.000 pagina's zonder significante problemen gerund, en ik heb verhalen gehoord van implementaties met 100.000+ pagina's die goed werken. De bottleneck is niet het aantal pagina's in cache -- het is de snelheid van gelijktijdige revalidaties. Als je 50.000 pagina's hebt met allemaal revalidate: 60, loop je tegen problemen aan. Spreid je revalidatieperioden uit op basis van inhoudsveranderingsfrequentie en het zal goed gaan.
Kost ISR meer dan SSR op Vercel?
Over het algemeen is ISR aanzienlijk goedkoper dan SSR voor hetzelfde verkeersvolume. Met ISR worden de meeste verzoeken vanuit edge cache geserveerd (in wezen gratis compute). Met SSR wordt elk verzoek uitgevoerd door een serverloze functie. Voor onze 2M paginaweergaven/maand site waren ISR's functieaanroepen (van revalidaties) ongeveer 15% van wat volledig SSR zou zijn geweest.
Wat gebeurt er wanneer ISR-revalidatie mislukt?
De verouderde versie blijft worden geserveerd. Dit is zowel een feature als een risico. Je gebruikers zien geen fouten, maar ze zien mogelijk verouderde inhoud. We hebben situaties gehad waarin een CMS API-storing betekende dat pagina's inhoud serveerden die 6 uur oud was voordat iemand het merkte. Stel monitoring in.
Kan ik ISR gebruiken met Next.js App Router?
Ja, en het is eigenlijk schoner in App Router. Je gebruikt export const revalidate = 60 op pagina- of layoutniveau, en next: { revalidate, tags } in je fetch-aanroepen. De generateStaticParams-functie vervangt getStaticPaths. Alles wat we in dit artikel hebben beschreven, werkt met zowel Pages Router als App Router, hoewel de App Router-syntaxis degene is die we zouden aanbevelen voor nieuwe projecten in 2025.
Hoe handle ik ISR met dynamische queryparameters?
ISR slaat alleen in cache op basis van het URL-pad, niet queryparameters. Als je verschillende cached versies nodig hebt voor ?color=red versus ?color=blue, moet je werkelijke padsegmenten gebruiken (/product/widget/red in plaats van /product/widget?color=red) of de variatie client-side afhandelen. Dit overrompelde ons met onze filterimplementatie.
Is on-demand revalidatie betrouwbaar op schaal?
Grotendeels. We hebben af en toe vertragingen van 10-30 seconden waargenomen tussen het aanroepen van revalidateTag() en dat de cache werkelijk wordt geïnvalideerd over alle edge-locaties. Voor 99% van de use cases is dit goed. Als je onmiddellijke globale invalidatie nodig hebt, moet je misschien cache-busting-queryparameters toevoegen of SSR voor die specifieke pagina's gebruiken.
Zou ik Next.js zelf moeten hosten in plaats van Vercel voor grote ISR-sites?
Het hangt af van je team. Zelf-hosten (op AWS, bijvoorbeeld) geeft je meer controle over cachinggedrag en kan op schaal 50-70% goedkoper zijn. Maar je bent verantwoordelijk voor het instellen van de CDN-cache-invalidatie, het beheren van de bouwpijplijn en het afhandelen van edge-distributie zelf. We hebben teams zien maanden besteden aan het repliceren van wat Vercel je direct geeft. Als je opties wilt verkennen, neem contact met ons op -- we hebben beide gedaan.
Wat is de beste CMS voor een 25.000+ pagina ISR-site?
We hebben Contentful, Sanity en Hygraph op deze schaal gebruikt. Contentful handelt webhook-gebaseerde revalidatie goed af, maar snelheidslimiet kan een probleem zijn (plan voor caching). Sanity's GROQ-abonnementen zijn geweldig voor real-time bewustzijn van inhoudsveranderingen. Hygraph's webhooksysteem is solide. De belangrijkste vereiste is betrouwbare webhook-levering en een API die burst-verkeer van revalidatiestormen aankan. Controleer onze headless CMS-ontwikkelingscapaciteiten voor meer specifieke aanbevelingen op basis van je inhoudsmodel.