Rendu côté serveur pour les applications monopages (SPA) : résoudre l’indexation par les moteurs de recherche
Les robots d’exploration comme Googlebot ou YandexBot ne voient souvent qu’un HTML vide lorsqu’ils analysent des SPAs reposant sur le rendu côté client (CSR). Sans rendu côté serveur (SSR), ils n’attendent pas l’exécution du JavaScript — et laissent les pages non indexées. Le SSR résout ce problème en générant un HTML entièrement rendu côté serveur avant de l’envoyer au navigateur. Cela accélère le FCP, le LCP et le TTFB — améliorant ainsi les Core Web Vitals et la visibilité organique.
Comparaison des approches de rendu
| Paramètre | CSR | SSR | Pré-rendu | SSG |
|----------|-----|-----|--------------|-----|
| Lieu du rendu | Navigateur | Serveur (par requête) | Généré à l’avance pour des URL spécifiques | Généré à l’avance pour toutes les pages |
| FCP/LCP | Lent | Rapide | Rapide | Très rapide |
| SEO | Médiocre | Excellent | Bon | Excellent |
| Actualité des données | En temps réel | En temps réel | Obsolète | Obsolète |
| Charge serveur | Minimale | Élevée | Faible | Minimale |
Le SSR convient particulièrement aux pages dynamiques — catalogues produits, fils d’actualités, tableaux de bord. Le pré-rendu s’adapte mieux aux URL rarement mises à jour (ex. : pages marketing). La génération statique (SSG) est idéale pour les contenus fixes comme la documentation ou les pages d’accueil.
React + Next.js : mise en œuvre pas à pas
Créez un nouveau projet :
npx create-next-app@latest mon-app-ssr
cd mon-app-ssr
npm run dev
Pour les pages dynamiques, utilisez getServerSideProps :
// pages/product/[id].jsx
export async function getServerSideProps(context) {
const { id } = context.params;
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
if (!res.ok) {
return { notFound: true };
}
return {
props: { product },
};
}
export default function ProductPage({ product }) {
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}
Ajoutez des balises méta optimisées pour le référencement :
import Head from 'next/head';
export default function ProductPage({ product }) {
return (
<>
<Head>
<title>{product.name} | Boutique</title>
<meta name="description" content={product.shortDescription} />
<meta property="og:title" content={product.name} />
<meta property="og:image" content={product.imageUrl} />
</Head>
<main>
<h1>{product.name}</h1>
</main>
</>
);
}
Déploiement : Vercel, Netlify ou Node.js avec PM2.
Hydratation sans erreurs
L’hydratation désigne la façon dont React côté client « réanime » l’HTML rendu côté serveur. Évitez les incohérences :
- Gardez les données côté serveur et côté client synchronisées.
- Vérifiez l’environnement d’exécution avant d’utiliser
windowoudocument. - Surveillez les avertissements dans la console navigateur.
const isBrowser = typeof window !== 'undefined';
Vue + Nuxt.js : rendu côté serveur
Initialisez un projet :
npx nuxi@latest init mon-app-nuxt
cd mon-app-nuxt
npm install
npm run dev
Récupérez les données côté serveur :
<!-- pages/product/[id].vue -->
<script setup>
const route = useRoute();
const { data: product, error } = await useFetch(
`/api/products/${route.params.id}`
);
if (error.value) {
throw createError({ statusCode: 404, message: 'Non trouvé' });
}
useHead({
title: product.value?.name,
meta: [
{ name: 'description', content: product.value?.shortDescription },
],
});
</script>
<template>
<main>
<h1>{{ product?.name }}</h1>
<p>{{ product?.description }}</p>
</main>
</template>
Configurez nuxt.config.ts :
export default defineNuxtConfig({
ssr: true,
app: {
head: {
htmlAttrs: { lang: 'fr' },
link: [{ rel: 'canonical', href: 'https://example.com' }],
},
},
nitro: {
preset: 'node-server',
},
});
Angular Universal : intégration SSR
Pour Angular 17+ :
ng add @angular/ssr
Définissez le point de terminaison serveur dans server.ts :
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
// ... autres imports
const server = express();
// ...
server.get('*', (req, res, next) => {
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: join(browserDistFolder, 'index.html'),
url: req.originalUrl,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
})
.then((html) => res.send(html))
.catch(next);
});
Construisez et lancez :
npm run build:ssr
npm run serve:ssr
Détectez en toute sécurité la plateforme :
import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
console.log(window.location.href);
}
Intégration avec un CMS headless
Pour Next.js + Contentful GraphQL :
export async function getServerSideProps({ params }) {
const query = `
query GetArticle($slug: String!) {
articleCollection(where: { slug: $slug }) {
items {
title
body
seo {
title
description
}
}
}
}
`;
const res = await fetch(
`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`,
},
body: JSON.stringify({ query, variables: { slug: params.slug } }),
}
);
const { data } = await res.json();
// ...
}
Points clés à retenir
- Le SSR fournit un HTML entièrement rendu aux robots d’exploration — résolvant ainsi le problème fondamental d’indexation des applications CSR.
- Next.js, Nuxt.js et Angular Universal sont des frameworks éprouvés en production pour le SSR.
- Les incohérences d’hydratation déclenchent des rendus complets — validez toujours l’environnement d’exécution.
- Les Core Web Vitals (FCP, LCP, TTFB) s’améliorent de 50 à 80 % après passage au SSR.
- Intégrez un cache Redis pour maintenir le TTFB sous les 200 ms même en cas de pics de trafic.
— Editorial Team
Aucun commentaire pour le moment.