frontend / nextjs
Rendu SSG, SSR et ISR
Explication
Ce que vous allez apprendre
- Distinguer SSG, SSR et ISR et savoir quand choisir chacune de ces stratégies
- Forcer explicitement un mode avec
dynamicetrevalidateau niveau d'une route entière - Comprendre pourquoi ce choix se fait route par route, pas pour toute l'application
- Mettre en place une régénération "à la demande" déclenchée par un webhook plutôt qu'un timer
- Repérer les signaux (fetch, cookies, headers) qui font basculer Next.js vers un mode dynamique automatiquement
Dans quel contexte ?
Une même application contient une page "À propos" qui ne change jamais, un tableau de bord /dashboard affichant des métriques en temps réel propres à chaque utilisateur connecté, et un catalogue produit /produits mis à jour quelques fois par jour par l'équipe marketing. Appliquer la même stratégie de rendu aux trois serait une erreur : la première n'a besoin que du SSG, la deuxième exige du SSR, et la troisième profite pleinement de l'ISR. Cette leçon donne les critères pour choisir correctement, route par route.
Formalisons ce qu'on a déjà entrevu
Les leçons précédentes sur le cache ont déjà montré des indices : Next.js propose trois grandes stratégies de rendu. Chacune est adaptée à un type de contenu différent.
Commençons par la plus simple : le SSG (Static Site Generation). La page est générée UNE FOIS, au moment du build, puis servie telle quelle à tous les visiteurs, comme un fichier statique. C'est le plus rapide, mais adapté seulement à du contenu qui ne change pas souvent — une page "À propos", un article déjà publié.
À l'opposé, il y a le SSR (Server-Side Rendering). La page est recalculée à CHAQUE requête. Indispensable pour du contenu personnalisé (un tableau de bord en temps réel, du contenu propre à l'utilisateur connecté), mais plus coûteux en ressources serveur.
Entre les deux, un compromis intéressant : l'ISR (Incremental Static Regeneration). La page est servie depuis un cache statique (rapide comme du SSG), mais régénérée en arrière-plan après un délai (revalidate), sans jamais faire attendre l'utilisateur pour cette régénération.
| Stratégie | Rapidité perçue | Fraîcheur des données | Exemple |
|---|---|---|---|
| SSG | Maximale | Fixée au build | Page "À propos", CGU |
| SSR | Correcte | Toujours à jour | Dashboard temps réel, contenu par utilisateur |
| ISR | Maximale | Rafraîchie périodiquement | Catalogue produit, blog |
Prérequis
Cette leçon suppose que tu es à l'aise avec les options cache et revalidate de fetch, vues dans la leçon sur le cache et la revalidation : ici, on les applique au niveau d'une route entière.
Pourquoi ce choix est stratégique et pas juste technique ? Du SSR partout gaspille des ressources serveur sur du contenu qui ne change jamais. Du SSG partout peut, à l'inverse, afficher des données périmées pendant des heures.
Il existe une version encore plus fine de l'ISR : le "à la demande". Déclenchée par un webhook plutôt que par un timer, elle convient très bien à du contenu semi-dynamique comme un catalogue produit — les données restent fraîches SEULEMENT quand elles changent réellement.
Le piège fréquent à éviter : croire qu'il faut choisir UNE seule stratégie pour toute l'application. En réalité, ce choix se fait route par route, voire fetch par fetch : une page marketing peut être en SSG pendant qu'un dashboard est en SSR, dans la même application.
Bonne pratique
Commence toujours par te poser une question simple sur une nouvelle route : "cette page affiche-t-elle la même chose pour tout le monde ?". Si oui, SSG ou ISR conviennent. Si le contenu dépend de la session de l'utilisateur connecté, SSR est presque toujours nécessaire.
Un dernier point utile à savoir : Next.js déduit souvent automatiquement le bon mode selon l'usage de fetch, cookies ou headers dans le code — mais comprendre ce qui se passe reste essentiel pour ne pas être surpris par ce comportement par défaut.
Commandes & code
Rendu SSG, SSR et ISR
// SSG (Static Site Generation) — généré au build, servi comme fichier statique
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((r) => r.json());
return posts.map((p: { slug: string }) => ({ slug: p.slug }));
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await fetch(`https://api.example.com/posts/${slug}`, {
cache: "force-cache", // SSG : capturé au build
}).then((r) => r.json());
return <Article post={post} />;
}// SSR (Server-Side Rendering) — recalculé à CHAQUE requête
export const dynamic = "force-dynamic";
export default async function LiveDashboardPage() {
const metrics = await fetch("https://api.example.com/metrics", {
cache: "no-store",
}).then((r) => r.json());
return <MetricsPanel metrics={metrics} />;
}// ISR (Incremental Static Regeneration) — le meilleur des deux mondes
// Sert une page statique, la régénère en arrière-plan après "revalidate" secondes
export const revalidate = 300; // régénération au plus toutes les 5 minutes
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 300 },
}).then((r) => r.json());
return <ProductDetails product={product} />;
}// ISR à la demande (on-demand) : régénération déclenchée par événement, pas par timer
// app/api/webhooks/cms/route.ts
import { revalidatePath } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const payload = await request.json();
if (payload.event === "post.published") {
revalidatePath(`/blog/${payload.slug}`);
revalidatePath("/blog"); // invalide aussi la liste
}
return NextResponse.json({ ok: true });
}| Stratégie | Quand | Données |
|---|---|---|
| SSG | Contenu identique pour tous, rarement modifié | Générées au build |
| SSR | Contenu par requête (auth, temps réel) | Générées à chaque requête |
| ISR | Contenu semi-statique (catalogue produits, blog) | Régénérées périodiquement/à la demande |
Résumé
- Le mode par défaut d'une route est déduit automatiquement de son usage de
fetch/cookies/headers. force-static,force-dynamicetrevalidatepermettent de forcer explicitement une stratégie.- ISR à la demande (webhook +
revalidatePath) évite le compromis "stale vs coût de calcul".
Exercices pratiques
Mission : trois pages, trois stratégies, un seul choix appliqué partout
Objectif : Attribuer la bonne stratégie de rendu (SSG, SSR, ISR) à trois routes aux besoins très différents.
Contexte
Un développeur pressé a mis export const dynamic = "force-dynamic" sur TOUTES les routes de l'application "pour être sûr que rien n'est jamais périmé" : la page /a-propos (contenu figé, jamais modifié), /dashboard (métriques temps réel propres à chaque utilisateur connecté) et /produits (catalogue mis à jour deux fois par jour par l'équipe marketing). Les temps de réponse et la facture serveur ont explosé.