Data Fetching dans Next.js : SSR, SSG et ISR
Bienvenue dans cette leçon fondamentale de notre cours "Maîtriser Next.js : Construire des Applications Web Full-Stack Performantes et Scalables". Aujourd'hui, nous plongeons au cœur de l'optimisation des performances et du SEO avec Next.js : les stratégies de Data Fetching.
Le data fetching (récupération de données) est un pilier essentiel de toute application web dynamique. Next.js, en tant que framework React full-stack, offre un ensemble puissant et flexible de méthodes pour récupérer les données, bien au-delà de la simple récupération côté client que l'on trouve traditionnellement dans les SPAs (Single Page Applications). Ces méthodes permettent de choisir le bon compromis entre la fraîcheur des données, la performance et le SEO, en fonction des besoins spécifiques de chaque page de votre application.
Nous allons explorer trois paradigmes clés offerts par Next.js :
- SSR (Server-Side Rendering) : Le rendu côté serveur à la demande.
- SSG (Static Site Generation) : La génération de sites statiques au moment de la compilation.
- ISR (Incremental Static Regeneration) : Une évolution du SSG permettant une régénération incrémentale.
Comprendre et maîtriser ces approches est crucial pour construire des applications Next.js rapides, robustes et facilement indexables par les moteurs de recherche.
1. Le Contexte du Data Fetching dans Next.js
Traditionnellement, les applications React (SPA) récupèrent leurs données côté client (Client-Side Rendering - CSR) après que le navigateur a chargé le HTML, le CSS et le JavaScript. Cela signifie que le contenu initial de la page est souvent vide ou affiche un chargeur, puis se remplit une fois les données récupérées et le composant rendu.
Limitations du CSR pour certaines applications :
- SEO (Search Engine Optimization) : Les robots d'exploration des moteurs de recherche peuvent avoir du mal à indexer un contenu qui n'apparaît qu'après l'exécution du JavaScript.
- Performance perçue : Un écran vide ou un chargeur au premier chargement peut nuire à l'expérience utilisateur (Core Web Vitals).
- Performance réelle : Le navigateur doit télécharger, parser et exécuter le JavaScript avant de pouvoir afficher le contenu réel.
Next.js résout ces problèmes en offrant la possibilité de pré-rendre les pages, c'est-à-dire de générer le HTML avant qu'il ne soit envoyé au navigateur. Ce pré-rendu peut se faire de deux manières principales : SSR ou SSG.
1.1. Rappel : Client-Side Rendering (CSR) dans Next.js
Bien que Next.js excelle dans le pré-rendu, il est toujours possible et parfois nécessaire de récupérer des données côté client, surtout pour des données qui évoluent après le premier chargement ou pour des interactions spécifiques à l'utilisateur.
Pour le CSR, on utilise les hooks React standards comme useEffect, souvent combinés avec des bibliothèques de gestion d'état de données comme SWR (développée par Vercel, les créateurs de Next.js) ou React Query.
// pages/dashboard.js
import { useEffect, useState } from 'react';
import useSWR from 'swr'; // Exemple avec SWR
const fetcher = (...args) => fetch(...args).then(res => res.json());
function Dashboard() {
const { data, error } = useSWR('/api/user-data', fetcher);
if (error) return <div>Erreur de chargement...</div>;
if (!data) return <div>Chargement...</div>;
return (
<div>
<h1>Tableau de bord de {data.name}</h1>
<p>Email: {data.email}</p>
{/* Afficher d'autres données utilisateur */}
</div>
);
}
export default Dashboard;
Explication :
- Ce composant
DashboardutiliseuseSWRpour récupérer des données depuis une API (/api/user-data). - Les données ne sont chargées qu'après que le composant a été monté dans le navigateur.
- Idéal pour des données spécifiques à l'utilisateur (authentification requise) ou des parties de l'UI qui n'ont pas besoin d'être pré-rendues pour le SEO.
2. Server-Side Rendering (SSR) avec getServerSideProps
Le SSR est une méthode de pré-rendu où chaque requête pour une page entraîne l'exécution du code JavaScript sur le serveur pour générer le HTML de cette page.
2.1. Comment ça fonctionne ?
- Requête du navigateur : Un utilisateur demande une page (ex:
http://monapp.com/profile/123). - Exécution sur le serveur : Next.js exécute la fonction
getServerSideProps(que nous définissons) sur le serveur. - Récupération des données : Cette fonction récupère les données nécessaires.
- Rendu HTML : Les données sont passées au composant React de la page, qui est ensuite rendu en HTML sur le serveur.
- Envoi au client : Le HTML pré-rendu, avec les données, est envoyé au navigateur.
- Hydratation : Côté client, Next.js "hydrate" le HTML, le transformant en une application React interactive.
2.2. Quand l'utiliser ?
- Données dynamiques et en temps réel : Chaque requête doit afficher les données les plus à jour (ex: cours de bourse, flux d'actualités en direct, météo actuelle).
- Contenu spécifique à l'utilisateur : Données nécessitant une authentification ou qui varient pour chaque utilisateur (ex: panier d'achat, tableau de bord personnalisé).
- SEO important : Le contenu est entièrement rendu et disponible pour les robots d'exploration dès le premier chargement.
2.3. Avantages et Inconvénients
Avantages :
- Toujours à jour : Les données sont toujours les plus récentes au moment de la requête.
- Excellent pour le SEO : Le contenu est présent dans le HTML initial.
- Bonne performance perçue : L'utilisateur voit directement le contenu complet de la page.
Inconvénients :
- Temps de chargement initial potentiellement plus lent (TTFB) : Le serveur doit générer la page à chaque requête.
- Charge serveur plus élevée : Chaque requête implique un travail du serveur.
- Mise en cache plus complexe : Moins facile à mettre en cache globalement via un CDN que le SSG.
2.4. Exemple de code avec getServerSideProps
Pour utiliser le SSR, exportez une fonction asynchrone appelée getServerSideProps depuis la page Next.js. Elle s'exécutera sur le serveur pour chaque requête.
// pages/posts/[id].js
import Head from 'next/head';
function Post({ post }) {
if (!post) {
return <div>Chargement ou aucune publication trouvée...</div>;
}
return (
<div>
<Head>
<title>{post.title}</title>
</Head>
<h1>{post.title}</h1>
<p>{post.body}</p>
<p>ID de l l'auteur: {post.userId}</p>
</div>
);
}
// Cette fonction s'exécute à chaque requête pour cette page
export async function getServerSideProps(context) {
const { params } = context; // Accéder aux paramètres de l'URL pour les routes dynamiques
try {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
if (!res.ok) {
// Gérer les erreurs HTTP (ex: 404)
return {
notFound: true, // Redirigera vers la page 404 par défaut de Next.js si le post n'existe pas
};
}
const post = await res.json();
// La propriété `props` sera passée au composant de la page
return { props: { post } };
} catch (error) {
console.error("Erreur lors de la récupération du post:", error);
return {
props: { post: null }, // Gérer l'absence de données si l'API échoue
};
}
}
export default Post;
Explication :
getServerSidePropsest exportée de manière asynchrone. Elle est exécutée sur le serveur.contextcontient des informations sur la requête, y comprisparamspour les routes dynamiques (ici[id]).- Nous utilisons
fetchpour récupérer les données d'un placeholder d'API. - Si la requête API échoue ou le post n'est pas trouvé, nous retournons
notFound: truepour afficher la page 404. - L'objet retourné doit contenir une propriété
props, dont les valeurs seront passées comme props au composantPost. - Le composant
Postreçoitpostcomme prop et l'affiche.
3. Static Site Generation (SSG) avec getStaticProps
Le SSG est une méthode de pré-rendu où les pages sont générées une seule fois au moment de la compilation (build-time) de l'application. Le HTML résultant est ensuite servi statiquement.
3.1. Comment ça fonctionne ?
- Phase de compilation (build) : Lors de l'exécution de
next build, Next.js exécute la fonctiongetStaticProps(etgetStaticPathspour les routes dynamiques) sur le serveur. - Récupération des données :
getStaticPropsrécupère les données nécessaires. - Rendu HTML : Les données sont passées au composant de la page, qui est ensuite rendu en HTML.
- Enregistrement des fichiers : Le fichier HTML généré (et le JSON des props) est sauvegardé dans le répertoire
.next/pour être servi statiquement. - Déploiement : Les fichiers statiques sont déployés sur un serveur web ou un CDN.
- Requête du navigateur : Un utilisateur demande une page, et le serveur/CDN sert directement le fichier HTML pré-généré.
- Hydratation : Côté client, Next.js "hydrate" le HTML.
3.2. Quand l'utiliser ?
- Contenu statique ou rarement mis à jour : Pages de blog, documentation, pages "À propos", pages de produits dont les informations ne changent pas fréquemment.
- Performance maximale : Le HTML est servi instantanément sans aucun délai de traitement serveur au moment de la requête.
- Moins de charge serveur : Le serveur n'a qu'à servir des fichiers statiques.
- SEO crucial : Le contenu est directement disponible pour les crawlers.
3.3. Avantages et Inconvénients
Avantages :
- Extrêmement rapide (TTFB quasi nul) : Les pages sont des fichiers statiques, servis directement par un CDN.
- Excellent pour le SEO : Contenu complet disponible au premier chargement.
- Faible coût de déploiement et de maintenance : Moins de ressources serveur nécessaires.
- Très résilient : Peut être servi même si la base de données ou l'API est temporairement hors ligne (si les données sont mises en cache lors du build).
Inconvénients :
- Nécessite un nouveau build et un redéploiement : Chaque mise à jour des données nécessite une nouvelle compilation et un déploiement de l'application.
- Ne convient pas aux données en temps réel : Les données sont celles au moment du build.
3.4. Exemple de code avec getStaticProps
Pour utiliser le SSG, exportez une fonction asynchrone appelée getStaticProps depuis la page Next.js. Elle s'exécutera uniquement au moment de la compilation.
// pages/blog.js
import Head from 'next/head';
import Link from 'next/link';
function Blog({ posts }) {
return (
<div>
<Head>
<title>Mon Blog Statique</title>
</Head>
<h1>Derniers Articles</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
// Cette fonction s'exécute uniquement au moment de la compilation
export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10'); // Limite à 10 pour l'exemple
const posts = await res.json();
// La propriété `props` sera passée au composant de la page au moment du build
return {
props: {
posts,
},
};
}
export default Blog;
Explication :
getStaticPropsest exécutée une seule fois lors du processus de build.- Elle récupère une liste de posts et les passe au composant
Blogvia les props. - Le fichier
blog.htmlest généré avec la liste complète des posts.
3.5. SSG avec Routes Dynamiques : getStaticPaths et getStaticProps
Pour les pages SSG avec des routes dynamiques (ex: /blog/[id].js), Next.js a besoin de savoir quelles chemins doivent être générés statiquement au moment du build. C'est le rôle de getStaticPaths.
// pages/blog/[id].js
import Head from 'next/head';
function BlogPost({ post }) {
// Fallback: true ou 'blocking'
if (!post) {
// Si la page n'a pas été générée au build et fallback: true,
// ce sera affiché pendant que Next.js tente de la générer en arrière-plan.
return <div>Chargement...</div>;
}
return (
<div>
<Head>
<title>{post.title}</title>
</Head>
<h1>{post.title}</h1>
<p>{post.body}</p>
<Link href="/blog"><a>Retour au blog</a></Link>
</div>
);
}
// 1. getStaticPaths: Définit les chemins à pré-rendre au moment du build
export async function getStaticPaths() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5'); // Limite pour l'exemple
const posts = await res.json();
// Map les posts en chemins au format attendu par Next.js
const paths = posts.map((post) => ({
params: { id: post.id.toString() }, // L'ID doit être une chaîne de caractères
}));
return {
paths,
// fallback: false | true | 'blocking'
fallback: 'blocking', // Voir explication ci-dessous
};
}
// 2. getStaticProps: Récupère les données pour un chemin spécifique
export async function getStaticProps({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
if (!res.ok) {
// Si l'API renvoie une erreur (ex: 404), la page n'est pas trouvée
return {
notFound: true,
};
}
const post = await res.json();
return {
props: {
post,
},
};
}
export default BlogPost;
Explication :
-
getStaticPaths:- Est exécutée au moment du build.
- Elle doit retourner un tableau d'objets
paths, où chaque objet contient lesparamspour une page dynamique spécifique. L'ID doit être converti en chaîne de caractères. - La propriété
fallbackest cruciale :fallback: false: Seuls les chemins spécifiés parpathsseront générés. Toute autre URL pour cette route dynamique renverra une page 404. Idéal si vous connaissez tous les chemins à l'avance.fallback: true: Les chemins non générés au build ne renverront pas une 404 immédiatement. Au lieu de cela, Next.js servira une version "fallbac" de la page (souvent un état de chargement), puis générera la page à la première requête. Une fois générée, les requêtes suivantes serviront la version statique. Utile si vous avez de très nombreuses pages et ne voulez pas toutes les générer au build.fallback: 'blocking': Similaire àfallback: truemais la page ne sera pas affichée jusqu'à ce quegetStaticPropssoit terminée côté serveur. L'utilisateur verra un écran blanc ou le comportement par défaut du navigateur en attendant. Meilleur pour le SEO car le contenu complet est toujours livré directement.
-
getStaticProps(dans une page dynamique) :- Est exécutée pour chaque chemin retourné par
getStaticPaths. - Elle reçoit les
paramsdu chemin actuel, ce qui permet de récupérer les données spécifiques à ce chemin. - L'objet
postest ensuite passé au composantBlogPost.
- Est exécutée pour chaque chemin retourné par
4. Incremental Static Regeneration (ISR) avec revalidate
L'ISR est une fonctionnalité unique de Next.js qui combine les avantages du SSG (performance, SEO) avec la capacité de mettre à jour le contenu après le build, sans avoir à redéployer l'application. Elle est rendue possible grâce à l'option revalidate dans getStaticProps.
4.1. Comment ça fonctionne ?
- Build-time : La page est générée initialement via
getStaticPropscomme pour le SSG. - Requête initiale : Un utilisateur demande la page. Le HTML statique pré-généré est servi instantanément.
- Expiration du cache : Après le temps défini par
revalidate(ex: 60 secondes), si une nouvelle requête arrive :- Le contenu périmé (stale) est immédiatement servi à l'utilisateur.
- En arrière-plan, Next.js tente de re-générer la page en exécutant
getStaticPropssur le serveur. - Si la re-génération réussit, le nouveau contenu remplace l'ancien dans le cache.
- Requêtes suivantes : Les requêtes suivantes serviront le nouveau contenu mis à jour, jusqu'à la prochaine expiration de
revalidate.
C'est le modèle "stale-while-revalidate" (périmé tant que la re-validation est en cours).
4.2. Quand l'utiliser ?
- Contenu qui change occasionnellement : Articles de blog, pages de produits, listes d'événements. Vous voulez les avantages du SSG mais ne voulez pas un déploiement complet pour chaque petite mise à jour.
- Contenu avec un grand nombre de pages : Évite de générer toutes les pages au build et permet de les mettre à jour de manière incrémentale.
4.3. Avantages et Inconvénients
Avantages :
- Performance et SEO du SSG : La page est servie statiquement.
- Fraîcheur des données améliorée : Les données peuvent être mises à jour sans redéploiement complet.
- Moins de charge serveur qu'avec SSR : La re-génération ne se produit qu'après l'expiration et en arrière-plan.
- Scalabilité : Idéal pour des sites avec beaucoup de contenu.
Inconvénients :
- Légèrement plus complexe à mettre en œuvre : Nécessite une compréhension du concept de revalidation.
- Les données ne sont pas exactement en temps réel : Il y a un délai de
revalidatesecondes avant que le nouveau contenu n'apparaisse. - Nécessite un environnement de déploiement qui supporte les fonctions de revalidation (Vercel le gère nativement, sur d'autres plateformes cela peut demander des configurations spécifiques ou des webhooks).
4.4. Exemple de code avec revalidate
// pages/product/[slug].js
import Head from 'next/head';
function ProductPage({ product }) {
if (!product) {
return <div>Produit non trouvé...</div>;
}
return (
<div>
<Head>
<title>{product.name}</title>
</Head>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Prix : {product.price} €</p>
<p>Dernière mise à jour : {new Date(product.lastUpdated).toLocaleTimeString()}</p>
</div>
);
}
// getStaticPaths est toujours nécessaire pour les routes dynamiques avec ISR
export async function getStaticPaths() {
// Ici, on pourrait pré-générer les produits les plus visités, par exemple.
// Pour la démo, on simule un seul slug.
const paths = [
{ params: { slug: 'mon-super-produit' } },
];
return {
paths,
fallback: 'blocking', // ou true, selon le comportement souhaité pour les slugs non pré-générés
};
}
// getStaticProps avec l'option `revalidate` pour ISR
export async function getStaticProps({ params }) {
const { slug } = params;
// Simule une récupération de données d'API
// En production, cette API retournerait les données réelles
const productData = {
'mon-super-produit': {
id: 1,
name: 'Mon Super Produit',
description: 'Ceci est un produit incroyable avec des caractéristiques étonnantes.',
price: 99.99,
lastUpdated: Date.now(), // Simule une date de dernière mise à jour
},
};
const product = productData[slug] || null;
if (!product) {
return {
notFound: true,
};
}
return {
props: {
product,
},
// revalidate: Indique à Next.js de re-générer cette page toutes les 10 secondes (en mode prod)
// Cela ne se produira que si une nouvelle requête arrive après ces 10 secondes.
revalidate: 10, // secondes
};
}
export default ProductPage;
Explication :
- Le principe reste le même que pour
getStaticPathsetgetStaticProps. - La différence clé est l'ajout de
revalidate: 10à l'objet retourné pargetStaticProps. - Cela signifie que si une requête arrive plus de 10 secondes après la dernière génération de la page, Next.js servira la version périmée et lancera une re-génération en arrière-plan. La prochaine requête recevra la version mise à jour.
- En développement (
next dev),revalidatene fonctionne pas comme en production ; la page est toujours re-générée à chaque requête. Testez-le aprèsnext buildetnext start.
5. Choisir la Bonne Stratégie de Data Fetching
Le choix de la méthode de récupération de données dépend de plusieurs facteurs :
| Critère | Client-Side Rendering (CSR) | Server-Side Rendering (SSR) | Static Site Generation (SSG) | Incremental Static Regeneration (ISR) |
| :----------------------- | :-------------------------- | :-------------------------------- | :---------------------------------- | :------------------------------------- |
| Quand utiliser ? | Données post-chargement, user-specific, tableau de bord | Données en temps réel, personnalisées | Contenu statique, blog, doc, e-commerce | Contenu qui change occasionnellement, e-commerce |
| Où le code s'exécute ?| Navigateur | Serveur (à chaque requête) | Serveur (au build-time) | Serveur (au build et en arrière-plan) |
| Vitesse (TTFB) | Ralenti (après hydratation) | Modéré (dépend de l'API) | Très rapide (CDN) | Très rapide (CDN) |
| SEO | Difficile sans pré-rendu | Excellent | Excellent | Excellent |
| Fraîcheur des données| Temps réel (dès le chargement) | Temps réel | Au moment du build | Quasi temps réel (selon revalidate) |
| Charge serveur | Faible (peu de calcul) | Élevée (à chaque requête) | Très faible (fichiers statiques) | Faible (re-génération sporadique) |
| Mise à jour du contenu| Automatique (re-fetch) | Automatique (nouvelle requête) | Nouveau build et déploiement | Auto (selon revalidate), ou webhook |
En résumé :
- CSR : Pour les données non critiques pour le SEO ou l'affichage initial, ou pour les interactions post-chargement (ex: un compteur de clics, un fil de discussion en temps réel après le chargement initial).
- SSR (
getServerSideProps) : Lorsque vous avez besoin de données toujours à jour et spécifiques à la requête de l'utilisateur. Pensez aux tableaux de bord personnalisés ou aux pages d'actualités très dynamiques. - SSG (
getStaticProps) : Pour les pages dont le contenu ne change pas souvent et qui peuvent être pré-rendues à l'avance. Idéal pour la performance maximale et le SEO. - ISR (
revalidatedansgetStaticProps) : Le meilleur des deux mondes. Utilise la vitesse et le SEO du SSG tout en permettant aux données d'être rafraîchies périodiquement ou à la demande sans un re-déploiement complet. Parfait pour les blogs, les produits e-commerce ou les listes d'articles.
Il est important de noter que Next.js vous permet d'utiliser différentes stratégies de data fetching au sein de la même application, voire de la même page. Vous pouvez avoir une page principale en SSG, des pages de produits en ISR, et un tableau de bord utilisateur en SSR, avec des composants qui récupèrent leurs données via CSR. C'est cette flexibilité qui rend Next.js si puissant.
Conclusion
La maîtrise des stratégies de data fetching dans Next.js est essentielle pour construire des applications web performantes, scalables et optimisées pour le référencement. En comprenant les nuances entre le Server-Side Rendering (SSR), le Static Site Generation (SSG) et l'Incremental Static Regeneration (ISR), vous êtes en mesure de prendre des décisions éclairées sur la manière dont chaque page de votre application doit être construite et servie.
Next.js vous offre les outils pour choisir le bon compromis entre la vitesse de chargement, la fraîcheur des données et les exigences SEO. Adoptez une approche hybride, en sélectionnant la stratégie la plus adaptée à chaque route et à chaque besoin métier de votre application. C'est ainsi que vous exploiterez pleinement la puissance de Next.js et construirez des expériences utilisateur exceptionnelles.