JavaScript SEO : ce que Googlebot peut et ne peut pas crawler

Un e-commerce de 22 000 pages produits migre vers une SPA React. Trois mois plus tard, 60 % des pages produits disparaissent de l'index Google. Le trafic organique chute de 41 %. L'équipe technique n'a rien changé côté contenu — le problème est entièrement lié au rendering JavaScript que Googlebot n'a jamais terminé.

Ce scénario n'est pas fictif. Il se reproduit à chaque cycle de migration front-end, parce que la plupart des équipes surestiment les capacités de rendering de Googlebot et sous-estiment les contraintes de sa file d'attente.

L'architecture de crawl en deux passes de Google

Googlebot ne fonctionne pas comme un navigateur classique. Le crawl d'une page JavaScript se déroule en deux phases distinctes, et c'est cette architecture qui crée l'essentiel des problèmes.

Première passe : le crawl HTML brut

Googlebot envoie une requête HTTP classique et reçoit le HTML initial — exactement ce que curl retournerait. À ce stade, il extrait :

  • Les liens <a href> présents dans le HTML statique
  • Les balises <meta> (title, description, robots, canonical)
  • Le contenu textuel déjà présent dans le DOM initial
  • Les directives du <head> (hreflang, données structurées, etc.)

Si votre page retourne un squelette HTML vide avec un <div id="root"></div> et que tout le contenu est injecté par JavaScript, cette première passe ne capture quasiment rien d'exploitable.

Deuxième passe : le Web Rendering Service (WRS)

Google place ensuite la page dans une file d'attente de rendering. Le WRS (Web Rendering Service) exécute le JavaScript avec un Chromium headless — actuellement basé sur une version evergreen de Chrome — et capture le DOM final après exécution.

Le problème : cette file d'attente n'est pas immédiate. Google a confirmé lors de Google I/O 2019 que le délai entre la première et la deuxième passe pouvait aller de quelques secondes à plusieurs jours, selon la charge de la queue et le crawl budget alloué au site. Martin Splitt (Developer Advocate chez Google) a détaillé ce mécanisme dans la série JavaScript SEO de la documentation officielle.

Concrètement, une page peut être indexée avec son contenu HTML statique pendant des jours avant que le rendering JavaScript ne soit exécuté. Si vos balises <title>, <meta description>, ou votre contenu principal n'existent que dans le DOM post-rendering, vous avez une fenêtre d'indexation aveugle.

<!-- Ce que Googlebot voit en première passe sur une SPA React classique -->
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8" />
  <title>Mon App</title> <!-- Title générique, pas celui de la page produit -->
  <meta name="description" content="" /> <!-- Vide -->
  <link rel="canonical" href="https://shop.example.fr/" /> <!-- Canonical erronée vers la home -->
</head>
<body>
  <div id="root"></div>
  <script src="/static/js/main.a1b2c3.js"></script>
</body>
</html>

Dans cet exemple, la première passe indexe un title générique, une meta description vide, et une canonical qui pointe vers la homepage. Même si le WRS finit par exécuter le JavaScript, le dégât initial est fait : Google a potentiellement consolidé des signaux erronés.

Ce que Googlebot rend correctement (et les limites réelles)

Le WRS basé sur Chromium est techniquement capable d'exécuter la majorité du JavaScript moderne. Mais "techniquement capable" ne signifie pas "systématiquement exécuté".

Ce qui fonctionne

  • Frameworks majeurs : React, Vue, Angular, Svelte — le rendering fonctionne si le code ne dépend pas d'interactions utilisateur.
  • ES6+ et modules : le WRS supporte les features modernes de JavaScript. Pas besoin de transpiler vers ES5 pour Googlebot.
  • Fetch API et XHR : les appels réseau côté client sont exécutés par le WRS. Si votre composant fait un fetch('/api/products/123') au mount, Googlebot le verra — à condition que l'API réponde dans les temps.
  • Web Components : le Shadow DOM est supporté, y compris les custom elements.

Ce qui échoue silencieusement

C'est ici que les équipes techniques se font piéger, parce que ces échecs ne génèrent pas d'erreur visible dans la Search Console.

1. Contenu conditionné à une interaction utilisateur

Googlebot ne clique pas, ne scrolle pas, ne survole pas. Tout contenu affiché via un onClick, onScroll, ou IntersectionObserver déclenché par le scroll reste invisible.

// Ce contenu ne sera JAMAIS vu par Googlebot
document.querySelector('.load-more').addEventListener('click', () => {
  fetch('/api/products?page=2')
    .then(res => res.json())
    .then(data => renderProducts(data));
});

// Ce contenu non plus — IntersectionObserver ne se déclenche pas sans scroll
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadDeferredContent(entry.target);
    }
  });
});
document.querySelectorAll('.lazy-section').forEach(el => observer.observe(el));

Ce point est critique pour les pages à pagination infinie ou les mécanismes de lazy loading. Si votre catalogue produit charge les items suivants au scroll, Googlebot ne verra que le premier lot.

2. Timeouts et APIs lentes

Le WRS a un budget temps limité. La documentation officielle ne donne pas de chiffre exact, mais les tests empiriques de la communauté (notamment ceux de Bartosz Góralewicz et Onely) convergent vers un budget d'environ 5 secondes pour le rendering initial. Si vos appels API prennent 3 secondes et que le rendering du composant ajoute 2 secondes, vous êtes à la limite.

// Pattern dangereux : chaîne de fetches séquentiels
async function ProductPage({ id }) {
  const product = await fetch(`/api/products/${id}`);        // 800ms
  const reviews = await fetch(`/api/reviews/${id}`);          // 1200ms
  const recommendations = await fetch(`/api/reco/${id}`);     // 600ms
  const availability = await fetch(`/api/stock/${id}`);       // 400ms
  // Total : ~3s rien que pour les données, avant le rendering React
  // Googlebot risque de capturer un état intermédiaire ou un skeleton
}

3. Service Workers

Le WRS n'exécute pas les Service Workers. Si votre site repose sur un Service Worker pour pré-cacher des routes ou servir du contenu offline-first, Googlebot ne verra rien de ce que le Service Worker aurait dû fournir.

4. Consentement et modales bloquantes

Les banners de consentement RGPD qui bloquent le rendering du contenu principal posent un problème direct. Si le contenu est derrière un overlay CSS qui dépend d'une acceptation, le WRS verra la modale, pas le contenu.

5. Authentification et sessions

Googlebot ne gère pas les cookies de session de manière persistante. Chaque requête est stateless. Tout contenu derrière un localStorage.getItem('authToken') ou un cookie de session est invisible.

Le scénario concret : migration SPA et chute d'indexation

Prenons un cas réaliste. ShopTech, un e-commerce B2B avec 15 000 pages produits, 2 000 pages catégories et 800 articles de blog, migre de Symfony avec rendu serveur vers une SPA Next.js en mode CSR (Client-Side Rendering) pur — une erreur classique quand l'équipe front impose le stack sans consulter l'équipe SEO.

Avant migration

  • 14 200 pages indexées (95 % du catalogue)
  • Trafic organique : 180 000 sessions/mois
  • Temps de crawl moyen par page : 200ms (HTML statique)
  • Couverture Search Console : 96 % de pages valides

Après migration (semaine 3)

  • Pages indexées : 5 800 (chute de 59 %)
  • Trafic organique : 108 000 sessions/mois (-40 %)
  • Erreurs Search Console : 3 200 pages "Crawlée, actuellement non indexée"
  • Le rapport de couverture montre des milliers de pages dont le <title> est "ShopTech" (le title par défaut du index.html)

Diagnostic

En inspectant les pages via l'outil d'inspection d'URL de la Search Console, l'équipe constate que :

  1. Le HTML capturé par Google est le squelette SPA sans contenu
  2. La capture d'écran du rendering montre un spinner de chargement — le WRS a timeout avant la fin du rendering
  3. Les données structurées Product ne sont plus détectées (elles étaient injectées par JavaScript après le chargement des données API)
  4. Les canonicals pointent toutes vers la même URL racine

L'outil d'inspection d'URL de la Search Console est le premier réflexe ici. L'onglet "HTML rendu" montre exactement ce que le WRS a capturé. Screaming Frog en mode JavaScript rendering (via son intégration Chromium) permet de vérifier l'ensemble du site à grande échelle pour identifier combien de pages souffrent du même problème.

Stratégies de rendering : SSR, SSG, ISR et leurs trade-offs

La solution à ces problèmes n'est pas "passer tout en SSR" — c'est plus nuancé que cela. Chaque stratégie de rendering a des implications différentes selon le profil du site.

Server-Side Rendering (SSR)

Le serveur exécute le JavaScript et retourne du HTML complet. Googlebot reçoit le contenu dès la première passe — pas besoin d'attendre le WRS.

// next.config.js — forcer le SSR sur les pages produits
// Next.js App Router (Route Handlers)

// app/product/[slug]/page.tsx
export const dynamic = 'force-dynamic'; // Force SSR, pas de cache statique

export default async function ProductPage({ params }) {
  const product = await fetch(`https://api.shoptech.fr/products/${params.slug}`, {
    next: { revalidate: 0 } // Pas de cache — SSR pur
  });
  const data = await product.json();

  return (
    <>
      <head>
        <title>{data.name} — ShopTech</title>
        <meta name="description" content={data.metaDescription} />
        <link rel="canonical" href={`https://shoptech.fr/product/${params.slug}`} />
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{
            __html: JSON.stringify({
              "@context": "https://schema.org",
              "@type": "Product",
              "name": data.name,
              "description": data.description,
              "offers": {
                "@type": "Offer",
                "price": data.price,
                "priceCurrency": "EUR"
              }
            })
          }}
        />
      </head>
      <ProductDetail product={data} />
    </>
  );
}

Trade-off : le SSR pur consomme du CPU serveur à chaque requête. Pour 15 000 pages produits avec un crawl Google soutenu, le coût d'infrastructure augmente significativement. Et le Time to First Byte (TTFB) peut se dégrader si le serveur est sous charge.

Static Site Generation (SSG)

Les pages sont pré-rendues au build. HTML complet, TTFB minimal, parfait pour le SEO.

Trade-off : avec 15 000 pages, le temps de build explose. Une modification de prix nécessite un rebuild complet (ou partiel via ISR). Inenvisageable pour un catalogue qui change plusieurs fois par jour.

Incremental Static Regeneration (ISR)

Le compromis le plus pertinent pour les gros catalogues. Les pages sont générées statiquement à la première requête, puis revalidées à un intervalle défini.

// app/product/[slug]/page.tsx avec ISR
export const revalidate = 3600; // Revalidation toutes les heures

export async function generateStaticParams() {
  // Pré-générer les 500 produits les plus populaires au build
  const topProducts = await fetch('https://api.shoptech.fr/products/top500');
  const data = await topProducts.json();
  return data.map((product: { slug: string }) => ({
    slug: product.slug,
  }));
}

export default async function ProductPage({ params }) {
  const product = await fetch(
    `https://api.shoptech.fr/products/${params.slug}`
  );
  // ... rendering identique au SSR
}

Ici, les 500 produits à plus fort trafic sont pré-générés (SSG), et les 14 500 restants sont générés à la demande (SSR au premier accès) puis mis en cache pendant 1 heure (ISR). Googlebot recevra toujours du HTML complet.

Dynamic Rendering (le cas particulier)

Google a longtemps toléré le dynamic rendering — servir du HTML pré-rendu à Googlebot et une SPA aux utilisateurs. Cette approche est désormais considérée comme un workaround temporaire par Google, pas une solution pérenne. Si vous l'utilisez encore, planifiez votre migration vers SSR/ISR.

Diagnostiquer les problèmes de rendering à grande échelle

Attendre que le trafic chute pour identifier un problème de rendering JavaScript est un luxe que personne ne peut se permettre. Voici la stack de diagnostic.

Search Console : l'outil d'inspection d'URL

L'onglet "Test en direct" de l'outil d'inspection d'URL exécute le rendering en temps réel et montre :

  • Le HTML brut (première passe)
  • Le HTML rendu (après exécution JS)
  • Une capture d'écran du résultat visuel
  • Les erreurs JavaScript dans la console

Si le HTML brut et le HTML rendu divergent significativement, vous avez un problème de dépendance au rendering client.

Pour automatiser ce diagnostic sur un grand nombre d'URLs, l'API URL Inspection permet d'interroger programmatiquement l'état d'indexation et de rendering de vos pages.

Screaming Frog en mode JavaScript

Screaming Frog peut crawler en mode JavaScript (Configuration > Spider > Rendering > JavaScript). Cela utilise un Chromium embarqué qui simule le WRS. Comparez les résultats d'un crawl HTML-only et d'un crawl JavaScript pour identifier les écarts.

Configuration recommandée pour un audit JavaScript SEO :

  • JavaScript Rendering : activé
  • AJAX Timeout : 5 secondes (pour simuler la contrainte du WRS)
  • Allow Cookies : désactivé (Googlebot est stateless)
  • Crawl Budget : limitez à 2 000-5 000 URLs par crawl pour ne pas surcharger votre serveur

Chrome DevTools : simuler Googlebot manuellement

Pour une page spécifique, ouvrez Chrome DevTools, allez dans l'onglet Network, et cochez "Disable cache". Rechargez la page. Observez la cascade de requêtes et identifiez :

  • Les appels API qui prennent plus de 2 secondes
  • Les ressources bloquées (403, 404, CORS)
  • Le moment exact où le contenu principal apparaît dans le DOM

L'onglet Performance de DevTools donne un timeline précis du rendering. Si votre LCP dépasse 4 secondes, le WRS risque de capturer un état incomplet.

Vérification serveur : s'assurer que Googlebot peut accéder aux ressources

Un piège fréquent : le robots.txt bloque les fichiers JavaScript ou les endpoints API. Si Googlebot ne peut pas télécharger main.js ou accéder à /api/products/, le rendering échoue silencieusement.

# nginx.conf — s'assurer que les assets JS/CSS ne sont PAS bloqués
# Vérifiez aussi votre robots.txt

# Mauvais : bloquer /api/ dans robots.txt empêche le rendering
# Disallow: /api/   ← NE FAITES PAS ÇA si vos pages dépendent de ces endpoints

# Bon : autoriser les ressources critiques pour le rendering
location /api/ {
    # Headers CORS pour les appels client-side
    add_header Access-Control-Allow-Origin "https://shoptech.fr";
    add_header Access-Control-Allow-Methods "GET, OPTIONS";

    # Cache léger pour réduire la charge lors du crawl Google
    add_header Cache-Control "public, max-age=300"; # 5 min

    proxy_pass http://api_backend;
}

location /static/ {
    # Assets JS/CSS : cache longue durée, jamais bloqués
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri =404;
}

Vérifiez votre robots.txt pour vous assurer qu'aucune directive Disallow ne bloque les chemins nécessaires au rendering. Le testeur de robots.txt dans la Search Console permet de vérifier ça rapidement.

Checklist technique : sécuriser l'indexation JavaScript

Les meta critiques dans le HTML statique

Ne déléguez jamais ces éléments au rendering client :

  • <title> — doit être dans le HTML initial
  • <meta name="description"> — idem
  • <link rel="canonical"> — une canonical absente ou erronée en première passe crée des signaux contradictoires
  • <meta name="robots"> — une directive noindex injectée par JavaScript pourrait ne pas être vue à temps, ou au contraire être vue trop tard
  • Balises hreflang — doivent être dans le <head> statique
  • Données structurées JSON-LD — idéalement dans le HTML initial, même si Google peut les capter au rendering

Le routing côté client

Les SPA utilisent typiquement l'API History (pushState) pour gérer les URLs. Googlebot supporte pushState, mais chaque route doit être accessible directement par URL. Si https://shoptech.fr/product/chaise-ergonomique retourne une 404 ou une page vide quand elle est accédée directement (sans passer par la navigation interne de la SPA), Googlebot ne pourra pas l'indexer.

Testez avec curl :

# Le test le plus basique : est-ce que l'URL retourne du contenu sans JS ?
curl -s -o /dev/null -w "%{http_code}" https://shoptech.fr/product/chaise-ergonomique
# Doit retourner 200

# Vérifier le contenu HTML brut
curl -s https://shoptech.fr/product/chaise-ergonomique | grep -i "<title>"
# Doit retourner le title spécifique du produit, pas le title par défaut

# Vérifier la présence du canonical
curl -s https://shoptech.fr/product/chaise-ergonomique | grep -i "canonical"
# Doit retourner le bon canonical

# Simuler le User-Agent de Googlebot pour détecter un éventuel cloaking involontaire
curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://shoptech.fr/product/chaise-ergonomique | grep -i "<title>"

Si l'une de ces vérifications échoue, votre site a un problème fondamental de rendering côté serveur.

Les erreurs JavaScript qui tuent l'indexation

Une erreur JavaScript non catchée peut interrompre tout le rendering. Le WRS abandonne si le script principal plante. Les coupables habituels :

  • Variables d'environnement manquantes : process.env.API_URL est undefined en production, l'appel fetch échoue
  • APIs navigateur absentes : window.matchMedia, navigator.geolocation — le WRS est headless, certaines APIs retournent des valeurs inattendues
  • Race conditions : le composant tente de lire des données qui ne sont pas encore chargées

Un monitoring JavaScript qui détecte ces régressions avant que Googlebot ne les rencontre évite des semaines de perte d'indexation. Un outil comme Seogard détecte automatiquement quand des balises meta disparaissent ou quand le contenu rendu diverge du contenu attendu — exactement le type de régression silencieuse que provoque un bug JavaScript en production.

Au-delà du rendering : l'impact sur le crawl budget

Le rendering JavaScript a un coût direct sur votre crawl budget. Chaque page qui nécessite un passage par le WRS consomme des ressources supplémentaires côté Google. Pour un site de 15 000 pages, si Google doit render chaque page individuellement, le taux de crawl effectif diminue.

Le rapport "Statistiques d'exploration" de la Search Console montre le temps moyen de réponse vu par Googlebot. Si ce temps augmente après une migration vers un framework JavaScript lourd, c'est un signal que le rendering coûte cher en crawl budget.

Les pages qui retournent du HTML complet (SSR/SSG) sont crawlées et indexées en une seule passe. Pas de file d'attente du WRS, pas de délai de rendering. Pour les sites de grande taille, cette différence se traduit directement en vitesse d'indexation des nouveaux contenus et des mises à jour.

Un sitemap XML à jour aide Googlebot à prioriser le crawl, mais ne résout pas le problème fondamental du rendering. Si Googlebot crawle la page mais que le rendering échoue, la page apparaîtra dans le rapport "Crawlée, actuellement non indexée" — un signal classique de problème JavaScript.

Les images chargées en lazy loading et les optimisations de performance jouent aussi un rôle : un JavaScript lourd qui dégrade le LCP et le CLS impacte à la fois l'expérience utilisateur et la capacité de Googlebot à capturer un DOM stable.


Le rendering JavaScript par Googlebot fonctionne — mais dans un cadre contraint que la plupart des équipes techniques ne mesurent pas avant d'en subir les conséquences. La règle est simple : tout ce qui est critique pour l'indexation (titres, descriptions, canonicals, contenu principal, liens internes, données structurées) doit être dans le HTML initial. Le JavaScript peut enrichir l'expérience, pas la porter. Et pour les sites de taille significative, un monitoring continu du HTML rendu — via Seogard ou des crawls réguliers en mode JavaScript — est le seul moyen fiable de détecter les régressions avant qu'elles ne coûtent du trafic.

Articles connexes

JavaScript SEO5 avril 2026

React et SEO : pièges techniques et solutions SSR/SSG

React casse votre SEO ? SSR, SSG, hydratation, meta tags dynamiques : solutions concrètes pour rendre vos apps React indexables par Google.

JavaScript SEO5 avril 2026

Vue.js et SEO : pourquoi Nuxt est indispensable

Vue.js seul pose des problèmes majeurs d'indexation. Découvrez pourquoi Nuxt (SSR/SSG) est la solution technique et comment migrer sans perdre de trafic.

JavaScript SEO5 avril 2026

SPA et SEO : rendre une Single Page Application crawlable

Guide technique pour rendre une SPA visible par Google : routing, SSR, hydration, prerendering. Avec code, configs et scénarios concrets.