SSR vs CSR : impact réel sur le crawl et le SEO

Un e-commerce de 18 000 fiches produits migre de Nuxt.js SSR vers une SPA Vue.js pure pour "simplifier l'architecture". Trois semaines plus tard, 60% des pages disparaissent de l'index Google. Le trafic organique chute de 42%. Le CTO ne comprend pas — Googlebot est censé exécuter JavaScript depuis 2019. Ce scénario n'est pas hypothétique. C'est le genre de cas qui arrive tous les trimestres sur des sites de cette taille, et la cause racine est toujours la même : une incompréhension de ce que le rendering côté client implique réellement pour le crawl.

Ce que Googlebot voit : la mécanique du Web Rendering Service

Avant de comparer SSR et CSR, il faut comprendre le pipeline de Google. Googlebot fonctionne en deux phases distinctes, documentées par Google dans sa documentation sur le rendering JavaScript :

  1. Crawl + indexation du HTML brut : Googlebot récupère le HTML retourné par le serveur. Si le contenu est présent dans ce HTML, il est indexé immédiatement.
  2. File d'attente de rendering : si le HTML contenu ne suffit pas (SPA, contenu injecté en JS), la page est mise en file d'attente pour être exécutée dans un headless Chromium (le Web Rendering Service, WRS). Ce rendering intervient plus tard — parfois des heures, parfois des jours.

Ce délai est le cœur du problème. Sur un site de 500 pages, l'impact est négligeable. Sur un site de 15 000+ pages avec du contenu qui change fréquemment (stock, prix, avis), ce délai peut signifier que Googlebot indexe des informations obsolètes ou n'indexe jamais certaines pages.

Le HTML brut d'une SPA classique

Voici ce que retourne un serveur typique pour une application React CSR :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8" />
  <title>MonSite</title>
  <meta name="description" content="MonSite - Achetez en ligne" />
  <link rel="stylesheet" href="/static/css/main.a3b2c1.css" />
</head>
<body>
  <div id="root"></div>
  <script src="/static/js/vendor.8f2d1a.js"></script>
  <script src="/static/js/main.4e7b3c.js"></script>
</body>
</html>

C'est tout. Pas de <h1>, pas de contenu produit, pas de maillage interne, pas de données structurées. La balise <title> est générique — le titre dynamique de la page est injecté côté client par react-helmet ou useHead(). La meta description est celle par défaut du shell applicatif.

Googlebot en phase 1 voit ça. La page entre dans la file de rendering. Si le WRS est surchargé, ou si le JavaScript échoue pour une raison quelconque (timeout, erreur réseau sur un chunk, API tierce indisponible), la page reste indexée avec ce HTML vide.

Le même contenu en SSR

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8" />
  <title>Chaussures de running Trail X500 - MonSite</title>
  <meta name="description" content="Trail X500 : chaussure de trail avec semelle Vibram, drop 6mm. Livraison 24h. À partir de 129,90€." />
  <link rel="canonical" href="https://monsite.fr/chaussures/trail-x500" />
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Product",
    "name": "Chaussures de running Trail X500",
    "offers": { "@type": "Offer", "price": "129.90", "priceCurrency": "EUR" }
  }
  </script>
</head>
<body>
  <header><!-- navigation complète --></header>
  <main>
    <h1>Chaussures de running Trail X500</h1>
    <p>Conçue pour les sentiers techniques, la Trail X500 combine...</p>
    <!-- contenu complet de la fiche produit -->
    <nav aria-label="Produits similaires">
      <a href="/chaussures/trail-x400">Trail X400</a>
      <a href="/chaussures/trail-x600">Trail X600</a>
    </nav>
  </main>
  <script src="/static/js/hydrate.4e7b3c.js" defer></script>
</body>
</html>

Googlebot en phase 1 voit le titre, la description, le contenu, le maillage interne, les données structurées. Pas besoin d'attendre le WRS. L'indexation est immédiate et complète.

Le coût réel du CSR sur le crawl budget

Le crawl budget n'est pas un concept abstrait. Sur un site de 18 000 pages, il se mesure concrètement dans les logs serveur et dans le rapport de statistiques d'exploration de la Search Console.

Scénario concret : migration CSR d'un catalogue e-commerce

Prenons un site e-commerce avec les caractéristiques suivantes :

  • 18 200 pages indexées (fiches produits, catégories, guides d'achat)
  • Framework : Nuxt.js 3 en mode SSR, hébergé sur un VPS
  • Trafic organique : ~35 000 sessions/mois
  • Crawl moyen : ~1 200 pages/jour par Googlebot

L'équipe décide de migrer vers une SPA Vue.js pure (CSR) pour réduire la charge serveur et simplifier le déploiement sur un CDN statique. Le raisonnement semble logique : Google exécute le JavaScript, donc le contenu sera rendu.

Voici ce qui se passe dans les semaines suivant la migration :

Semaine 1-2 : le rapport de couverture de la Search Console commence à montrer une augmentation des pages "Découvertes, actuellement non indexées". Googlebot crawle le shell HTML vide, ne trouve pas de contenu exploitable en phase 1, et met les pages en file d'attente WRS. Mais le volume de pages à rendre dépasse la capacité allouée par Google pour ce site.

Semaine 3 : les impressions chutent sur les requêtes longue traîne (fiches produits spécifiques). Les pages catégories, plus crawlées, conservent une partie de leur visibilité car elles sont re-rendues plus fréquemment.

Semaine 4-6 : le trafic organique global a baissé de 42%. Les logs serveur montrent que Googlebot continue à crawler ~1 200 pages/jour, mais chaque crawl retourne le même shell HTML — donc chaque visite de Googlebot consomme du budget sans rien apporter tant que le rendering n'est pas effectué.

Le problème n'est pas que Google ne peut pas rendre le JavaScript. C'est qu'il ne le fait pas assez vite pour un site de cette taille avec du contenu qui évolue. Le WRS a un coût en ressources pour Google. Plus votre site est petit ou peu autoritaire, moins Google y investit de capacité de rendering.

Vérifier le rendering avec les outils appropriés

Pour diagnostiquer ce type de problème, trois outils sont essentiels :

1. L'outil d'inspection d'URL de la Search Console — Il montre le HTML rendu tel que Google le voit. Comparez le HTML brut (onglet "Code source de la page") avec le rendu complet (onglet "Page testée en direct"). Si le contenu critique n'apparaît que dans le rendu live mais pas dans le HTML source, vous dépendez du WRS.

2. Screaming Frog en mode JavaScript rendering — Configurez Screaming Frog pour crawler avec et sans exécution JavaScript :

Configuration > Spider > Rendering > JavaScript
Comparer les résultats :
- Crawl 1 : Rendering = None (HTML brut)
- Crawl 2 : Rendering = JavaScript (Chrome headless)

Exporter les deux crawls et comparer :
- Titles manquants en mode HTML brut
- H1 manquants en mode HTML brut
- Liens internes non découverts en mode HTML brut
- Meta description vides/génériques en mode HTML brut

Si le diff entre les deux crawls est massif, votre site a un problème de dépendance au rendering client.

3. Chrome DevTools avec JavaScript désactivé — La méthode la plus rapide pour un diagnostic visuel. Ctrl+Shift+P > "Disable JavaScript" > rechargez la page. Ce que vous voyez est l'approximation la plus proche de ce que Googlebot voit en phase 1. Si la page est blanche ou n'affiche que le layout sans contenu, c'est un signal d'alerte.

Les nuances que personne ne mentionne

Le CSR n'est pas toujours un problème

Affirmer que "le CSR est mauvais pour le SEO" serait une simplification excessive. Plusieurs cas de figure rendent le CSR parfaitement acceptable :

  • Dashboards et applications authentifiées : si la page est derrière un login, Googlebot ne la verra jamais de toute façon. Le rendering mode est sans importance pour le SEO.
  • Sites de moins de ~200 pages à contenu stable : Google a largement la capacité WRS pour rendre un petit site. Le délai de rendering est rarement un problème.
  • Pages à faible valeur SEO : filtres de recherche, pages de comparaison dynamique, outils interactifs. Si la page n'a pas vocation à être indexée, le mode de rendering est un non-sujet.

Le problème se concentre sur les sites à fort volume de pages + contenu dynamique + enjeu SEO significatif. C'est là que le SSR devient non-négociable.

L'hydratation SSR a un coût

Le SSR n'est pas gratuit non plus. Rendre 18 000 pages côté serveur à chaque requête a un coût CPU significatif. C'est pourquoi les architectures modernes utilisent des stratégies hybrides :

  • SSG (Static Site Generation) pour les pages à contenu stable (guides, pages catégories, landing pages)
  • ISR (Incremental Static Regeneration) pour les pages à contenu semi-dynamique (fiches produits avec prix/stock mis à jour toutes les heures)
  • SSR pur uniquement pour les pages qui nécessitent un contenu temps réel

Next.js, Nuxt.js et Astro supportent tous ces modes de manière granulaire, page par page.

Le pre-rendering dynamique : une solution temporaire, pas une architecture

Le dynamic rendering (servir du HTML pre-rendu à Googlebot et du CSR aux utilisateurs) a été longtemps recommandé par Google comme solution transitoire. Google a depuis clarifié sa position : le dynamic rendering est considéré comme une solution de contournement, pas une architecture cible. Il introduit de la complexité (détection du user-agent, maintenance d'un service de pre-rendering), des risques de cloaking involontaire, et un point de défaillance supplémentaire.

Si vous utilisez encore Rendertron ou Prerender.io, planifiez la migration vers du SSR ou SSG natif.

Implémenter le SSR correctement : les pièges techniques

Le Time to First Byte (TTFB) du SSR

Un SSR mal implémenté peut dégrader le SEO au lieu de l'améliorer. Le piège classique : le serveur SSR fait des appels API bloquants pour assembler la page, ce qui gonfle le TTFB à 2-3 secondes. Googlebot a des timeouts — et les Core Web Vitals pénalisent un TTFB excessif.

Voici une implémentation Next.js qui illustre le problème et sa solution :

// ❌ Mauvais : appels API séquentiels dans getServerSideProps
export async function getServerSideProps(context: GetServerSidePropsContext) {
  const product = await fetch(`${API_URL}/products/${context.params.slug}`);
  const reviews = await fetch(`${API_URL}/reviews/${context.params.slug}`);
  const related = await fetch(`${API_URL}/related/${context.params.slug}`);
  // TTFB = somme des 3 appels (ex: 400ms + 300ms + 250ms = 950ms)

  return {
    props: {
      product: await product.json(),
      reviews: await reviews.json(),
      related: await related.json(),
    },
  };
}

// ✅ Mieux : appels parallèles + timeout + fallback
export async function getServerSideProps(context: GetServerSidePropsContext) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 800); // hard timeout 800ms

  try {
    const [product, reviews, related] = await Promise.all([
      fetch(`${API_URL}/products/${context.params.slug}`, { signal: controller.signal }),
      fetch(`${API_URL}/reviews/${context.params.slug}`, { signal: controller.signal }),
      fetch(`${API_URL}/related/${context.params.slug}`, { signal: controller.signal }),
    ]);
    clearTimeout(timeout);
    // TTFB = max des 3 appels (ex: max(400ms, 300ms, 250ms) = 400ms)

    return {
      props: {
        product: await product.json(),
        reviews: await reviews.json(),
        related: await related.json(),
      },
    };
  } catch (err) {
    clearTimeout(timeout);
    // Fallback : servir au minimum les données produit depuis le cache
    const cachedProduct = await redis.get(`product:${context.params.slug}`);
    return {
      props: {
        product: cachedProduct ? JSON.parse(cachedProduct) : null,
        reviews: [],
        related: [],
      },
    };
  }
}

La différence entre les deux approches : ~950ms vs ~400ms de TTFB. Sur 18 000 pages, c'est la différence entre un site que Googlebot crawle confortablement et un site qu'il crawle au ralenti.

Configurer le cache serveur pour le SSR

Un SSR sans stratégie de cache est un serveur qui souffre. Voici une configuration Nginx qui met en cache les réponses SSR pour Googlebot et les utilisateurs, avec invalidation intelligente :

# /etc/nginx/conf.d/ssr-cache.conf

proxy_cache_path /var/cache/nginx/ssr
  levels=1:2
  keys_zone=ssr_cache:64m
  max_size=2g
  inactive=60m
  use_temp_path=off;

server {
    listen 443 ssl http2;
    server_name monsite.fr;

    location / {
        proxy_pass http://127.0.0.1:3000; # serveur Next.js / Nuxt.js

        # Cache SSR : 10 minutes pour les fiches produits, 1h pour les catégories
        proxy_cache ssr_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;

        # Header pour debug : HIT/MISS/STALE
        add_header X-Cache-Status $upstream_cache_status always;

        # Bypass cache pour les requêtes avec cookie de session (utilisateurs connectés)
        proxy_cache_bypass $cookie_session_id;
        proxy_no_cache $cookie_session_id;

        # Stale-while-revalidate : servir le cache périmé pendant la regénération
        proxy_cache_background_update on;
    }

    # Invalidation via PURGE (protégé par IP)
    location ~ /purge(/.*) {
        allow 10.0.0.0/8;
        deny all;
        proxy_cache_purge ssr_cache "$scheme$request_method$host$1";
    }
}

Cette configuration garantit que les pages sont servies rapidement (depuis le cache Nginx) tout en restant à jour. Le header X-Cache-Status est précieux pour le debug : si vous voyez systématiquement MISS dans vos logs lors du crawl de Googlebot, votre cache est mal configuré ou votre TTL est trop court.

Mesurer l'impact : les métriques qui comptent

Dans les logs serveur

La seule source de vérité pour comprendre le comportement de Googlebot est l'analyse de logs. Pas la Search Console (qui agrège et simplifie), pas Screaming Frog (qui simule un crawl, pas le crawl réel).

Filtrez vos access logs sur les user-agents Google :

# Extraire les requêtes Googlebot avec le status code et le temps de réponse
grep -E "Googlebot|Google-InspectionTool" /var/log/nginx/access.log \
  | awk '{print $7, $9, $NF}' \
  | sort \
  | head -100

# Compter les pages crawlées par jour
grep "Googlebot" /var/log/nginx/access.log \
  | awk '{print $4}' \
  | cut -d: -f1 \
  | sort \
  | uniq -c \
  | sort -rn

# Identifier les pages avec un temps de réponse > 1s (TTFB problématique)
grep "Googlebot" /var/log/nginx/access.log \
  | awk '$NF > 1.0 {print $7, $NF}' \
  | sort -t' ' -k2 -rn \
  | head -50

Ce que vous cherchez :

  • Le ratio pages crawlées / pages indexables : si Google crawle 1 200 pages/jour mais que vous avez 18 000 pages indexables, il faut ~15 jours pour un crawl complet. Chaque page modifiée peut mettre 2+ semaines à être re-indexée.
  • Les codes de statut : un taux élevé de 5xx sur les requêtes Googlebot signale un serveur SSR instable.
  • Le TTFB moyen pour Googlebot : au-delà de 800ms, vous perdez du crawl budget par lenteur.

Dans la Search Console

Le rapport "Statistiques sur l'exploration" donne trois indicateurs clés :

  • Temps de réponse moyen : corrèle directement avec votre stratégie de rendering + cache. Un site SSR bien caché est typiquement sous 200ms. Un SSR sans cache peut être à 800ms+. Un CSR retourne le shell HTML en ~50ms mais le contenu réel n'y est pas.
  • Pages explorées par jour : une chute soudaine après une migration CSR est un signal d'alarme classique.
  • Ressources explorées : en CSR, Googlebot doit télécharger les chunks JS en plus du HTML. C'est du crawl budget consommé pour des assets, pas pour des pages.

SSR, CSR, SSG, ISR : arbre de décision

Plutôt qu'un tableau simpliste, voici la logique de décision que nous recommandons :

Le contenu de la page doit-il être indexé par Google ? → Non : CSR, aucun enjeu. Dashboard, espace client, outils internes. → Oui : continuez.

Le contenu change-t-il plus d'une fois par jour ? → Non : SSG (build-time rendering). Le HTML est généré au déploiement. Performances optimales, zéro charge serveur au runtime. Idéal pour les blogs, landing pages, documentation. → Oui : continuez.

Le contenu change-t-il en temps réel (stock, prix live, personnalisation) ? → Non (mise à jour toutes les heures/quelques heures) : ISR avec revalidation. Next.js revalidate: 3600, Nuxt.js routeRules avec swr: 3600. Le meilleur compromis performance/fraîcheur. → Oui : SSR pur avec cache courte durée (30s-5min). Acceptez le coût serveur, mitigez avec du cache Nginx ou un CDN edge (Vercel, Cloudflare Workers).

Le volume de pages dépasse-t-il 10 000 ? → Si oui, le SSG pur devient problématique (temps de build). Privilégiez ISR ou SSR avec cache agressif. Next.js et Nuxt.js supportent le build incrémental pour ne pas regénérer les 18 000 pages à chaque déploiement.

Le cas hybride : SSR pour les pages critiques, CSR pour le reste

Une architecture mature combine souvent les deux. Les pages catégories et fiches produits sont en SSR/ISR (enjeu SEO). Les pages de panier, de checkout, de compte client sont en CSR (aucun enjeu SEO, interactivité maximale).

Next.js et Nuxt.js permettent cette granularité nativement. Avec Nuxt.js 3 par exemple :

// nuxt.config.ts — rendering hybride par route
export default defineNuxtConfig({
  routeRules: {
    // Pages catégories : ISR, revalidation toutes les heures
    '/categorie/**': { isr: 3600 },

    // Fiches produits : ISR, revalidation toutes les 30 minutes
    '/produit/**': { isr: 1800 },

    // Blog : SSG au build
    '/blog/**': { prerender: true },

    // Espace client : CSR uniquement, pas d'indexation
    '/compte/**': { ssr: false },

    // Panier et checkout : CSR
    '/panier': { ssr: false },
    '/checkout/**': { ssr: false },
  },
});

Cette configuration est testable en un crawl Screaming Frog : les pages /produit/* et /categorie/* doivent retourner du HTML complet en mode "no JavaScript rendering". Les pages /compte/* retourneront un shell vide, ce qui est attendu et ne pose aucun problème SEO.

Détecter les régressions de rendering en continu

Le piège le plus vicieux n'est pas la migration initiale — c'est la régression silencieuse. Un développeur pousse un commit qui casse le SSR sur un segment de pages. Le fallback client-side prend le relais, les utilisateurs ne voient rien d'anormal, mais Googlebot reçoit désormais un shell vide sur 3 000 fiches produits.

Sans monitoring automatisé, cette régression peut passer inaperçue pendant des semaines. Le temps que l'impact apparaisse dans la Search Console (délai de 3-5 jours sur les données), puis que quelqu'un remarque la baisse de trafic et lance l'investigation, le mal est fait.

C'est précisément le type de problème qu'un outil de monitoring comme SEOGard détecte en moins de 24h : un crawl régulier qui compare le HTML brut servi sur chaque segment de pages, et qui alerte dès qu'un title, un H1 ou un bloc de contenu disparaît du HTML source. La détection se fait sur le HTML sans exécution JavaScript — exactement ce que Googlebot voit en phase 1.

Les points de contrôle à monitorer en continu :

  • Présence du <title> dynamique dans le HTML brut (pas le title générique du shell)
  • Présence du <h1> dans le HTML brut
  • Nombre de liens internes dans le HTML brut (une chute soudaine signale un SSR cassé)
  • Présence des données structurées dans le HTML brut
  • Code de statut HTTP sur les routes SSR (un 500 intermittent qui tombe en fallback CSR)

Automatisez ces vérifications sur un échantillon représentatif de chaque segment de pages. Un check quotidien sur 5% des URLs de chaque type (catégorie, produit, article) suffit pour détecter une régression avant qu'elle n'impacte l'indexation.

La détection proactive de ces régressions de rendering est la raison d'être d'un monitoring SEO technique continu — attendre la Search Console, c'est réagir 3 semaines trop tard.


Le choix entre SSR et CSR n'est pas une question de préférence technique — c'est une décision d'architecture qui a des conséquences directes et mesurables sur votre visibilité organique. Pour tout site dont le SEO est un canal d'acquisition significatif et dont le volume de pages dépasse quelques centaines, le SSR (ou ses variantes SSG/ISR) n'est pas optionnel. Et la vraie difficulté n'est pas de l'implémenter une fois : c'est de garantir qu'il ne casse pas silencieusement au fil des déploiements.

Articles connexes

Rendering21 février 2026

Tester le rendering Google : outils et méthodes avancées

Méthodes concrètes pour valider ce que Google voit sur vos pages : Inspect URL, Puppeteer headless, diff DOM et monitoring continu du rendering SEO.

Rendering21 février 2026

Dynamic rendering : solution temporaire ou piège SEO

Avantages, limites et alternatives au dynamic rendering. Pourquoi cette approche recommandée par Google devient un risque technique à long terme.

Rendering20 février 2026

Prerendering pour le SEO : quand l'utiliser et comment l'implémenter

Guide technique du prerendering pour le SEO : cas d'usage concrets, implémentation avec Next.js, Nuxt et Prerender.io, et pièges à éviter sur les SPA.