Mise en Pratique : Implémenter SSR et SSG avec des Frameworks Modernes
Introduction : Maîtriser le Rendu Web Avancé
Bienvenue dans cette leçon pratique, pierre angulaire de notre module sur le "Rendu Web Avancé : SSR, SSG et Hydratation pour des Performances Inégalées". Après avoir exploré les concepts fondamentaux du Server-Side Rendering (SSR) et du Static Site Generation (SSG), il est temps de passer à l'action et de voir comment ces techniques de rendu transforment la performance et l'expérience utilisateur de nos applications web.
Dans le paysage actuel du développement web, les attentes en matière de rapidité, d'interactivité et de référencement (SEO) sont plus élevées que jamais. Les architectures de rendu côté client pur (SPA) ont leurs limites, notamment un temps de chargement initial potentiellement lent et des défis pour le SEO, car le contenu n'est pas immédiatement disponible pour les robots d'exploration. C'est là que le SSR et le SSG interviennent, offrant des solutions robustes pour livrer des pages HTML complètes au navigateur, améliorant ainsi la First Contentful Paint (FCP) et l'indexabilité.
Cette leçon se concentrera sur l'implémentation concrète du SSR et du SSG à l'aide de frameworks modernes qui ont démocratisé ces approches. Nous verrons comment des outils comme Next.js (pour React) ou Nuxt.js (pour Vue.js) simplifient grandement la mise en place de ces stratégies, vous permettant de construire des applications web rapides, robustes et optimisées.
Notre objectif est de comprendre non seulement comment implémenter le SSR et le SSG, mais aussi quand choisir l'une ou l'autre approche en fonction des besoins spécifiques de votre projet.
Prérequis
Pour tirer le meilleur parti de cette leçon, une compréhension de base des concepts suivants est recommandée :
- HTML, CSS, JavaScript : Les fondations du développement web.
- Concepts de base d'un framework JavaScript moderne : Avoir déjà travaillé avec React, Vue.js, ou Angular.
- Connaissance théorique du SSR et du SSG : Avoir suivi les leçons précédentes sur ces concepts.
- Notions de Node.js et npm/yarn : Pour l'installation et la gestion des dépendances des projets.
1. Implémenter le Server-Side Rendering (SSR)
1.1 Rappel du Concept de SSR
Le Server-Side Rendering (SSR) est une technique où le serveur web construit la page HTML complète pour chaque requête. Plutôt que d'envoyer un fichier HTML vide et de laisser le navigateur charger le JavaScript pour construire le contenu (comme dans une SPA typique), le serveur envoie une page déjà rendue avec toutes les données nécessaires. Une fois la page chargée dans le navigateur, le JavaScript prend le relais pour "hydrater" la page, la rendant interactive.
Avantages clés du SSR :
- Meilleure performance perçue : Le contenu s'affiche plus rapidement car le HTML est directement disponible.
- Meilleur SEO : Les robots d'exploration peuvent facilement indexer le contenu complet de la page.
- Expérience utilisateur améliorée : Moins d'état de chargement, plus fluide.
Inconvénients du SSR :
- Charge serveur accrue : Le serveur doit générer chaque page pour chaque requête.
- Complexité de déploiement : Nécessite un serveur Node.js (ou équivalent) capable de rendre les pages.
1.2 Implémentation Pratique avec Next.js
Next.js est un framework React populaire qui offre nativement des capacités de SSR et SSG. C'est un excellent choix pour démontrer ces concepts.
1.2.1 Mise en place d'un projet Next.js simple
Si vous n'avez pas encore Next.js, commençons par un nouveau projet :
npx create-next-app my-ssr-app --ts
cd my-ssr-app
npm run dev
Cela créera un projet Next.js avec TypeScript et lancera le serveur de développement. Votre application est accessible sur http://localhost:3000.
1.2.2 Exemple de SSR avec getServerSideProps
Dans Next.js, pour implémenter le SSR sur une page spécifique, vous exportez une fonction asynchrone appelée getServerSideProps depuis cette page. Cette fonction est exécutée à chaque requête côté serveur, avant que la page ne soit envoyée au client.
Considérons un exemple où nous voulons afficher des données météorologiques qui sont mises à jour fréquemment. Ces données doivent être à jour à chaque chargement de page.
Modifions le fichier pages/index.tsx :
// pages/index.tsx
import { GetServerSideProps } from 'next';
interface WeatherData {
city: string;
temperature: number;
condition: string;
timestamp: string;
}
interface HomePageProps {
weather: WeatherData;
}
export default function HomePage({ weather }: HomePageProps) {
return (
<div>
<h1>Météo Actuelle à {weather.city}</h1>
<p>Température : {weather.temperature}°C</p>
<p>Condition : {weather.condition}</p>
<p>Dernière mise à jour (serveur) : {new Date(weather.timestamp).toLocaleTimeString()}</p>
<p>
Actualisez la page pour voir les données mises à jour.
</p>
</div>
);
}
// Cette fonction s'exécute côté serveur à chaque requête.
export const getServerSideProps: GetServerSideProps<HomePageProps> = async (context) => {
console.log('Exécution de getServerSideProps côté serveur...');
// Simule une récupération de données dynamique (par exemple, depuis une API externe)
const data: WeatherData = {
city: 'Paris',
temperature: Math.floor(Math.random() * (25 - 10 + 1)) + 10, // Température aléatoire entre 10 et 25
condition: Math.random() > 0.5 ? 'Ensoleillé' : 'Nuageux',
timestamp: new Date().toISOString(),
};
// Retourne les props qui seront passées au composant de page
return {
props: {
weather: data,
},
};
};
Explication du code :
import { GetServerSideProps } from 'next';: Importe le type pourgetServerSideProps, améliorant la sécurité et l'autocomplétion.export const getServerSideProps: GetServerSideProps<HomePageProps> = async (context) => { ... };: Cette fonction est le cœur du SSR.- Elle est asynchrone car elle effectue souvent des opérations d'E/S (Input/Output) comme des appels API ou des requêtes de base de données.
- Elle est exécutée uniquement sur le serveur et à chaque fois qu'une requête arrive pour cette page.
contextcontient des informations sur la requête (params, query, req, res, etc.).
console.log('Exécution de getServerSideProps côté serveur...');: Si vous regardez la console de votre terminal où tournenpm run dev, vous verrez ce message apparaître à chaque fois que vous rafraîchissez la page dans votre navigateur. C'est la preuve que le code s'exécute bien côté serveur.- Simulation de données : Nous générons des données météorologiques aléatoires pour simuler une récupération de données dynamique. En production, ce serait un appel à une API externe ou une base de données.
return { props: { weather: data } };: Le retour degetServerSidePropsdoit être un objet avec une propriétéprops. Les données contenues danspropsseront passées en tant que props au composant de page (HomePagedans cet exemple).export default function HomePage({ weather }: HomePageProps) { ... };: Le composant de page reçoit les donnéesweatherpré-rendues par le serveur.
Lorsque vous naviguez vers cette page ou que vous la rafraîchissez, le serveur exécutera getServerSideProps, récupérera les données (ici, générera des données aléatoires), rendra la page HTML avec ces données, et l'enverra à votre navigateur. Le contenu est visible immédiatement, même sans JavaScript actif.
1.2.3 Quand utiliser le SSR ?
- Contenu dynamique et fréquent : Informations changeant souvent (ex: actualités en temps réel, cours de la bourse, météo).
- SEO critique : Pour les pages où le contenu doit être impérativement visible et indexable par les moteurs de recherche dès le premier chargement.
- Expérience utilisateur immédiate : Pour garantir que les utilisateurs voient le contenu le plus rapidement possible.
- Authentification et sessions : Quand le rendu de la page dépend de l'état de l'utilisateur ou de sa session (peut récupérer des données spécifiques à l'utilisateur sur le serveur).
2. Implémenter le Static Site Generation (SSG)
2.1 Rappel du Concept de SSG
Le Static Site Generation (SSG) est une technique où toutes les pages HTML sont générées au moment de la compilation de l'application (build time). Ces fichiers HTML statiques sont ensuite déployés sur un CDN (Content Delivery Network). Quand un utilisateur demande une page, le CDN sert directement le fichier HTML pré-généré, sans qu'un serveur n'ait à le construire à la volée.
Avantages clés du SSG :
- Performance inégalée : Les pages sont des fichiers statiques, servis très rapidement par les CDNs.
- Sécurité accrue : Pas de serveur côté back-end actif pour la plupart des requêtes, moins de surface d'attaque.
- Coût d'hébergement réduit : L'hébergement de fichiers statiques est généralement très économique.
- SEO excellent : Contenu disponible et indexable immédiatement.
Inconvénients du SSG :
- Données "statiques" ou moins dynamiques : Ne convient pas aux pages dont le contenu change très fréquemment.
- Reconstruction requise : Chaque modification du contenu (via CMS, etc.) nécessite une nouvelle compilation et un nouveau déploiement.
- Gestion des routes dynamiques : Peut être plus complexe pour les routes basées sur des données externes.
2.2 Implémentation Pratique avec Next.js
Next.js excelle également dans le SSG. Pour cela, vous utiliserez principalement la fonction getStaticProps. Pour les routes dynamiques, vous devrez également utiliser getStaticPaths.
2.2.1 Exemple de SSG avec getStaticProps
Pour le SSG, vous exportez une fonction asynchrone appelée getStaticProps. Cette fonction est exécutée au moment de la compilation (build time) et non à chaque requête.
Créons une page pour afficher une liste de posts de blog. Ces posts ne changent pas toutes les secondes, donc le SSG est idéal.
Créez un nouveau fichier pages/posts.tsx :
// pages/posts.tsx
import { GetStaticProps } from 'next';
interface Post {
id: number;
title: string;
body: string;
}
interface PostsPageProps {
posts: Post[];
buildTime: string;
}
export default function PostsPage({ posts, buildTime }: PostsPageProps) {
return (
<div>
<h1>Nos Articles de Blog</h1>
<p>Ces articles ont été générés statiquement à : {new Date(buildTime).toLocaleTimeString()} ({new Date(buildTime).toLocaleDateString()})</p>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body.substring(0, 100)}...</p>
</li>
))}
</ul>
</div>
);
}
// Cette fonction s'exécute côté serveur au moment de la compilation.
export const getStaticProps: GetStaticProps<PostsPageProps> = async (context) => {
console.log('Exécution de getStaticProps côté serveur au moment du build...');
// Simule une récupération de données depuis une API externe (par exemple, JSONPlaceholder)
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
const posts: Post[] = await res.json();
// Retourne les props qui seront passées au composant de page
return {
props: {
posts,
buildTime: new Date().toISOString(),
},
};
};
Explication du code :
export const getStaticProps: GetStaticProps<PostsPageProps> = async (context) => { ... };: Cette fonction est le cœur du SSG.- Elle est asynchrone pour permettre des appels API ou des accès à des bases de données.
- Elle est exécutée uniquement au moment de la compilation (lorsque vous exécutez
npm run build). - Crucial : Les données récupérées ici sont "gelées" dans le fichier HTML généré. Pour les mettre à jour, vous devez reconstruire l'application.
console.log('Exécution de getStaticProps côté serveur au moment du build...');: Vous ne verrez ce message qu'une seule fois dans la console lors de l'exécution denpm run build. Le serveur de développement (npm run dev) simule ce comportement mais en production, c'est bien au build que cela se passe.const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');: Nous récupérons ici 5 posts d'une API publique. C'est typiquement ainsi que vous intégreriez un CMS (Content Management System) headless ou une base de données statique.return { props: { posts, buildTime: new Date().toISOString() } };: Les données sont passées au composant de page. La propriétébuildTimeest incluse pour démontrer quand la page a été générée.
Pour voir cette page en action en mode SSG :
- Visitez
http://localhost:3000/posts. Next.js simule le SSG en mode dev. - Pour une véritable expérience SSG, arrêtez votre serveur de développement (
Ctrl+C) et exécutez :npm run build npm run startnpm run buildva créer le dossier.nextavec les fichiers HTML, CSS, JS optimisés.npm run startva servir ces fichiers. Vous verrez le messageExécution de getStaticProps...pendant lebuild, mais plus jamais au rafraîchissement de la page.
2.2.2 SSG avec chemins dynamiques (getStaticPaths)
Imaginez que vous ayez des milliers d'articles de blog et que chaque article doit avoir sa propre page statique (par exemple, /posts/1, /posts/2, etc.). Pour cela, Next.js propose getStaticPaths en combinaison avec getStaticProps.
getStaticPaths indique à Next.js quels chemins dynamiques doivent être pré-rendus au moment de la compilation.
Créez un dossier pages/posts et à l'intérieur, un fichier [id].tsx (notez les crochets pour un paramètre dynamique) :
// pages/posts/[id].tsx
import { GetStaticProps, GetStaticPaths } from 'next';
interface Post {
id: number;
title: string;
body: string;
}
interface PostPageProps {
post: Post;
buildTime: string;
}
export default function PostPage({ post, buildTime }: PostPageProps) {
// Fallback state for pages not pre-generated (if fallback is true)
if (!post) {
return <div>Chargement...</div>;
}
return (
<div>
<h1>{post.title}</h1>
<p>Article généré statiquement à : {new Date(buildTime).toLocaleTimeString()} ({new Date(buildTime).toLocaleDateString()})</p>
<p>{post.body}</p>
</div>
);
}
// Spécifie quels chemins (IDs de posts) doivent être pré-rendus au moment du build.
export const getStaticPaths: GetStaticPaths = async () => {
// Récupère la liste de tous les posts possibles pour générer leurs chemins
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5'); // Limité à 5 pour l'exemple
const posts: Post[] = await res.json();
// Map les posts en objets de chemins { params: { id: '...' } }
const paths = posts.map((post) => ({
params: { id: post.id.toString() }, // L'ID doit être une chaîne de caractères
}));
return {
paths,
// 'fallback: false' signifie que toutes les pages doivent être pré-générées.
// Les chemins non listés dans 'paths' retourneront un 404.
// 'fallback: true' permet la génération à la demande (ISR).
// 'fallback: 'blocking'' est similaire à 'true' mais bloque la navigation jusqu'à ce que la page soit générée.
fallback: false,
};
};
// Récupère les données pour un post spécifique, au moment du build, pour chaque chemin défini par getStaticPaths.
export const getStaticProps: GetStaticProps<PostPageProps> = async ({ params }) => {
const id = params?.id;
console.log(`Exécution de getStaticProps pour le post ${id} au moment du build.`);
// Récupère les données du post avec l'ID spécifié
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const post: Post = await res.json();
if (!post.id) { // Vérifie si le post existe
return {
notFound: true, // Renvoie une page 404 si le post n'est pas trouvé
};
}
return {
props: {
post,
buildTime: new Date().toISOString(),
},
};
};
Explication du code :
export const getStaticPaths: GetStaticPaths = async () => { ... };:- Cette fonction est exécutée au moment de la compilation.
- Elle doit retourner un objet avec une propriété
paths, qui est un tableau d'objets, chacun représentant un chemin dynamique que Next.js doit pré-rendre. Chaque objetpathdoit avoir une propriétéparamsqui correspond aux noms de vos paramètres dynamiques dans le nom du fichier (ici,id). fallback: false: Signifie que seules les pages listées danspathsseront générées. Si un utilisateur essaie d'accéder à unidqui n'est pas dans cette liste, Next.js retournera une page 404.fallback: trueoufallback: 'blocking': Ces options permettent à Next.js de générer des pages à la demande si elles n'ont pas été pré-générées. C'est utile pour des sites avec un très grand nombre de pages (par exemple, millions de produits) où pré-générer toutes les pages serait trop long. C'est ce qu'on appelle l'Incremental Static Regeneration (ISR), un hybride puissant entre SSG et SSR.
export const getStaticProps: GetStaticProps<PostPageProps> = async ({ params }) => { ... };:- Cette fonction est similaire à la précédente
getStaticPropsmais elle reçoit le paramètreidextrait du chemin. - Elle est exécutée pour chaque chemin défini par
getStaticPathsau moment du build. - Elle récupère les données spécifiques au post correspondant à cet
id.
- Cette fonction est similaire à la précédente
Après avoir ajouté ce fichier, exécutez de nouveau npm run build puis npm run start. Naviguez vers http://localhost:3000/posts/1, http://localhost:3000/posts/2, etc. Vous verrez ces pages chargées instantanément car elles ont été pré-générées.
2.2.3 Quand utiliser le SSG ?
- Contenu statique ou peu fréquent : Pages d'information (À propos, Contact), blogs, documentation, portfolios, catalogues de produits qui ne changent pas toutes les heures.
- Performance maximale : Quand la vitesse de chargement est la priorité absolue.
- Très bon SEO : Le contenu est entièrement disponible pour les robots dès le premier chargement.
- Faible coût d'hébergement : Peut être hébergé sur des CDNs ou des services de stockage d'objets (comme Netlify, Vercel, AWS S3) pour un coût minimal.
3. SSR vs. SSG : Quand Choisir Quoi ?
Le choix entre SSR et SSG dépend principalement de la nature de votre contenu et des exigences de votre application. Souvent, les applications modernes utilisent une approche hybride, combinant SSR et SSG pour différentes pages.
| Caractéristique / Scénario | Server-Side Rendering (SSR) | Static Site Generation (SSG) | | :------------------------------ | :-------------------------------------------------- | :------------------------------------------------ | | Données | Dynamiques, mises à jour fréquentes | Statiques, ou mises à jour peu fréquentes | | Quand le rendu est fait | À chaque requête utilisateur (Run Time) | Au moment de la compilation (Build Time) | | Performance | Bonne (meilleur FCP que SPA) | Excellente (servi par CDN, instantané) | | SEO | Excellent | Excellent | | Charge serveur | Plus élevée (chaque requête rendue) | Très faible (sert des fichiers statiques) | | Temps de déploiement | Rapide | Peut être long si beaucoup de pages à générer | | Complexité de gestion | Serveur Node.js actif, gestion de l'infrastructure | Simplicité de déploiement sur CDN, pas de serveur | | Exemples d'utilisation | Panier d'achat, tableau de bord utilisateur, flux d'actualités en temps réel, page de profil dynamique | Blogs, documentation, pages "À propos", landing pages, e-commerce avec produits stables |
Principes de décision :
- Le contenu change-t-il souvent ?
- Si OUI, et que la dernière donnée est cruciale à chaque chargement (ex: prix en temps réel), optez pour le SSR.
- Si NON, ou si un léger décalage n'est pas critique, le SSG est probablement une meilleure option pour la performance et le coût.
- Le nombre de pages est-il gérable pour une pré-génération ?
- Si OUI, le SSG est parfait.
- Si NON (millions de pages), utilisez le SSR ou explorez l'ISR (Incremental Static Regeneration) avec Next.js (
fallback: trueourevalidatedansgetStaticProps) qui est un compromis puissant permettant de générer des pages à la volée tout en les cachant statiquement.
- La rapidité de déploiement est-elle prioritaire sur la performance ultime à chaque chargement ?
- Si OUI, le SSR peut être plus rapide à mettre à jour.
- Si NON, et que la reconstruction n'est pas un problème, le SSG offre une performance inégalée.
4. L'Hydratation : Le Pont entre Statique et Interactif
Peu importe si vous utilisez le SSR ou le SSG, une fois que le navigateur reçoit le HTML pré-rendu, il y a une étape cruciale appelée l'hydratation.
Qu'est-ce que l'hydratation ?
L'hydratation est le processus par lequel le JavaScript de votre application (le bundle client) prend le contrôle du HTML statique ou pré-rendu. Il attache les écouteurs d'événements, gère l'état et rend votre application interactive, transformant une page HTML statique en une Single Page Application fonctionnelle.
Pourquoi est-ce important ?
- Interactivité : Sans hydratation, votre page serait juste du texte et des images. Elle ne répondrait pas aux clics, ne gérerait pas les formulaires, etc.
- Continuité de l'expérience : L'hydratation permet une transition en douceur du contenu statique initial à une application dynamique, évitant le "flash de contenu" ou la reconstruction visuelle.
- Réactivité : Après l'hydratation, les navigations subséquentes et les mises à jour de l'UI se comportent comme une SPA rapide, ne nécessitant pas de rechargement complet de page.
Les frameworks comme Next.js ou Nuxt.js gèrent l'hydratation de manière transparente. Lorsque vous utilisez getServerSideProps ou getStaticProps, le framework s'assure que le bundle JavaScript correspondant est chargé par le navigateur et qu'il "reprend" là où le serveur s'est arrêté, en attachant les comportements interactifs.
Conclusion
Nous avons parcouru le chemin de l'implémentation pratique du Server-Side Rendering (SSR) et du Static Site Generation (SSG) à l'aide de frameworks modernes comme Next.js. Vous avez vu comment getServerSideProps permet de construire des pages dynamiques à la volée, idéales pour les données en temps réel et les interactions utilisateur spécifiques. Parallèlement, getStaticProps et getStaticPaths vous offrent les outils pour pré-générer des sites ultra-rapides, parfaits pour le contenu statique ou les applications centrées sur le contenu.
Le choix entre SSR et SSG n'est pas mutuellement exclusif ; la puissance des frameworks modernes réside dans leur capacité à vous permettre de combiner ces stratégies au sein d'une même application. C'est ce qu'on appelle la "Full Stack React" ou "Universal Vue", où chaque page peut adopter la stratégie de rendu la plus appropriée à ses besoins.
En maîtrisant ces techniques, vous êtes désormais équipé pour construire des applications web qui non seulement délivrent une expérience utilisateur exceptionnelle grâce à une performance et une réactivité accrues, mais qui sont également optimisées pour le référencement, assurant ainsi une meilleure visibilité sur le web. Le rendu web avancé est un pilier fondamental de l'ingénierie web moderne, et vous en maîtrisez maintenant les aspects les plus pratiques. Continuez à expérimenter et à explorer les nombreuses possibilités offertes par ces puissants outils !