Aller au contenu
Me contacter
Tous les articles
Mis à jour le 6 min de lecture

SSR, ISR ou statique : choisir le rendu avec Next.js

Le bon mode de rendu se choisit page par page, et non pour tout un site. Voici une grille de décision simple, illustrée par deux cas réels (ce portfolio, entièrement statique, et l’e-commerce Cora, rendu à chaque requête), avec la revalidation telle qu’elle fonctionne dans Next.js 16.

  • Next.js
  • SSR
  • ISR
  • Performance
  • App Router

« SSR ou ISR ? » est une question qu’on pose souvent pour un site entier. C’est la mauvaise échelle : dans une même application, la page d’accueil, une fiche produit et un panier n’ont pas les mêmes besoins. Cet article passe en revue les questions que je me pose pour chaque page, deux cas réels qui aboutissent à des choix opposés et ce que Next.js 16 a changé dans la revalidation.

Quels sont les modes de rendu de Next.js ?

  • Statique : la page est générée au build, une fois pour toutes, puis servie telle quelle. C’est le plus rapide et le moins coûteux.
  • ISR (Incremental Static Regeneration) : la page est statique, mais régénérée en arrière-plan après un délai ou à la demande. Le visiteur reçoit toujours une page prête, éventuellement un peu ancienne.
  • SSR, ou rendu dynamique : la page est construite à chaque requête, avec les données du moment et du visiteur (cookies, en-têtes, paramètres d’URL).
  • Rendu côté client : le serveur envoie une structure initiale, puis le navigateur charge les données et construit l’interface. Utile pour une partie interactive, rarement pour une page entière à indexer.

Avec l’App Router, on ne choisit pas un mode par une option globale : Next.js le déduit de ce que fait la page. Une page qui lit les cookies devient dynamique ; une page qui ne dépend de rien d’autre que de ses données de build reste statique.

Quelles questions se poser pour chaque page ?

La page est-elle la même pour tous les visiteurs ?

C’est la question qui tranche le plus vite. Si le contenu dépend du visiteur (sa session, son magasin, sa langue lue dans un cookie), la page ne peut pas être servie à l’identique à tout le monde. Elle se rend alors à chaque requête, à moins d’isoler la partie personnalisée.

À quelle fréquence change-t-elle ?

Un contenu qui ne change qu’au déploiement est statique par nature. Un contenu qui change dans la journée sans changer d’un visiteur à l’autre, comme un catalogue ou un article de CMS, est le terrain de l’ISR : on garde la vitesse du statique, et la fraîcheur se règle par un délai ou par un signal du CMS.

Combien de pages existe-t-il ?

Générer au build quelques dizaines de pages ne coûte rien. Des dizaines de milliers de fiches produit, si : on en prégénère une partie avec generateStaticParams, et les autres sont générées à la première visite puis mises en cache.

Faut-il l’indexer ?

Statique, ISR et SSR envoient tous du HTML complet aux moteurs de recherche. C’est le rendu entièrement côté client qui pose problème pour une page à référencer.

Exemple 1 : pourquoi ce portfolio est entièrement statique

Le contenu de ce site (projets, articles, compétences) est écrit en TypeScript dans le dépôt. Il ne change donc qu’à un nouveau déploiement, et rien ne justifie de reconstruire une page entre deux mises en ligne. Toutes les pages sont générées au build, y compris les pages d’articles, les images de partage et le flux RSS :

// app/blog/[slug]/page.tsx: one page per article, generated at build time
export const dynamicParams = false; // an unknown slug is a 404, not a render

export function generateStaticParams() {
  return blogPosts.map((post) => ({slug: post.slug}));
}

// app/blog/feed.xml/route.ts: the RSS feed is built once, too
export const dynamic = 'force-static';

Chaque page devient ainsi un fichier prêt à servir, et une faute de frappe dans un slug renvoie une 404 au lieu de déclencher un rendu inutile.

Exemple 2 : pourquoi l’e-commerce Cora est rendu à chaque requête

Sur l’e-commerce Cora, la réponse aux trois questions est inverse. Le client choisit son magasin parmi sept, et ce choix, stocké dans un cookie, change l’accès à l’e-shop, l’offre, les services et une partie des contenus, en français ou en néerlandais. Une page catalogue existe donc en une version par magasin et par langue.

Prégénérer toutes ces combinaisons, puis les invalider au bon moment, aurait été plus complexe que de rendre la page à la demande, qui livre d’emblée une page complète construite pour le magasin du client. La contrepartie tient aux temps de réponse, qui ont demandé un vrai travail sur les appels à l’API ; le retour d’expérience sur Cora détaille ces deux aspects.

Et l’ISR, dans quels cas ?

Entre les deux, l’ISR convient aux pages identiques pour tous mais dont le contenu bouge, comme un catalogue sans prix personnalisés, des articles gérés dans un CMS ou une page de documentation. Dans le modèle de cache par défaut de Next.js 16, deux mécanismes se combinent :

// Time-based: the page is regenerated in the background at most every hour
export const revalidate = 3600;

// On-demand: a CMS webhook marks tagged data as stale
// app/api/revalidate/route.ts
import {revalidateTag} from 'next/cache';

export async function POST() {
  revalidateTag('products', 'max'); // serve stale content while refreshing
  return Response.json({revalidated: true});
}

Le délai seul suffit quand une fraîcheur de quelques minutes est acceptable. La revalidation à la demande s’impose dès qu’un éditeur publie et s’attend à voir son changement. Le CMS appelle alors la route, et seules les données concernées sont régénérées, à la visite suivante.

Qu’est-ce qui a changé dans Next.js 16 ?

  • revalidateTag prend désormais un second argument : le profil « max » est recommandé, et la forme à un seul argument est dépréciée. Pour une Server Action qui doit montrer immédiatement le résultat d’une modification, on utilise updateTag.
  • Cache Components est un modèle de cache optionnel (cacheComponents dans la configuration) : on marque explicitement ce qui est mis en cache avec la directive « use cache », sa durée avec cacheLife et ses étiquettes avec cacheTag, et les données propres à la requête s’affichent dans un Suspense, en streaming, autour d’une structure statique.
  • Le middleware s’appelle désormais proxy.

Les deux modèles coexistent. Un projet peut rester sur le modèle par défaut, comme ce portfolio, et adopter Cache Components quand il a besoin de mêler, dans une même page, du statique, du cache et du dynamique. Dans tous les cas, la documentation embarquée dans node_modules/next/dist/docs fait foi, plus que les tutoriels écrits pour les versions précédentes.

Quels pièges éviter ?

  • Lire les cookies ou les en-têtes dans un layout partagé : toutes les pages qui en dépendent deviennent dynamiques. Si c’est voulu, très bien ; sinon, isolez la lecture dans le composant qui en a besoin.
  • Mettre en cache une donnée personnelle dans un cache partagé : un visiteur pourrait voir la session ou le panier d’un autre. Ce qui dépend du visiteur ne va jamais dans un cache commun.
  • Compter sur l’ISR avec un export statique : il n’y est pas pris en charge, car il faut un serveur pour régénérer les pages.
  • Oublier que, sur plusieurs instances, le cache par défaut est propre à chaque instance : une revalidation à la demande n’invalide que celle qui la reçoit, sauf avec un gestionnaire de cache partagé.
  • Revalider une URL réécrite par le proxy : la revalidation à la demande ne passe pas par le proxy, il faut viser le chemin exact.

Quelle grille de décision retenir ?

  • Même contenu pour tous, change au déploiement : statique (generateStaticParams, dynamicParams à false si la liste est connue).
  • Même contenu pour tous, change dans la journée : ISR, avec un délai de revalidation ou une revalidation à la demande depuis le CMS.
  • Contenu qui dépend du visiteur : rendu à chaque requête, ou, avec Cache Components, une structure statique et la partie personnalisée en streaming.
  • Partie très interactive (panier en cours d’édition, filtres) : côté client, au sein d’une page rendue côté serveur.

Aucun mode de rendu n’est bon dans l’absolu ; chaque page appelle sa propre réponse. Ce portfolio et l’e-commerce Cora font des choix opposés pour de bonnes raisons, et la plupart des applications mêlent les deux. Pour voir comment ces choix se traduisent dans un projet réel, lisez le retour d’expérience sur Cora ou la fiche du projet ; pour en parler, écrivez-moi.