Next.js 16 : SSR, SSG et ISR expliqués simplement
Pourquoi plusieurs stratégies de rendu ?
Quand un visiteur arrive sur ton site, il faut bien qu'à un moment, le HTML soit généré. La question que Next.js pose est simple : quand ce HTML doit-il être fabriqué ?
- Une fois pour toutes, à l'avance ?
- À chaque visite, en temps réel ?
- Un mélange des deux, avec une mise à jour périodique ?
C'est exactement ce que couvrent SSG, SSR et ISR. Ce ne sont pas des concepts concurrents — ce sont trois outils dans la même boîte, et le bon choix dépend entièrement de la nature de la page.
Analogie simple : imagine un restaurant. SSG, c'est le buffet préparé le matin — rapide à servir, mais figé. SSR, c'est la cuisine à la commande — toujours frais, mais plus lent. ISR, c'est le buffet réapprovisionné toutes les 30 minutes — rapide ET raisonnablement frais.
SSG — Static Site Generation
Avec le SSG, Next.js génère le HTML de la page une seule fois, au moment du build (npm run build). Ce HTML est ensuite servi tel quel à tous les visiteurs, depuis un CDN si besoin — aucun calcul serveur à chaque visite.
Résultat : c'est la stratégie la plus rapide possible. Le fichier HTML existe déjà, prêt à être envoyé.
Exemple de code
// app/projets/page.tsx
// Pas de fetch dynamique, pas de cookies, pas de searchParams
// → Next.js génère cette page en statique par défaut
import { projects } from "@/app/constant/Data";
export default function Projets() {
return (
<ul>
{projects.map((p) => (
<li key={p.slug}>{p.title}</li>
))}
</ul>
);
}
Pour une route dynamique comme /projets/[slug], il faut indiquer à Next.js quelles pages générer à l'avance avec generateStaticParams :
// app/projets/[slug]/page.tsx
export async function generateStaticParams() {
return projects.map((project) => ({ slug: project.slug }));
}
export default async function ProjectDetail({ params }) {
const { slug } = await params;
const project = projects.find((p) => p.slug === slug);
// Cette page est pré-générée pour CHAQUE slug listé ci-dessus
}
Quand utiliser le SSG
Contenu identique pour tous les visiteurs et qui change rarement : pages "À propos", articles de blog, portfolio de projets, documentation, pages marketing.
SSR — Server-Side Rendering
Avec le SSR, Next.js génère le HTML à chaque requête, sur le serveur, au moment précis où l'utilisateur visite la page. Rien n'est pré-calculé à l'avance.
Résultat : le contenu est toujours à jour à la seconde près, mais chaque visite déclenche un vrai travail serveur — donc un temps de réponse plus long qu'en SSG.
Exemple de code
// app/dashboard/page.tsx
import { cookies } from "next/headers";
export default async function Dashboard() {
const cookieStore = await cookies();
const userId = cookieStore.get("userId")?.value;
// La présence de cookies() force Next.js à passer en SSR :
// impossible de pré-générer une page qui dépend d'un utilisateur précis
const res = await fetch(`https://api.exemple.com/user/${userId}/stats`, {
cache: "no-store", // force une requête fraîche à chaque fois
});
const stats = await res.json();
return <div>Bienvenue, voici tes stats : {stats.total}</div>;
}
L'option cache: "no-store" est la clé : elle dit explicitly à Next.js de ne jamais réutiliser une réponse mise en cache et de tout recalculer à chaque requête.
Quand utiliser le SSR
Contenu personnalisé par utilisateur ou qui doit être garanti frais à 100% : tableau de bord connecté, panier d'achat, résultats de recherche, cours de bourse en direct.
ISR — Incremental Static Regeneration
L'ISR est le compromis entre les deux. La page est générée statiquement comme en SSG, mais Next.js la régénère automatiquement en arrière-plan après un délai que tu définis — sans jamais faire attendre un visiteur.
Comment ça marche concrètement : le premier visiteur après l'expiration du délai reçoit quand même l'ancienne version (rendu instantané), mais Next.js déclenche en parallèle un recalcul silencieux. Les visiteurs suivants recevront la version fraîche.
Exemple de code
// app/articles/page.tsx
export default async function Articles() {
const res = await fetch("https://api.exemple.com/articles", {
next: { revalidate: 3600 }, // régénère au plus une fois par heure
});
const articles = await res.json();
return (
<div>
{articles.map((a) => (
<ArticleCard key={a.slug} {...a} />
))}
</div>
);
}
Tu peux aussi définir le délai de revalidation pour toute une route avec une constante exportée :
// app/articles/page.tsx
export const revalidate = 3600; // secondes
export default async function Articles() {
// ...
}
L'ISR ne s'applique qu'aux données récupérées via fetch côté serveur (ou aux fonctions fs comme lib/markdown.ts combinées à revalidate). Une donnée lue depuis cookies() ou searchParams force toujours du SSR, quel que soit le revalidate défini.
Quand utiliser l'ISR
Contenu qui change, mais pas à chaque seconde : liste d'articles de blog, catalogue produit e-commerce, page d'accueil avec compteur de vues, résultats sportifs mis à jour toutes les 5 minutes.
Comment Next.js choisit automatiquement
Avec l'App Router, tu n'as presque jamais besoin de déclarer explicitement "cette page est en SSR" ou "cette page est en SSG". Next.js décide automatiquement selon ce que ton code utilise :
// → SSG automatique : aucune donnée dynamique utilisée
export default function About() {
return <p>Page statique, pas de fetch, pas de cookies.</p>;
}
// → SSR automatique : utilisation de cookies()
import { cookies } from "next/headers";
export default async function Profile() {
const session = (await cookies()).get("session");
return <p>Connecté : {session?.value}</p>;
}
// → ISR automatique : fetch avec revalidate
export default async function Blog() {
const res = await fetch("https://api.exemple.com/posts", {
next: { revalidate: 60 },
});
// ...
}
La règle mentale à retenir : dès que Next.js détecte une source de données qui varie par requête (cookies(), headers(), searchParams, ou un fetch avec cache: "no-store"), il bascule automatiquement en SSR pour cette page. Sinon, il opte pour le SSG — et si un revalidate est présent sur un fetch, ce SSG devient de l'ISR.
Tableau comparatif
| SSG | SSR | ISR | |
|---|---|---|---|
| Quand le HTML est généré | Une fois, au build | À chaque requête | Au build, puis périodiquement |
| Vitesse perçue | ⚡️ Très rapide | 🐢 Dépend du serveur | ⚡️ Très rapide |
| Fraîcheur des données | Figée jusqu'au prochain build | Toujours à jour | À jour avec un délai maîtrisé |
| Charge serveur | Aucune (fichier statique) | Une requête = un calcul | Minime (recalcul occasionnel) |
| Cas d'usage type | Page "À propos", CGU | Dashboard utilisateur | Blog, catalogue produits |
| Déclencheur dans le code | Aucun fetch dynamique |
cookies(), no-store |
fetch + revalidate |
Comment tester toi-même
La meilleure façon de comprendre la différence, c'est de la voir en pratique. Voici un mini test à faire sur ton propre projet.
Étape 1 — Crée une route qui affiche l'heure de génération :
// app/test-rendering/page.tsx
export const revalidate = 10; // ISR toutes les 10 secondes
export default function TestRendering() {
const generatedAt = new Date().toLocaleTimeString("fr-FR");
return (
<div>
<h1>Page générée à : {generatedAt}</h1>
<p>
Rafraîchis la page plusieurs fois d'affilée pour observer le
comportement.
</p>
</div>
);
}
Étape 2 — Build et lance en production (le comportement de cache ne s'observe pas correctement en next dev) :
npm run build
npm run start
Étape 3 — Ouvre http://localhost:3000/test-rendering et rafraîchis plusieurs fois dans la même seconde : l'heure affichée ne change pas. Attends 10 secondes, rafraîchis à nouveau : l'heure change enfin — c'est l'ISR en action, la régénération n'a lieu qu'après le délai défini.
Pour observer du SSR pur, remplace export const revalidate = 10 par export const dynamic = "force-dynamic" : l'heure changera alors à chaque rafraîchissement, sans exception.
En résumé
| Concept | À retenir |
|---|---|
| SSG | HTML figé au build, ultra-rapide, pour du contenu qui ne bouge pas |
| SSR | HTML recalculé à chaque requête, pour du contenu personnalisé ou temps réel |
| ISR | Le meilleur des deux mondes : statique, mais qui se rafraîchit tout seul |
| Choix automatique | Next.js détecte cookies(), no-store ou revalidate et adapte la stratégie sans configuration explicite |
| Test local | npm run build && npm run start, jamais next dev, pour observer le vrai comportement de cache |
Il n'y a pas de stratégie "meilleure" dans l'absolu — le bon réflexe est de se poser la question par page, voire par section de page : est-ce que ce contenu est identique pour tout le monde ? Change-t-il souvent ? Peut-il tolérer quelques secondes ou minutes de retard ? Les réponses à ces trois questions suffisent à choisir entre SSG, SSR et ISR.
