ISR à l'échelle : Exécuter 25 000+ pages avec la régénération statique progressive sur Vercel
ISR à l'échelle : Exécuter 25 000+ pages avec la régénération statique incrémentielle sur Vercel
L'année dernière, nous avons lancé un site Next.js avec plus de 25 000 pages générées statiquement sur Vercel. Pages produit, articles de blog, pages d'atterrissage géographiques, filtres de catégorie dynamiques -- tout le nécessaire. La promesse de la régénération statique incrémentielle est séduisante : obtenir la rapidité des sites statiques avec la fraîcheur du contenu rendu côté serveur. Et honnêtement ? Cela fonctionne pour la plupart. Mais avec 25 000+ pages, ISR se comporte différemment que sur votre site marketing de 50 pages. Les cas limites deviennent vos cas principaux. Les coûts s'accumulent. Les problèmes d'invalidation de cache qui semblaient théoriques dans la documentation deviennent très, très réels.
Ceci est l'article que j'aurais aimé avoir avant de commencer. Tout ici provient de l'expérience en production -- des métriques réelles, des factures surprenantes, et des décisions architecturales réelles que nous avons prises (et parfois regrettées).

Table des matières
- Ce que fait réellement ISR sous le capot
- Pourquoi 25 000 pages changent tout
- Stratégie de construction : quoi pré-rendre par rapport à ce qu'il faut différer
- Modèles de revalidation qui fonctionnent réellement
- Les pièges et limites spécifiques à Vercel
- Coûts de production réels à l'échelle
- Surveillance et débogage d'ISR en production
- Décisions architecturales : ISR par rapport aux alternatives
- Repères de performance de notre déploiement
- FAQ
Ce que fait réellement ISR sous le capot
Avant d'aborder les problèmes d'échelle, assurons-nous que nous sommes d'accord sur ce qu'ISR fait. Quand vous définissez revalidate: 60 dans une page Next.js, voici le flux réel :
Première requête après le déploiement : Si la page a été pré-rendue au moment de la construction, Vercel la sert depuis le cache edge. Si ce n'est pas le cas (vous avez retourné
fallback: 'blocking'ou utilisédynamicParams: truedans App Router), elle est rendue côté serveur, le résultat est mis en cache, puis servi.Requêtes suivantes dans la fenêtre de revalidation : Servi depuis le cache. Rapide. Aucun calcul.
Première requête après l'expiration de la fenêtre de revalidation : La page obsolète est servie immédiatement (c'est la partie "stale-while-revalidate"), et une régénération en arrière-plan est déclenchée. Le visiteur suivant obtient la page fraîche.
C'est conceptuellement simple. Mais avec 25 000 pages, cette étape de régénération en arrière-plan devient un torrent.
// App Router (Next.js 14/15)
export const revalidate = 60; // secondes
export async function generateStaticParams() {
// Avec 25k pages, vous ne voudrez probablement pas tous les retourner ici
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} />;
}
Le compromis stale-while-revalidate
Ce qui trompe les gens : ISR sert toujours du contenu obsolète à la requête qui déclenche la régénération. C'est une fonctionnalité, pas un bug -- cela signifie qu'aucun visiteur n'attend jamais un rendu. Mais cela signifie aussi que votre contenu est toujours au moins une requête en retard. Pour un site de 25 000 pages où certaines pages sont visitées une fois par semaine, ce "une requête en retard" pourrait signifier que quelqu'un voit du contenu datant de plusieurs jours après l'expiration de la fenêtre de revalidation, parce que personne n'a visité pour déclencher la régénération.
Pourquoi 25 000 pages changent tout
À petite échelle, ISR est essentiellement magique. À grande échelle, trois choses changent :
Les temps de construction deviennent un goulot d'étranglement
Si vous essayez de pré-rendre toutes les 25 000 pages au moment de la construction, vous regardez des temps de construction qui vous feront remettre en question vos choix de vie. Chaque page doit récupérer ses données, rendre React en HTML et générer les actifs statiques. Même à 200ms par page (ce qui est optimiste si vous frappez une API CMS), c'est 5 000 secondes -- plus de 83 minutes. Le plan Pro de Vercel a un délai d'expiration de construction de 45 minutes. Enterprise reçoit plus, mais vous brûlez toujours des crédits de calcul.
L'invalidation du cache devient un vrai problème
Avec 25 000 pages, vous ne pouvez pas simplement « reconstruire tout » quand le contenu change. Vous avez besoin d'une invalidation chirurgicale. Les API revalidatePath() et revalidateTag() de Vercel aident, mais elles ont leurs propres bizarreries à l'échelle que nous couvrirons.
Les pics de charge de régénération en arrière-plan
Imaginez 5 000 pages ayant toutes revalidate: 60 et elles reçoivent tout du trafic simultanément. C'est 5 000 invocations de fonction serverless en arrière-plan se produisant chaque minute. Votre API CMS doit être capable de gérer cela.

Stratégie de construction : quoi pré-rendre par rapport à ce qu'il faut différer
C'est la décision architecturale la plus importante pour les sites ISR à grande échelle. Voici le cadre que nous utilisons :
| Catégorie de page | Nombre (Notre cas) | Stratégie | Justification |
|---|---|---|---|
| Pages très visitées (top 500) | 500 | Pré-rendre au moment de la construction | Elles sont frappées immédiatement après le déploiement. Pas de pénalité de démarrage à froid. |
| Pages moyennement visitées | 4 500 | Différer avec fallback: 'blocking' |
Le premier visiteur attend ~300ms, puis c'est mis en cache. Acceptable. |
| Pages longue traîne | 20 000 | Différer avec fallback: 'blocking' |
La plupart ne seront pas visitées pendant des heures/jours après le déploiement. Aucun intérêt de pré-rendre. |
L'idée clé : ne pas pré-rendre les pages que personne ne visitera dans la première heure après le déploiement. Vous gaspillez des minutes de construction et de l'argent.
// generateStaticParams - retournez uniquement vos pages très visitées
export async function generateStaticParams() {
// Nous utilisons les données analytiques pour déterminer les pages principales
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,
}));
}
Avec cette approche, nos constructions sont passées du délai d'expiration à l'achèvement en environ 8 minutes. C'est une énorme différence. Nous avons écrit sur des stratégies d'optimisation similaires dans le contexte de notre travail de développement Next.js -- les principes s'appliquent largement.
Le paramètre `dynamicParams` est important
Dans App Router, définir dynamicParams = true (la valeur par défaut) signifie que les pages non retournées par generateStaticParams seront rendues à la demande et mises en cache. Le définir à false retourne un 404 pour toute page non pré-rendue. Pour un site de 25 000 pages, vous voulez presque certainement true.
export const dynamicParams = true; // Autoriser le rendu à la demande pour les pages absentes de generateStaticParams
Modèles de revalidation qui fonctionnent réellement
Revalidation basée sur le temps
L'approche la plus simple. Définissez revalidate sur un nombre de secondes. Mais quel nombre ?
Voici ce sur quoi nous nous sommes arrêtés après des mois d'ajustement :
| Type de contenu | Période de revalidation | Pourquoi |
|---|---|---|
| Prix des produits | 60 secondes | Les prix changent fréquemment, les clients remarquent les prix obsolètes |
| Descriptions de produits | 3600 secondes (1 heure) | Change rarement, pas sensible au temps |
| Articles de blog | 86400 secondes (24 heures) | Presque jamais changé après la publication |
| Pages de catégorie/listing | 300 secondes (5 minutes) | De nouveaux produits apparaissent, mais un léger délai c'est OK |
| Pages de localisation | 86400 secondes (24 heures) | Les informations d'adresse changent à peine |
L'erreur que nous avons commise au début : tout définir à 60 secondes. Cela a martelé notre API CMS (Contentful, dans notre cas) avec des demandes de régénération et nous avons dépassé les limites de taux pendant les pics de trafic.
Revalidation à la demande
C'est la meilleure approche pour la plupart des mises à jour de contenu. Au lieu de sonder avec une revalidation basée sur le temps, vous déclenchez la régénération quand le contenu change réellement :
// 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();
// Revalidation basée sur les tags -- c'est la voie
if (body.tag) {
revalidateTag(body.tag);
return NextResponse.json({ revalidated: true, tag: body.tag });
}
// Revalidation basée sur le chemin comme solution de secours
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 });
}
Ensuite, configurez un webhook dans votre CMS pour frapper ce point de terminaison chaque fois que du contenu est publié. Nous l'associons à une revalidation basée sur le temps plus longue (comme 24 heures) comme filet de sécurité.
Revalidation basée sur les tags à l'échelle
C'est là que Next.js 14+ brille vraiment pour les grands sites. Vous pouvez étiqueter vos demandes de récupération et invalider par étiquette :
async function getProduct(slug: string) {
const res = await fetch(`https://api.cms.com/products/${slug}`, {
next: {
tags: [`product-${slug}`, 'products', 'all-content'],
revalidate: 86400 // filet de sécurité de 24 heures
},
});
return res.json();
}
Maintenant, quand un produit unique est mis à jour, vous appelez revalidateTag('product-blue-widget') et seule cette page se régénère. Quand vous faites une mise à jour de prix en masse, appelez revalidateTag('products') et toutes les pages produit se régénèrent à leur prochaine visite.
Le piège : appeler revalidateTag('products') sur un site avec 25 000 pages produit ne les régénère pas tous immédiatement. Cela les marque tous comme obsolètes. Ils se régénèrent à la prochaine visite. C'est important -- cela signifie que certaines pages ne se mettront peut-être pas à jour pendant des jours si elles ont peu de trafic.
Les pièges et limites spécifiques à Vercel
Nous exécutons cela sur Vercel depuis début 2024. Voici les choses que la documentation ne souligne pas assez :
Stockage en cache ISR
Vercel stocke les pages ISR dans le cache du réseau Edge. À partir de 2025, le cache de données Vercel a quelques limites que vous devriez connaître :
- Plan Pro : Le cache ISR inclus est généreux, mais il y a un coût pour les lectures/écritures de cache à très grand volume
- Enterprise : Limites personnalisées, mais vous payez pour cela
- Les entrées de cache ne vivent pas éternellement : Même avec
revalidate: false, Vercel peut supprimer les entrées de cache qui n'ont pas été accédées récemment. Nous avons vu des pages disparaître du cache après environ 30 jours sans trafic sur le plan Pro.
Durée de la fonction serverless
La régénération en arrière-plan s'exécute comme une fonction serverless. Sur Vercel Pro, le délai d'expiration par défaut est de 60 secondes (vous pouvez configurer jusqu'à 300 secondes). Si votre page prend plus de temps que cela pour se régénérer -- disons, parce que votre CMS est lent ou que vous effectuez un traitement d'image lourd -- la régénération échoue silencieusement et la page obsolète continue d'être servie.
Nous avons rencontré cela avec les pages qui récupéraient les données de trois API différentes. La solution était d'ajouter une couche de mise en cache (Redis via Upstash) entre notre application Next.js et l'API la plus lente.
Limites de régénération simultanée
Vercel ne publie pas de chiffres durs sur cela, mais nous avons observé une limitation quand plus de ~1 000 régénérations ISR ont été déclenchées simultanément (par exemple, après avoir appelé revalidateTag sur une étiquette largement utilisée). Les régénérations s'empilent et se traitent sur plusieurs minutes plutôt que tous à la fois. Planifiez cela.
Démarrages à froid
Les pages qui n'ont pas été visitées depuis un moment (et ont été supprimées du cache edge) connaîtront un démarrage à froid à la prochaine visite. Dans nos repères :
- Accès au cache chaud : 15-40ms TTFB
- Revalidation obsolète (servie depuis le cache) : 15-40ms TTFB (identique, puisque obsolète est servi)
- Régénération à froid (pas de cache, blocage) : 400-1200ms TTFB selon les temps de réponse API
Coûts de production réels à l'échelle
Parlons argent. C'est ici que les gens sont surpris.
Notre site de 25 000 pages sur Vercel Pro ($20/mois de base) avec ISR :
| Composant de coût | Mensuel | Notes |
|---|---|---|
| Abonnement Vercel Pro | $20 | Plan de base |
| Exécution de fonction serverless | $180-$340 | Varie selon le trafic. Les régénérations ISR comptent comme des invocations de fonction. |
| Bande passante Edge | $90-$150 | 25k pages avec des images s'accumulent |
| Cache de données Vercel | $40-$80 | Lectures/écritures de cache pour ISR |
| Total Vercel | $330-$590/mo | Dépend du trafic du mois |
| Contentful (CMS) | $489/mo | Leur plan Team. Les appels API à partir de la régénération ISR nous ont rapidement dépassé le niveau gratuit. |
| Upstash Redis (mise en cache) | $30/mo | Ajouté pour réduire les appels API CMS |
| Total général | $849-$1 109/mo | Pour un site servant ~2M pages vues/mois |
Est-ce cher ? Comparé à une configuration serveur traditionnelle, c'est compétitif. Comparé à un site statique sur un CDN, c'est cher. Les invocations de fonction serverless de régénération ISR sont le coût variable le plus important -- chaque fois qu'une page se régénère, c'est une fonction serverless en cours d'exécution pendant 1-5 secondes.
Nous avons travaillé avec des clients qui ont exploré des approches basées sur Astro pour les sites riches en contenu où les coûts d'ISR commencent à dépasser ses avantages. Pour les sites où le contenu change rarement, une construction complètement statique avec Astro peut être nettement moins chère à héberger.
Surveillance et débogage d'ISR en production
Les défaillances d'ISR sont silencieuses par défaut. La page obsolète continue d'être servie, et vous ne savez peut-être pas que votre régénération échoue depuis des jours. Voici notre configuration de surveillance :
Journalisation de régénération personnalisée
// 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;
// Consigner votre service de surveillance
if (duration > 5000) {
console.warn(`[ISR] Slow fetch: ${url} took ${duration}ms`);
// Envoyer à Datadog/Sentry/etc.
}
return res;
} catch (error) {
console.error(`[ISR] Fetch failed: ${url}`, error);
// C'est critique -- si la récupération échoue, la régénération échoue
throw error;
}
}
Les outils intégrés de Vercel
Le tableau de bord Vercel montre les taux de succès du cache ISR et les comptages de régénération. Dans l'onglet Analytics, cherchez :
- Cache status dans les journaux de fonction :
HIT,MISS,STALE - Durée de régénération ISR dans les métriques de fonction serverless
- Taux d'erreur sur vos routes ISR
L'en-tête `x-vercel-cache`
Chaque réponse de Vercel inclut cet en-tête :
HIT-- Servi depuis le cache edge, fraisSTALE-- Servi depuis le cache edge, régénération déclenchée en arrière-planMISS-- Pas en cache, rendu à la demande
Nous avons mis en place un simple moniteur qui vérifie 100 pages aléatoires toutes les heures et alerte si plus de 10% retournent MISS -- cela indiquerait des problèmes d'éviction de cache.
Décisions architecturales : ISR par rapport aux alternatives
Après avoir exécuté ISR à cette échelle pendant plus d'un an, voici mon avis honnête sur quand l'utiliser et quand ne pas l'utiliser :
Utilisez ISR quand :
- Vous avez 5 000-100 000 pages qui changent à différentes fréquences
- La fraîcheur du contenu mesurée en minutes (pas en secondes) est acceptable
- Vous êtes déjà engagé dans Next.js
- Votre équipe comprend l'invalidation du cache (ce n'est pas une connaissance facultative à cette échelle)
Considérez les alternatives quand :
- Vous avez besoin d'un contenu en temps réel (utilisez SSR ou la récupération côté client à la place)
- Votre site change rarement (les constructions complètement statiques sont plus simples et moins chères)
- Vous avez 500 000+ pages (ISR commence à peiner à très grand nombre de pages -- considérez une approche de construction distribuée)
- Le coût est la préoccupation principale (Next.js auto-hébergé avec votre propre CDN peut être 60-70% moins cher)
Pour les clients avec des architectures de contenu complexes, nous recommandons souvent une configuration headless CMS qui vous donne la flexibilité de basculer entre ISR, SSR et complètement statique selon le type de contenu.
L'approche hybride que nous utilisons réellement
Nous ne utilisons pas ISR pour tout sur notre site de 25k pages. Voici la répartition :
- ISR : Pages produit, pages de catégorie, pages de localisation (22 000 pages)
- SSR : Résultats de recherche, tableau de bord utilisateur, panier
- Statique : À propos, contact, pages juridiques (générées au moment de la construction, pas de revalidation)
- Côté client : Comptages de stock en temps réel, tarification spécifique à l'utilisateur
Cette approche hybride a réduit nos coûts de fonction serverless d'environ 40% par rapport à notre stratégie initiale "ISR tout".
Repères de performance de notre déploiement
Voici les chiffres réels de notre déploiement en production, mesurés au cours du Q1 2025 :
| Métrique | Accès au cache ISR | Absence de cache ISR (Blocage) | SSR complet (Pas de 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 |
| Taux de réussite des Core Web Vitals | 96% | 78% | 64% |
La différence entre un accès au cache et une absence est dramatique. C'est pourquoi votre stratégie de pré-rendu est si importante -- vous voulez que vos pages très visitées soient toujours chaudes.
Une constatation intéressante : nos scores Core Web Vitals se sont améliorés de 12% quand nous sommes passés de revalidate: 60 à revalidate: 3600 sur le contenu peu changeant. Moins de régénérations signifiaient des accès au cache plus cohérents, ce qui signifiait une performance plus cohérente.
FAQ
Combien de pages ISR peut-il gérer sur Vercel avant la dégradation des performances ?
Nous avons exécuté 25 000 pages sans problèmes importants, et j'ai entendu parler de déploiements avec 100 000+ pages fonctionnant correctement. Le goulot d'étranglement n'est pas le nombre de pages en cache -- c'est le taux de régénérations simultanées. Si vous avez 50 000 pages avec revalidate: 60, vous aurez des problèmes. Répartissez vos périodes de revalidation en fonction de la fréquence de changement du contenu et vous irez bien.
ISR coûte-t-il plus cher que SSR sur Vercel ?
Généralement, ISR est nettement moins cher que SSR pour le même volume de trafic. Avec ISR, la plupart des requêtes sont servies depuis le cache edge (essentiellement calcul gratuit). Avec SSR, chaque requête exécute une fonction serverless. Pour notre site de 2M pages vues/mois, les invocations de fonction d'ISR (à partir des régénérations) représentaient environ 15% de ce que le SSR complet aurait été.
Que se passe-t-il quand la régénération ISR échoue ?
La version obsolète continue d'être servie. C'est à la fois une fonctionnalité et un risque. Vos utilisateurs ne voient pas d'erreurs, mais ils pourraient voir du contenu obsolète. Nous avons eu des situations où une panne d'API CMS signifiait que les pages servaient du contenu datant de 6 heures avant que quelqu'un le remarque. Configurez la surveillance.
Puis-je utiliser ISR avec Next.js App Router ?
Oui, et c'est en fait plus propre dans App Router. Vous utilisez export const revalidate = 60 au niveau de la page ou de la mise en page, et next: { revalidate, tags } dans vos appels de récupération. La fonction generateStaticParams remplace getStaticPaths. Tout ce que nous avons décrit dans cet article fonctionne avec Pages Router et App Router, bien que la syntaxe App Router soit celle que nous recommanderions pour les nouveaux projets en 2025.
Comment gérer ISR avec des paramètres de requête dynamiques ?
ISR ne met en cache que le chemin URL, pas les paramètres de requête. Si vous avez besoin de différentes versions mises en cache pour ?color=red par rapport à ?color=blue, vous devez utiliser des segments de chemin réels (/product/widget/red au lieu de /product/widget?color=red) ou gérer la variation côté client. Cela nous a surpris avec notre implémentation de filtrage.
La revalidation à la demande est-elle fiable à l'échelle ?
Surtout. Nous avons vu des délais occasionnels de 10-30 secondes entre l'appel à revalidateTag() et le cache réellement invalide sur tous les emplacements edge. Pour 99% des cas d'usage c'est bien. Si vous avez besoin d'une invalidation globale instantanée, vous pourriez avoir besoin d'ajouter des paramètres de suppression de cache ou d'utiliser SSR pour ces pages spécifiques.
Devrais-je auto-héberger Next.js à la place d'utiliser Vercel pour les grands sites ISR ?
Cela dépend de votre équipe. L'auto-hébergement (sur AWS, par exemple) vous donne plus de contrôle sur le comportement de mise en cache et peut être 50-70% moins cher à l'échelle. Mais vous êtes responsable de la configuration de l'invalidation du cache CDN, de la gestion du pipeline de construction et de la gestion de la distribution edge vous-même. Nous avons vu des équipes passer des mois à reproduire ce que Vercel vous donne dès le départ. Si vous voulez explorer les options, contactez-nous -- nous avons fait les deux.
Quel est le meilleur CMS pour un site ISR de 25 000+ pages ?
Nous avons utilisé Contentful, Sanity et Hygraph à cette échelle. Contentful gère bien la revalidation basée sur webhook mais les limites de taux peuvent être un problème (prévoyez la mise en cache). Les souscriptions GROQ de Sanity sont excellentes pour la sensibilisation en temps réel des changements de contenu. Le système webhook d'Hygraph est solide. L'exigence clé est une livraison fiable des webhooks et une API pouvant gérer le trafic d'rafales de régénération. Consultez nos capacités de développement headless CMS pour des recommandations plus spécifiques basées sur votre modèle de contenu.