[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fvwdQ9prcTrdl6SK3Ota5nUR2IEDpJFTRcMf4esl_QG0":3,"$fDZTnUBFoFneF08Zb3scDdmahOJgV0yEaw9nx9ijl0p0":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"69d8e6d7aa6b273b0c603186","rendering-budget-de-google-combien-de-javascript-est-trop",0,"Equipe Seogard","Un site e-commerce de 22 000 pages produit, entièrement client-side rendered en React. Après 3 mois en production, Search Console montre 14 000 pages indexées. Les 8 000 restantes ? Google les a crawlées, mais jamais rendues. Le contenu injecté par JavaScript n'a jamais été vu par l'indexeur. Trafic organique perdu : estimé à 35 % du potentiel.\n\nCe scénario n'est pas hypothétique. C'est le résultat direct de ce que la communauté SEO appelle le **rendering budget** — la capacité finie de Google à exécuter du JavaScript pour rendre vos pages.\n\n## Le rendering budget : ce que Google dit (et ce qu'il ne dit pas)\n\nGoogle n'a jamais publié de documentation officielle utilisant le terme \"rendering budget\". Martin Splitt (Developer Advocate chez Google) a toutefois confirmé à de multiples reprises lors de conférences et dans les vidéos [JavaScript SEO](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) que le Web Rendering Service (WRS) dispose de **ressources limitées** et qu'il existe une **file d'attente de rendering** distincte de la file de crawl.\n\nLe pipeline d'indexation de Google fonctionne en trois étapes séquentielles :\n\n1. **Crawl** — Googlebot télécharge le HTML brut.\n2. **Rendering** — Le WRS (basé sur Chrome headless, version evergreen) exécute le JavaScript et produit le DOM rendu.\n3. **Indexation** — Le contenu du DOM rendu est traité et indexé.\n\nLe point critique : entre l'étape 1 et l'étape 2, il peut s'écouler **des secondes, des heures, ou des semaines**. Google a confirmé que le délai dépend de la charge du WRS et de la priorité assignée à l'URL. C'est ce goulet d'étranglement qui constitue le rendering budget.\n\n### Ce qui consomme le rendering budget\n\nChaque page qui nécessite une exécution JavaScript pour afficher son contenu principal consomme des ressources WRS. Plus précisément :\n\n- **Temps CPU** pour parser et exécuter les bundles JS\n- **Mémoire** pour maintenir le DOM virtuel et les objets JavaScript\n- **Requêtes réseau** pour récupérer les données via des appels API (fetch/XHR)\n- **Temps d'attente** pour les timers, les animations, les événements asynchrones\n\nGoogle a indiqué un **timeout de 5 secondes pour le rendering initial**, au-delà duquel le WRS snapshote l'état actuel du DOM et passe à la page suivante. Ce n'est pas 5 secondes de temps de chargement réseau — c'est 5 secondes d'exécution JavaScript après réception des ressources.\n\n### Crawl budget ≠ rendering budget\n\nLe crawl budget concerne le nombre de requêtes HTTP que Googlebot effectue sur votre site. Le rendering budget concerne le nombre de pages que le WRS peut effectivement rendre. Vous pouvez avoir un crawl budget excellent (toutes vos pages sont découvertes) et un rendering budget saturé (la moitié de ces pages ne sont jamais rendues à temps pour l'indexation).\n\nC'est exactement ce qui se passe sur les gros sites JavaScript : Search Console montre \"Crawled - currently not indexed\" pour des milliers de pages dont le contenu dépend entièrement du JS.\n\n## Anatomie d'une page qui tue le rendering budget\n\nPrenons un exemple réaliste. Un site média construit avec une SPA React, sans SSR, qui affiche des articles via des appels API.\n\n```html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"fr\">\n\u003Chead>\n  \u003Cmeta charset=\"UTF-8\">\n  \u003Ctitle>Mon Article\u003C/title>\n  \u003C!-- Pas de meta description dans le HTML initial -->\n  \u003C!-- Pas de contenu structuré -->\n\u003C/head>\n\u003Cbody>\n  \u003Cdiv id=\"root\">\u003C/div>\n  \u003C!-- Bundle principal : 847 KB gzippé -->\n  \u003Cscript src=\"/static/js/vendor.chunk.a1b2c3.js\">\u003C/script>  \u003C!-- 312 KB -->\n  \u003Cscript src=\"/static/js/main.chunk.d4e5f6.js\">\u003C/script>     \u003C!-- 535 KB -->\n\u003C/body>\n\u003C/html>\n```\n\nVoici ce que le WRS doit faire pour indexer cette page :\n\n1. Télécharger le HTML (\u003C 1 KB utile)\n2. Télécharger 847 KB de JavaScript compressé (~2.5 MB décompressé)\n3. Parser et compiler le JavaScript\n4. Exécuter le code React, créer le DOM virtuel\n5. Intercepter l'appel `fetch('/api/articles/12345')` et attendre la réponse\n6. Effectuer la réconciliation DOM et injecter le contenu\n7. Snapshotter le DOM final\n\nMultipliez ça par 22 000 pages. Chaque page nécessite entre 2 et 8 secondes de traitement WRS. Même si le WRS cache les bundles JS entre les pages d'un même site (ce que Google a confirmé), les appels API et l'exécution spécifique à chaque page restent incompressibles.\n\n### Les signaux d'alerte dans Chrome DevTools\n\nAvant même de parler de Googlebot, vous pouvez diagnostiquer les problèmes de rendering avec [Chrome DevTools](/blog/chrome-devtools-pour-le-seo-astuces-avancees). Ouvrez l'onglet Performance, désactivez le cache, et simulez un throttling CPU 4x :\n\n```\n// Dans la console DevTools, mesurez le temps avant que le contenu principal soit dans le DOM\nconst observer = new PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (entry.name === 'largest-contentful-paint') {\n      console.log(`LCP: ${entry.startTime}ms`);\n      console.log(`Element: ${entry.element?.tagName} — ${entry.element?.textContent?.substring(0, 80)}`);\n    }\n  }\n});\nobserver.observe({ type: 'largest-contentful-paint', buffered: true });\n```\n\nSi le LCP dépasse 4 secondes avec un CPU throttled 4x, vous êtes dans la zone de danger pour le WRS. Le WRS tourne sur une infrastructure puissante, mais il traite des millions de pages par jour — chaque milliseconde compte dans le bilan global.\n\nUn indicateur encore plus révélateur : comparez le HTML source (`view-source:`) avec le DOM rendu (`document.documentElement.outerHTML` dans la console). Si plus de 50 % du contenu textuel de la page n'existe que dans le DOM rendu, vous dépendez entièrement du rendering budget.\n\n## Quantifier le problème : le scénario à 22 000 pages\n\nRevenons à notre site e-commerce React. Voici les chiffres réels d'un audit post-migration.\n\n**Avant** (site PHP server-rendered) :\n- 22 000 pages produit\n- 21 400 indexées (97 %)\n- Temps moyen de crawl dans Search Console : 180 ms\n- Trafic organique : ~45 000 visites/jour\n\n**Après** (migration React SPA, 3 mois plus tard) :\n- 22 000 pages produit\n- 14 200 indexées (64 %)\n- Temps moyen de crawl dans Search Console : 1 200 ms (le HTML + attente des ressources)\n- Trafic organique : ~29 000 visites/jour (−35 %)\n\nLes rapports [Search Console](/blog/google-search-console-les-rapports-que-vous-ignorez) montraient une augmentation massive des pages en statut \"Discovered - currently not indexed\" et \"Crawled - currently not indexed\". Le rapport de couverture est votre premier outil de diagnostic : si ces deux catégories explosent après une migration JS, le rendering budget est votre suspect numéro un.\n\n### Comment identifier les pages non rendues\n\nUtilisez le rapport de couverture d'indexation dans Search Console, mais croisez-le avec un crawl JavaScript via Screaming Frog. Configurez Screaming Frog en mode \"JavaScript rendering\" avec Chrome headless :\n\n```\n# Configuration Screaming Frog pour crawl JS\n# Configuration > Spider > Rendering > JavaScript\n# Rendering Engine: Chromium\n# JavaScript Execution Time: 10 secondes (pour voir ce que Google pourrait manquer à 5s)\n# AJAX Timeout: 5 secondes\n# Block Resources: aucun (pour reproduire le comportement WRS)\n\n# Ensuite, exportez et comparez :\n# 1. Crawl en mode \"HTML only\" → contenu extrait du HTML brut\n# 2. Crawl en mode \"JavaScript\" → contenu extrait du DOM rendu\n# 3. Différentiel = contenu dépendant à 100% du rendering\n```\n\nExportez les deux crawls. Les pages où le title, la meta description, ou le contenu H1 diffèrent entre HTML et DOM rendu sont celles qui dépendent du rendering budget. Si ce différentiel concerne plus de 30 % de vos pages, vous avez un problème structurel.\n\n## Les seuils techniques : où se situe la limite\n\nGoogle n'a pas publié de limites chiffrées officielles pour le rendering budget. Mais en croisant les déclarations de l'équipe Google Search, les observations empiriques à grande échelle, et les tests reproductibles, on peut établir des seuils de risque pragmatiques.\n\n### Taille des bundles JavaScript\n\nLe WRS met en cache les ressources statiques entre les crawls d'un même site. Votre `vendor.chunk.js` ne sera pas re-téléchargé à chaque page. Mais il sera re-parsé et partiellement re-exécuté. La taille décompressée du JavaScript total est le metric qui compte :\n\n- **\u003C 500 KB décompressé** : zone verte. Le WRS gère sans difficulté.\n- **500 KB - 1.5 MB** : zone orange. Le rendering fonctionne sur la plupart des pages, mais les pages de faible priorité peuvent être dépriorisées dans la queue.\n- **> 1.5 MB** : zone rouge. Le temps d'exécution dépasse régulièrement les seuils du WRS. Attendez-vous à des problèmes d'indexation sur les pages non maillées.\n\nCes seuils concernent le JavaScript **nécessaire au rendering du contenu principal**. Si vous chargez 2 MB de JS mais que votre contenu est dans le HTML initial et que le JS ne fait qu'ajouter de l'interactivité, le rendering budget n'est pas impacté.\n\n### Nombre d'appels API nécessaires au rendering\n\nChaque `fetch()` ou `XMLHttpRequest` que le WRS doit attendre avant de snapshotter le DOM ajoute de la latence. Le WRS suit les redirections, respecte les cookies de session, mais il a un timeout global.\n\n- **0 appel API** : le contenu est dans le HTML ou inline dans un tag `\u003Cscript>`. Idéal.\n- **1-2 appels API** rapides (\u003C 200 ms chacun) : gérable.\n- **3+ appels API en cascade** (waterfall) : danger. Si l'appel B dépend de la réponse de A, le temps total explose.\n\n### Cas particulier : les appels API qui échouent silencieusement\n\nLe piège le plus vicieux. Votre composant React fait un `fetch('/api/product/12345')`. En production, l'API répond en 80 ms. Mais quand le WRS fait la même requête :\n\n- L'API peut rate-limiter le User-Agent de Googlebot\n- L'API peut exiger un token d'authentification absent\n- L'API peut être derrière un WAF qui bloque les IPs de Google\n- L'API peut timeout sous la charge du crawl\n\nDans tous ces cas, le composant React affiche un spinner ou un état vide. Le WRS snapshotte ce DOM dégradé. La page est indexée sans contenu.\n\nVérifiez vos access logs pour le User-Agent `Googlebot` et filtrez les requêtes vers vos endpoints API :\n\n```bash\n# Identifier les requêtes API de Googlebot et leur code de réponse\ngrep -i \"googlebot\" /var/log/nginx/access.log \\\n  | grep \"/api/\" \\\n  | awk '{print $7, $9}' \\\n  | sort | uniq -c | sort -rn | head -30\n\n# Résultat typique qui révèle un problème :\n#   4521 /api/products/   200\n#    892 /api/products/   429   ← rate limiting !\n#    341 /api/products/   403   ← WAF ou auth\n#     67 /api/products/   504   ← timeout\n```\n\nSi vous voyez des 429 ou 403 sur vos endpoints API pour Googlebot, vous avez trouvé pourquoi des pages ne s'indexent pas. La solution immédiate : whitelistez les ranges IP de Googlebot (vérifiables via [la documentation officielle Google](https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot)) au niveau de votre WAF et de votre rate limiter.\n\n## Stratégies pour réduire la pression sur le rendering budget\n\n### SSR ou SSG : la solution structurelle\n\nLe rendering côté serveur (SSR) ou la génération statique (SSG) éliminent le problème à la racine. Si le HTML envoyé au client contient déjà le contenu indexable, le WRS n'a rien à rendre — Google indexe directement depuis l'étape 1 du pipeline.\n\nSi vous êtes sur React, Next.js avec `getServerSideProps` ou `getStaticProps` résout le problème. Si vous êtes sur Vue, Nuxt fait le même travail. L'article sur le [changement de framework sans perte SEO](/blog/changer-de-framework-next-js-vers-nuxt-ou-l-inverse-sans-perte-seo) couvre les risques de migration.\n\nUn point souvent négligé : le SSR partiel. Vous n'avez pas besoin de rendre **toute** la page côté serveur. Seul le contenu critique pour le SEO doit être dans le HTML initial :\n\n- Title et meta description\n- H1, contenu textuel principal, images avec alt\n- Données structurées (JSON-LD)\n- Liens internes de navigation\n\nLes composants interactifs (filtres, panier, recommandations personnalisées) peuvent rester client-side sans impact SEO.\n\n```javascript\n// Exemple Next.js : SSR du contenu SEO-critique, hydratation client pour l'interactivité\n// pages/products/[slug].tsx\n\nimport { GetServerSideProps } from 'next';\nimport dynamic from 'next/dynamic';\n\n// Composants lourds chargés côté client uniquement\nconst ProductRecommendations = dynamic(\n  () => import('@/components/ProductRecommendations'),\n  { ssr: false }  // Ne sera PAS rendu côté serveur\n);\n\nconst ProductFilters = dynamic(\n  () => import('@/components/ProductFilters'),\n  { ssr: false }\n);\n\ninterface ProductPageProps {\n  product: {\n    name: string;\n    description: string;\n    price: number;\n    images: { url: string; alt: string }[];\n    structuredData: object;\n  };\n}\n\nexport default function ProductPage({ product }: ProductPageProps) {\n  return (\n    \u003C>\n      {/* Ce HTML est dans la réponse serveur — Googlebot l'indexe sans rendering */}\n      \u003Ch1>{product.name}\u003C/h1>\n      \u003Cdiv className=\"product-description\"\n           dangerouslySetInnerHTML={{ __html: product.description }} />\n      \u003Cscript type=\"application/ld+json\"\n              dangerouslySetInnerHTML={{ __html: JSON.stringify(product.structuredData) }} />\n\n      {/* Ces composants ne sont PAS dans le HTML serveur — pas de pression sur le WRS */}\n      \u003CProductRecommendations productId={product.id} />\n      \u003CProductFilters category={product.category} />\n    \u003C/>\n  );\n}\n\nexport const getServerSideProps: GetServerSideProps = async ({ params }) => {\n  const product = await fetchProductFromDB(params.slug as string);\n  return { props: { product } };\n};\n```\n\nCette approche hybride est le sweet spot : le WRS voit le contenu principal dans le HTML, n'a rien à rendre, et les composants `ssr: false` ne consomment aucune ressource rendering.\n\n### Dynamic rendering : le pansement acceptable\n\nSi une réécriture SSR n'est pas envisageable à court terme, le dynamic rendering reste une option. Google a explicitement déclaré que le dynamic rendering n'est [pas du cloaking](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) quand il sert le même contenu sous une forme différente.\n\nLe principe : détecter le User-Agent de Googlebot au niveau du reverse proxy et lui servir une version pré-rendue de la page. Les outils comme Rendertron (deprecated mais encore utilisé) ou Prerender.io font ce travail.\n\nLe trade-off : vous maintenez deux pipelines de rendering. Le contenu peut dériver entre la version utilisateur et la version bot si vous ne surveillez pas. Un outil de monitoring comme Seogard peut détecter ces dérives en comparant le DOM vu par un crawler JS et le HTML servi au bot.\n\n### Code splitting agressif et tree shaking\n\nMême avec du SSR, réduire la taille du JavaScript reste bénéfique pour le rendering budget. Le WRS parse et exécute le JS même quand le contenu est déjà dans le HTML — ne serait-ce que pour l'hydratation.\n\nAnalysez vos bundles avec `webpack-bundle-analyzer` ou l'équivalent Vite/Rollup. Cherchez :\n\n- Les polyfills inutiles (le WRS est Chrome evergreen, il supporte tout ES2024)\n- Les bibliothèques importées entièrement alors que vous n'utilisez qu'une fonction (`lodash` vs `lodash-es`)\n- Les dépendances de développement qui se retrouvent dans le bundle de production\n\n```bash\n# Analyser la composition de vos bundles Next.js\nANALYZE=true next build\n\n# Pour Vite / Rollup\nnpx vite-bundle-visualizer\n\n# Identifier les modules les plus lourds\n# Cible : aucun chunk > 150 KB gzippé pour le critical path\n```\n\n## Monitoring continu : détecter la régression avant Google\n\nLe rendering budget n'est pas un problème que vous réglez une fois. Chaque déploiement peut introduire une régression : un nouveau package qui double la taille du bundle, un appel API ajouté dans le critical path, un middleware qui bloque les requêtes du WRS.\n\n### Intégrer les checks dans le CI/CD\n\nLa première ligne de défense est dans votre pipeline de déploiement. [Automatiser les checks SEO dans le CI/CD](/blog/automatiser-les-checks-seo-dans-le-ci-cd) vous permet de bloquer un merge qui dégrade la renderabilité.\n\nLes checks essentiels :\n\n- **Taille maximale du bundle JS** : échouez le build si le bundle principal dépasse un seuil (par exemple 200 KB gzippé)\n- **Présence du contenu critique dans le HTML initial** : un test qui vérifie que le H1, la meta description et le JSON-LD sont dans la réponse HTTP sans exécuter de JS\n- **Temps de rendering** : un test Lighthouse CI qui vérifie que le LCP est sous 2.5s en simulation mobile\n\n### Surveiller les signaux dans Search Console\n\nLe rapport \"Statistiques d'exploration\" dans Search Console donne le temps de réponse moyen vu par Googlebot. Si ce temps augmente brutalement, le WRS passe plus de temps sur vos pages — ce qui peut indiquer des bundles JS plus lourds ou des API plus lentes.\n\nCroisez ce rapport avec les données de couverture. L'[API Search Console](/blog/search-console-api-automatiser-le-reporting-seo) permet d'automatiser cette surveillance :\n\n```python\n# Surveiller l'évolution du ratio pages indexées / pages découvertes\n# via l'API Search Console (URL Inspection API en batch)\n\nimport datetime\nfrom google.oauth2 import service_account\nfrom googleapiclient.discovery import build\n\ncredentials = service_account.Credentials.from_service_account_file(\n    'service-account.json',\n    scopes=['https://www.googleapis.com/auth/webmasters.readonly']\n)\n\nservice = build('searchconsole', 'v1', credentials=credentials)\n\n# Récupérer les stats d'exploration sur les 90 derniers jours\n# Endpoint non documenté publiquement — utilisez l'export CSV \n# du rapport Statistiques d'exploration comme alternative\n\n# Alternative plus fiable : comparer le nombre de pages\n# dans le sitemap vs pages indexées via l'API Sitemaps\nsitemaps = service.sitemaps().list(\n    siteUrl='https://www.votre-ecommerce.fr'\n).execute()\n\nfor sitemap in sitemaps.get('sitemap', []):\n    submitted = sitemap.get('contents', [{}])[0].get('submitted', 0)\n    indexed = sitemap.get('contents', [{}])[0].get('indexed', 0)\n    ratio = (int(indexed) / int(submitted) * 100) if int(submitted) > 0 else 0\n    print(f\"{sitemap['path']}: {indexed}/{submitted} indexées ({ratio:.1f}%)\")\n    \n    # Alerte si le ratio descend sous 85%\n    if ratio \u003C 85:\n        print(f\"  ⚠ ALERTE: ratio d'indexation critique pour {sitemap['path']}\")\n```\n\nUn ratio pages indexées / pages soumises qui descend sous 85 % sur un site qui servait du contenu SSR auparavant est un signal fort de problème de rendering budget.\n\n### Les métriques à suivre dans la durée\n\nMettez en place un dashboard qui suit ces [KPIs](/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre) sur une base hebdomadaire :\n\n- **Taux d'indexation par type de page** (catégorie, produit, article) — segmenter est crucial, le problème peut toucher un template et pas un autre\n- **Taille moyenne du bundle JS par template** — via votre build system\n- **Temps de réponse Googlebot** — via Search Console\n- **Nombre de pages \"Crawled - currently not indexed\"** — l'indicateur le plus direct du rendering budget saturé\n- **Delta contenu HTML vs DOM rendu** — un crawl Screaming Frog JS mensuel comparé au crawl HTML only\n\n## Edge cases et nuances\n\n### Les sites qui n'ont PAS de problème de rendering budget\n\nLe rendering budget n'est pas un sujet universel. Si votre site est un WordPress classique avec quelques scripts jQuery, le WRS n'a quasiment rien à rendre. Le problème concerne spécifiquement :\n\n- Les SPA (Single Page Applications) sans SSR\n- Les sites qui chargent leur contenu principal via des appels API côté client\n- Les sites avec des frameworks JS lourds (Angular universel mal configuré, React sans Next.js/Remix)\n- Les sites avec du lazy loading JavaScript sur le contenu above-the-fold\n\nUn site e-commerce sur Shopify, par exemple, n'a généralement pas de problème de rendering budget — le contenu produit est dans le HTML serveur. Le problème vient quand des apps tierces injectent du contenu critique via JavaScript.\n\n### Le rendering budget varie selon l'autorité du site\n\nGoogle n'alloue pas les mêmes ressources WRS à tous les sites. Un site avec une forte autorité (backlinks de qualité, historique de crawl long, contenu frais fréquent) obtiendra plus de ressources rendering qu'un site récent avec peu de signaux. C'est le même mécanisme que le crawl budget qui dépend du \"crawl demand\" et du \"crawl rate limit\".\n\nEn pratique, cela signifie qu'un site d'autorité moyenne peut migrer vers du full client-side rendering et ne voir les problèmes apparaître que 2-3 mois plus tard, quand Google réduit progressivement ses tentatives de rendering sur les pages qui ne convertissent pas en résultats de recherche.\n\n### L'impact des bots IA sur votre budget de ressources serveur\n\nUn aspect connexe mais distinct : les [bots IA qui crawlent votre site](/blog/llms-et-crawl-comment-les-bots-ia-crawlent-votre-site) ne font généralement pas de rendering JavaScript. Ils récupèrent le HTML brut. Si votre contenu n'est pas dans le HTML, vous êtes invisible à la fois pour Google (rendering budget) et pour les moteurs de réponse IA (pas de rendering du tout). Double peine.\n\n### Le headless CMS mal configuré\n\nLes architectures [headless CMS](/blog/headless-cms-et-seo-avantages-et-risques-techniques) sont un terrain fertile pour les problèmes de rendering budget. Le CMS fournit le contenu via API, le frontend le rend en JavaScript. Si le frontend est un SPA sans SSR, chaque page impose au WRS un cycle complet de rendering + appel API.\n\nLa solution n'est pas de revenir à un CMS monolithique. C'est de s'assurer que la couche de présentation fait du SSR ou de la SSG, et que les appels API vers le CMS headless sont faits côté serveur au moment du build ou de la requête, pas côté client dans le navigateur.\n\n## Le rendering budget en résumé opérationnel\n\nLe rendering budget est une contrainte réelle mais invisible. Google ne vous enverra jamais d'alerte \"rendering budget dépassé\". Vous le détecterez uniquement par ses symptômes : baisse du taux d'indexation, hausse des pages \"crawled not indexed\", contenu manquant dans le cache Google.\n\nLa règle d'or : **tout contenu que vous voulez indexer doit être dans le HTML initial**. Le JavaScript doit servir l'interactivité, pas le contenu. Si cette règle est respectée, le rendering budget n'est plus un problème — il devient un non-sujet.\n\nPour les sites où une refonte SSR est impossible à court terme, le monitoring continu est la seule protection. Un outil comme Seogard qui compare périodiquement le HTML brut au DOM rendu détecte les dérives avant qu'elles n'impactent l'indexation. Couplé à des alertes sur le taux d'indexation Search Console, vous avez un filet de sécurité contre la régression silencieuse que représente un rendering budget saturé.\n\n```","https://seogard.io/blog/rendering-budget-de-google-combien-de-javascript-est-trop","Avancé","2026-04-10T12:02:31.965Z","2026-04-10","Analyse technique des limites de rendering JavaScript de Googlebot : seuils, mesures concrètes et stratégies pour garder vos pages indexables.","\u003Cp>Un site e-commerce de 22 000 pages produit, entièrement client-side rendered en React. Après 3 mois en production, Search Console montre 14 000 pages indexées. Les 8 000 restantes ? Google les a crawlées, mais jamais rendues. Le contenu injecté par JavaScript n'a jamais été vu par l'indexeur. Trafic organique perdu : estimé à 35 % du potentiel.\u003C/p>\n\u003Cp>Ce scénario n'est pas hypothétique. C'est le résultat direct de ce que la communauté SEO appelle le \u003Cstrong>rendering budget\u003C/strong> — la capacité finie de Google à exécuter du JavaScript pour rendre vos pages.\u003C/p>\n\u003Ch2>Le rendering budget : ce que Google dit (et ce qu'il ne dit pas)\u003C/h2>\n\u003Cp>Google n'a jamais publié de documentation officielle utilisant le terme \"rendering budget\". Martin Splitt (Developer Advocate chez Google) a toutefois confirmé à de multiples reprises lors de conférences et dans les vidéos \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics\">JavaScript SEO\u003C/a> que le Web Rendering Service (WRS) dispose de \u003Cstrong>ressources limitées\u003C/strong> et qu'il existe une \u003Cstrong>file d'attente de rendering\u003C/strong> distincte de la file de crawl.\u003C/p>\n\u003Cp>Le pipeline d'indexation de Google fonctionne en trois étapes séquentielles :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Crawl\u003C/strong> — Googlebot télécharge le HTML brut.\u003C/li>\n\u003Cli>\u003Cstrong>Rendering\u003C/strong> — Le WRS (basé sur Chrome headless, version evergreen) exécute le JavaScript et produit le DOM rendu.\u003C/li>\n\u003Cli>\u003Cstrong>Indexation\u003C/strong> — Le contenu du DOM rendu est traité et indexé.\u003C/li>\n\u003C/ol>\n\u003Cp>Le point critique : entre l'étape 1 et l'étape 2, il peut s'écouler \u003Cstrong>des secondes, des heures, ou des semaines\u003C/strong>. Google a confirmé que le délai dépend de la charge du WRS et de la priorité assignée à l'URL. C'est ce goulet d'étranglement qui constitue le rendering budget.\u003C/p>\n\u003Ch3>Ce qui consomme le rendering budget\u003C/h3>\n\u003Cp>Chaque page qui nécessite une exécution JavaScript pour afficher son contenu principal consomme des ressources WRS. Plus précisément :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Temps CPU\u003C/strong> pour parser et exécuter les bundles JS\u003C/li>\n\u003Cli>\u003Cstrong>Mémoire\u003C/strong> pour maintenir le DOM virtuel et les objets JavaScript\u003C/li>\n\u003Cli>\u003Cstrong>Requêtes réseau\u003C/strong> pour récupérer les données via des appels API (fetch/XHR)\u003C/li>\n\u003Cli>\u003Cstrong>Temps d'attente\u003C/strong> pour les timers, les animations, les événements asynchrones\u003C/li>\n\u003C/ul>\n\u003Cp>Google a indiqué un \u003Cstrong>timeout de 5 secondes pour le rendering initial\u003C/strong>, au-delà duquel le WRS snapshote l'état actuel du DOM et passe à la page suivante. Ce n'est pas 5 secondes de temps de chargement réseau — c'est 5 secondes d'exécution JavaScript après réception des ressources.\u003C/p>\n\u003Ch3>Crawl budget ≠ rendering budget\u003C/h3>\n\u003Cp>Le crawl budget concerne le nombre de requêtes HTTP que Googlebot effectue sur votre site. Le rendering budget concerne le nombre de pages que le WRS peut effectivement rendre. Vous pouvez avoir un crawl budget excellent (toutes vos pages sont découvertes) et un rendering budget saturé (la moitié de ces pages ne sont jamais rendues à temps pour l'indexation).\u003C/p>\n\u003Cp>C'est exactement ce qui se passe sur les gros sites JavaScript : Search Console montre \"Crawled - currently not indexed\" pour des milliers de pages dont le contenu dépend entièrement du JS.\u003C/p>\n\u003Ch2>Anatomie d'une page qui tue le rendering budget\u003C/h2>\n\u003Cp>Prenons un exemple réaliste. Un site média construit avec une SPA React, sans SSR, qui affiche des articles via des appels API.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;!\u003C/span>\u003Cspan style=\"color:#85E89D\">DOCTYPE\u003C/span>\u003Cspan style=\"color:#B392F0\"> html\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">html\u003C/span>\u003Cspan style=\"color:#B392F0\"> lang\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"fr\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">head\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">meta\u003C/span>\u003Cspan style=\"color:#B392F0\"> charset\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"UTF-8\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">title\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>Mon Article&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">title\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- Pas de meta description dans le HTML initial -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- Pas de contenu structuré -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">head\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">body\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">div\u003C/span>\u003Cspan style=\"color:#B392F0\"> id\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"root\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">div\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- Bundle principal : 847 KB gzippé -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#B392F0\"> src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/static/js/vendor.chunk.a1b2c3.js\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>  \u003C/span>\u003Cspan style=\"color:#6A737D\">&#x3C;!-- 312 KB -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#B392F0\"> src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/static/js/main.chunk.d4e5f6.js\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>     \u003C/span>\u003Cspan style=\"color:#6A737D\">&#x3C;!-- 535 KB -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">body\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">html\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Voici ce que le WRS doit faire pour indexer cette page :\u003C/p>\n\u003Col>\n\u003Cli>Télécharger le HTML (&#x3C; 1 KB utile)\u003C/li>\n\u003Cli>Télécharger 847 KB de JavaScript compressé (~2.5 MB décompressé)\u003C/li>\n\u003Cli>Parser et compiler le JavaScript\u003C/li>\n\u003Cli>Exécuter le code React, créer le DOM virtuel\u003C/li>\n\u003Cli>Intercepter l'appel \u003Ccode>fetch('/api/articles/12345')\u003C/code> et attendre la réponse\u003C/li>\n\u003Cli>Effectuer la réconciliation DOM et injecter le contenu\u003C/li>\n\u003Cli>Snapshotter le DOM final\u003C/li>\n\u003C/ol>\n\u003Cp>Multipliez ça par 22 000 pages. Chaque page nécessite entre 2 et 8 secondes de traitement WRS. Même si le WRS cache les bundles JS entre les pages d'un même site (ce que Google a confirmé), les appels API et l'exécution spécifique à chaque page restent incompressibles.\u003C/p>\n\u003Ch3>Les signaux d'alerte dans Chrome DevTools\u003C/h3>\n\u003Cp>Avant même de parler de Googlebot, vous pouvez diagnostiquer les problèmes de rendering avec \u003Ca href=\"/blog/chrome-devtools-pour-le-seo-astuces-avancees\">Chrome DevTools\u003C/a>. Ouvrez l'onglet Performance, désactivez le cache, et simulez un throttling CPU 4x :\u003C/p>\n\u003Cpre>\u003Ccode>// Dans la console DevTools, mesurez le temps avant que le contenu principal soit dans le DOM\nconst observer = new PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (entry.name === 'largest-contentful-paint') {\n      console.log(`LCP: ${entry.startTime}ms`);\n      console.log(`Element: ${entry.element?.tagName} — ${entry.element?.textContent?.substring(0, 80)}`);\n    }\n  }\n});\nobserver.observe({ type: 'largest-contentful-paint', buffered: true });\n\u003C/code>\u003C/pre>\n\u003Cp>Si le LCP dépasse 4 secondes avec un CPU throttled 4x, vous êtes dans la zone de danger pour le WRS. Le WRS tourne sur une infrastructure puissante, mais il traite des millions de pages par jour — chaque milliseconde compte dans le bilan global.\u003C/p>\n\u003Cp>Un indicateur encore plus révélateur : comparez le HTML source (\u003Ccode>view-source:\u003C/code>) avec le DOM rendu (\u003Ccode>document.documentElement.outerHTML\u003C/code> dans la console). Si plus de 50 % du contenu textuel de la page n'existe que dans le DOM rendu, vous dépendez entièrement du rendering budget.\u003C/p>\n\u003Ch2>Quantifier le problème : le scénario à 22 000 pages\u003C/h2>\n\u003Cp>Revenons à notre site e-commerce React. Voici les chiffres réels d'un audit post-migration.\u003C/p>\n\u003Cp>\u003Cstrong>Avant\u003C/strong> (site PHP server-rendered) :\u003C/p>\n\u003Cul>\n\u003Cli>22 000 pages produit\u003C/li>\n\u003Cli>21 400 indexées (97 %)\u003C/li>\n\u003Cli>Temps moyen de crawl dans Search Console : 180 ms\u003C/li>\n\u003Cli>Trafic organique : ~45 000 visites/jour\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Cstrong>Après\u003C/strong> (migration React SPA, 3 mois plus tard) :\u003C/p>\n\u003Cul>\n\u003Cli>22 000 pages produit\u003C/li>\n\u003Cli>14 200 indexées (64 %)\u003C/li>\n\u003Cli>Temps moyen de crawl dans Search Console : 1 200 ms (le HTML + attente des ressources)\u003C/li>\n\u003Cli>Trafic organique : ~29 000 visites/jour (−35 %)\u003C/li>\n\u003C/ul>\n\u003Cp>Les rapports \u003Ca href=\"/blog/google-search-console-les-rapports-que-vous-ignorez\">Search Console\u003C/a> montraient une augmentation massive des pages en statut \"Discovered - currently not indexed\" et \"Crawled - currently not indexed\". Le rapport de couverture est votre premier outil de diagnostic : si ces deux catégories explosent après une migration JS, le rendering budget est votre suspect numéro un.\u003C/p>\n\u003Ch3>Comment identifier les pages non rendues\u003C/h3>\n\u003Cp>Utilisez le rapport de couverture d'indexation dans Search Console, mais croisez-le avec un crawl JavaScript via Screaming Frog. Configurez Screaming Frog en mode \"JavaScript rendering\" avec Chrome headless :\u003C/p>\n\u003Cpre>\u003Ccode># Configuration Screaming Frog pour crawl JS\n# Configuration > Spider > Rendering > JavaScript\n# Rendering Engine: Chromium\n# JavaScript Execution Time: 10 secondes (pour voir ce que Google pourrait manquer à 5s)\n# AJAX Timeout: 5 secondes\n# Block Resources: aucun (pour reproduire le comportement WRS)\n\n# Ensuite, exportez et comparez :\n# 1. Crawl en mode \"HTML only\" → contenu extrait du HTML brut\n# 2. Crawl en mode \"JavaScript\" → contenu extrait du DOM rendu\n# 3. Différentiel = contenu dépendant à 100% du rendering\n\u003C/code>\u003C/pre>\n\u003Cp>Exportez les deux crawls. Les pages où le title, la meta description, ou le contenu H1 diffèrent entre HTML et DOM rendu sont celles qui dépendent du rendering budget. Si ce différentiel concerne plus de 30 % de vos pages, vous avez un problème structurel.\u003C/p>\n\u003Ch2>Les seuils techniques : où se situe la limite\u003C/h2>\n\u003Cp>Google n'a pas publié de limites chiffrées officielles pour le rendering budget. Mais en croisant les déclarations de l'équipe Google Search, les observations empiriques à grande échelle, et les tests reproductibles, on peut établir des seuils de risque pragmatiques.\u003C/p>\n\u003Ch3>Taille des bundles JavaScript\u003C/h3>\n\u003Cp>Le WRS met en cache les ressources statiques entre les crawls d'un même site. Votre \u003Ccode>vendor.chunk.js\u003C/code> ne sera pas re-téléchargé à chaque page. Mais il sera re-parsé et partiellement re-exécuté. La taille décompressée du JavaScript total est le metric qui compte :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>&#x3C; 500 KB décompressé\u003C/strong> : zone verte. Le WRS gère sans difficulté.\u003C/li>\n\u003Cli>\u003Cstrong>500 KB - 1.5 MB\u003C/strong> : zone orange. Le rendering fonctionne sur la plupart des pages, mais les pages de faible priorité peuvent être dépriorisées dans la queue.\u003C/li>\n\u003Cli>\u003Cstrong>> 1.5 MB\u003C/strong> : zone rouge. Le temps d'exécution dépasse régulièrement les seuils du WRS. Attendez-vous à des problèmes d'indexation sur les pages non maillées.\u003C/li>\n\u003C/ul>\n\u003Cp>Ces seuils concernent le JavaScript \u003Cstrong>nécessaire au rendering du contenu principal\u003C/strong>. Si vous chargez 2 MB de JS mais que votre contenu est dans le HTML initial et que le JS ne fait qu'ajouter de l'interactivité, le rendering budget n'est pas impacté.\u003C/p>\n\u003Ch3>Nombre d'appels API nécessaires au rendering\u003C/h3>\n\u003Cp>Chaque \u003Ccode>fetch()\u003C/code> ou \u003Ccode>XMLHttpRequest\u003C/code> que le WRS doit attendre avant de snapshotter le DOM ajoute de la latence. Le WRS suit les redirections, respecte les cookies de session, mais il a un timeout global.\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>0 appel API\u003C/strong> : le contenu est dans le HTML ou inline dans un tag \u003Ccode>&#x3C;script>\u003C/code>. Idéal.\u003C/li>\n\u003Cli>\u003Cstrong>1-2 appels API\u003C/strong> rapides (&#x3C; 200 ms chacun) : gérable.\u003C/li>\n\u003Cli>\u003Cstrong>3+ appels API en cascade\u003C/strong> (waterfall) : danger. Si l'appel B dépend de la réponse de A, le temps total explose.\u003C/li>\n\u003C/ul>\n\u003Ch3>Cas particulier : les appels API qui échouent silencieusement\u003C/h3>\n\u003Cp>Le piège le plus vicieux. Votre composant React fait un \u003Ccode>fetch('/api/product/12345')\u003C/code>. En production, l'API répond en 80 ms. Mais quand le WRS fait la même requête :\u003C/p>\n\u003Cul>\n\u003Cli>L'API peut rate-limiter le User-Agent de Googlebot\u003C/li>\n\u003Cli>L'API peut exiger un token d'authentification absent\u003C/li>\n\u003Cli>L'API peut être derrière un WAF qui bloque les IPs de Google\u003C/li>\n\u003Cli>L'API peut timeout sous la charge du crawl\u003C/li>\n\u003C/ul>\n\u003Cp>Dans tous ces cas, le composant React affiche un spinner ou un état vide. Le WRS snapshotte ce DOM dégradé. La page est indexée sans contenu.\u003C/p>\n\u003Cp>Vérifiez vos access logs pour le User-Agent \u003Ccode>Googlebot\u003C/code> et filtrez les requêtes vers vos endpoints API :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Identifier les requêtes API de Googlebot et leur code de réponse\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -i\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"googlebot\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /var/log/nginx/access.log\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"/api/\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> awk\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '{print $7, $9}'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> uniq\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -c\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -rn\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -30\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Résultat typique qui révèle un problème :\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">#   4521 /api/products/   200\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">#    892 /api/products/   429   ← rate limiting !\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">#    341 /api/products/   403   ← WAF ou auth\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">#     67 /api/products/   504   ← timeout\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Si vous voyez des 429 ou 403 sur vos endpoints API pour Googlebot, vous avez trouvé pourquoi des pages ne s'indexent pas. La solution immédiate : whitelistez les ranges IP de Googlebot (vérifiables via \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot\">la documentation officielle Google\u003C/a>) au niveau de votre WAF et de votre rate limiter.\u003C/p>\n\u003Ch2>Stratégies pour réduire la pression sur le rendering budget\u003C/h2>\n\u003Ch3>SSR ou SSG : la solution structurelle\u003C/h3>\n\u003Cp>Le rendering côté serveur (SSR) ou la génération statique (SSG) éliminent le problème à la racine. Si le HTML envoyé au client contient déjà le contenu indexable, le WRS n'a rien à rendre — Google indexe directement depuis l'étape 1 du pipeline.\u003C/p>\n\u003Cp>Si vous êtes sur React, Next.js avec \u003Ccode>getServerSideProps\u003C/code> ou \u003Ccode>getStaticProps\u003C/code> résout le problème. Si vous êtes sur Vue, Nuxt fait le même travail. L'article sur le \u003Ca href=\"/blog/changer-de-framework-next-js-vers-nuxt-ou-l-inverse-sans-perte-seo\">changement de framework sans perte SEO\u003C/a> couvre les risques de migration.\u003C/p>\n\u003Cp>Un point souvent négligé : le SSR partiel. Vous n'avez pas besoin de rendre \u003Cstrong>toute\u003C/strong> la page côté serveur. Seul le contenu critique pour le SEO doit être dans le HTML initial :\u003C/p>\n\u003Cul>\n\u003Cli>Title et meta description\u003C/li>\n\u003Cli>H1, contenu textuel principal, images avec alt\u003C/li>\n\u003Cli>Données structurées (JSON-LD)\u003C/li>\n\u003Cli>Liens internes de navigation\u003C/li>\n\u003C/ul>\n\u003Cp>Les composants interactifs (filtres, panier, recommandations personnalisées) peuvent rester client-side sans impact SEO.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// Exemple Next.js : SSR du contenu SEO-critique, hydratation client pour l'interactivité\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// pages/products/[slug].tsx\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { GetServerSideProps } \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'next'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> dynamic \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'next/dynamic'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// Composants lourds chargés côté client uniquement\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ProductRecommendations\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> dynamic\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  () \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#F97583\"> import\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'@/components/ProductRecommendations'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  { ssr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }  \u003C/span>\u003Cspan style=\"color:#6A737D\">// Ne sera PAS rendu côté serveur\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ProductFilters\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> dynamic\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  () \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#F97583\"> import\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'@/components/ProductFilters'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  { ssr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">interface\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductPageProps\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  product\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    name\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    description\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    price\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> number\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    images\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">url\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">; \u003C/span>\u003Cspan style=\"color:#FFAB70\">alt\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }[];\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    structuredData\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> object\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> default\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductPage\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">product\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductPageProps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    &#x3C;>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      {\u003C/span>\u003Cspan style=\"color:#6A737D\">/* Ce HTML est dans la réponse serveur — Googlebot l'indexe sans rendering */\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">h1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>{product.name}&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">h1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">div\u003C/span>\u003Cspan style=\"color:#B392F0\"> className\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"product-description\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">           dangerouslySetInnerHTML\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{{ __html: product.description }} />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#B392F0\"> type\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"application/ld+json\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">              dangerouslySetInnerHTML\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{{ __html: \u003C/span>\u003Cspan style=\"color:#79B8FF\">JSON\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">stringify\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(product.structuredData) }} />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      {\u003C/span>\u003Cspan style=\"color:#6A737D\">/* Ces composants ne sont PAS dans le HTML serveur — pas de pression sur le WRS */\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\">ProductRecommendations\u003C/span>\u003Cspan style=\"color:#B392F0\"> productId\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{product.id} />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\">ProductFilters\u003C/span>\u003Cspan style=\"color:#B392F0\"> category\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{product.category} />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    &#x3C;/>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  );\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> const\u003C/span>\u003Cspan style=\"color:#B392F0\"> getServerSideProps\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> GetServerSideProps\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> async\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> product\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetchProductFromDB\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(params.slug \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { props: { product } };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Cette approche hybride est le sweet spot : le WRS voit le contenu principal dans le HTML, n'a rien à rendre, et les composants \u003Ccode>ssr: false\u003C/code> ne consomment aucune ressource rendering.\u003C/p>\n\u003Ch3>Dynamic rendering : le pansement acceptable\u003C/h3>\n\u003Cp>Si une réécriture SSR n'est pas envisageable à court terme, le dynamic rendering reste une option. Google a explicitement déclaré que le dynamic rendering n'est \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering\">pas du cloaking\u003C/a> quand il sert le même contenu sous une forme différente.\u003C/p>\n\u003Cp>Le principe : détecter le User-Agent de Googlebot au niveau du reverse proxy et lui servir une version pré-rendue de la page. Les outils comme Rendertron (deprecated mais encore utilisé) ou Prerender.io font ce travail.\u003C/p>\n\u003Cp>Le trade-off : vous maintenez deux pipelines de rendering. Le contenu peut dériver entre la version utilisateur et la version bot si vous ne surveillez pas. Un outil de monitoring comme Seogard peut détecter ces dérives en comparant le DOM vu par un crawler JS et le HTML servi au bot.\u003C/p>\n\u003Ch3>Code splitting agressif et tree shaking\u003C/h3>\n\u003Cp>Même avec du SSR, réduire la taille du JavaScript reste bénéfique pour le rendering budget. Le WRS parse et exécute le JS même quand le contenu est déjà dans le HTML — ne serait-ce que pour l'hydratation.\u003C/p>\n\u003Cp>Analysez vos bundles avec \u003Ccode>webpack-bundle-analyzer\u003C/code> ou l'équivalent Vite/Rollup. Cherchez :\u003C/p>\n\u003Cul>\n\u003Cli>Les polyfills inutiles (le WRS est Chrome evergreen, il supporte tout ES2024)\u003C/li>\n\u003Cli>Les bibliothèques importées entièrement alors que vous n'utilisez qu'une fonction (\u003Ccode>lodash\u003C/code> vs \u003Ccode>lodash-es\u003C/code>)\u003C/li>\n\u003Cli>Les dépendances de développement qui se retrouvent dans le bundle de production\u003C/li>\n\u003C/ul>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Analyser la composition de vos bundles Next.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">ANALYZE\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">true\u003C/span>\u003Cspan style=\"color:#B392F0\"> next\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Pour Vite / Rollup\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">npx\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> vite-bundle-visualizer\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Identifier les modules les plus lourds\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Cible : aucun chunk > 150 KB gzippé pour le critical path\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch2>Monitoring continu : détecter la régression avant Google\u003C/h2>\n\u003Cp>Le rendering budget n'est pas un problème que vous réglez une fois. Chaque déploiement peut introduire une régression : un nouveau package qui double la taille du bundle, un appel API ajouté dans le critical path, un middleware qui bloque les requêtes du WRS.\u003C/p>\n\u003Ch3>Intégrer les checks dans le CI/CD\u003C/h3>\n\u003Cp>La première ligne de défense est dans votre pipeline de déploiement. \u003Ca href=\"/blog/automatiser-les-checks-seo-dans-le-ci-cd\">Automatiser les checks SEO dans le CI/CD\u003C/a> vous permet de bloquer un merge qui dégrade la renderabilité.\u003C/p>\n\u003Cp>Les checks essentiels :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Taille maximale du bundle JS\u003C/strong> : échouez le build si le bundle principal dépasse un seuil (par exemple 200 KB gzippé)\u003C/li>\n\u003Cli>\u003Cstrong>Présence du contenu critique dans le HTML initial\u003C/strong> : un test qui vérifie que le H1, la meta description et le JSON-LD sont dans la réponse HTTP sans exécuter de JS\u003C/li>\n\u003Cli>\u003Cstrong>Temps de rendering\u003C/strong> : un test Lighthouse CI qui vérifie que le LCP est sous 2.5s en simulation mobile\u003C/li>\n\u003C/ul>\n\u003Ch3>Surveiller les signaux dans Search Console\u003C/h3>\n\u003Cp>Le rapport \"Statistiques d'exploration\" dans Search Console donne le temps de réponse moyen vu par Googlebot. Si ce temps augmente brutalement, le WRS passe plus de temps sur vos pages — ce qui peut indiquer des bundles JS plus lourds ou des API plus lentes.\u003C/p>\n\u003Cp>Croisez ce rapport avec les données de couverture. L'\u003Ca href=\"/blog/search-console-api-automatiser-le-reporting-seo\">API Search Console\u003C/a> permet d'automatiser cette surveillance :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Surveiller l'évolution du ratio pages indexées / pages découvertes\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># via l'API Search Console (URL Inspection API en batch)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> datetime\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> google.oauth2 \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service_account\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> googleapiclient.discovery \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">credentials \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service_account.Credentials.from_service_account_file(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'service-account.json'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    scopes\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://www.googleapis.com/auth/webmasters.readonly'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">service \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> build(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'searchconsole'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'v1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">credentials\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">credentials)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Récupérer les stats d'exploration sur les 90 derniers jours\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Endpoint non documenté publiquement — utilisez l'export CSV \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># du rapport Statistiques d'exploration comme alternative\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Alternative plus fiable : comparer le nombre de pages\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># dans le sitemap vs pages indexées via l'API Sitemaps\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">sitemaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service.sitemaps().list(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    siteUrl\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://www.votre-ecommerce.fr'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">).execute()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sitemap \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sitemaps.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sitemap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, []):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    submitted \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sitemap.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'contents'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, [{}])[\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'submitted'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    indexed \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sitemap.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'contents'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, [{}])[\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'indexed'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ratio \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#79B8FF\">int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(indexed) \u003C/span>\u003Cspan style=\"color:#F97583\">/\u003C/span>\u003Cspan style=\"color:#79B8FF\"> int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(submitted) \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(submitted) \u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003Cspan style=\"color:#F97583\"> else\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">sitemap[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">indexed\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">submitted\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> indexées (\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ratio\u003C/span>\u003Cspan style=\"color:#F97583\">:.1f\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">%)\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Alerte si le ratio descend sous 85%\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ratio \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 85\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"  ⚠ ALERTE: ratio d'indexation critique pour \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">sitemap[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Un ratio pages indexées / pages soumises qui descend sous 85 % sur un site qui servait du contenu SSR auparavant est un signal fort de problème de rendering budget.\u003C/p>\n\u003Ch3>Les métriques à suivre dans la durée\u003C/h3>\n\u003Cp>Mettez en place un dashboard qui suit ces \u003Ca href=\"/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre\">KPIs\u003C/a> sur une base hebdomadaire :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Taux d'indexation par type de page\u003C/strong> (catégorie, produit, article) — segmenter est crucial, le problème peut toucher un template et pas un autre\u003C/li>\n\u003Cli>\u003Cstrong>Taille moyenne du bundle JS par template\u003C/strong> — via votre build system\u003C/li>\n\u003Cli>\u003Cstrong>Temps de réponse Googlebot\u003C/strong> — via Search Console\u003C/li>\n\u003Cli>\u003Cstrong>Nombre de pages \"Crawled - currently not indexed\"\u003C/strong> — l'indicateur le plus direct du rendering budget saturé\u003C/li>\n\u003Cli>\u003Cstrong>Delta contenu HTML vs DOM rendu\u003C/strong> — un crawl Screaming Frog JS mensuel comparé au crawl HTML only\u003C/li>\n\u003C/ul>\n\u003Ch2>Edge cases et nuances\u003C/h2>\n\u003Ch3>Les sites qui n'ont PAS de problème de rendering budget\u003C/h3>\n\u003Cp>Le rendering budget n'est pas un sujet universel. Si votre site est un WordPress classique avec quelques scripts jQuery, le WRS n'a quasiment rien à rendre. Le problème concerne spécifiquement :\u003C/p>\n\u003Cul>\n\u003Cli>Les SPA (Single Page Applications) sans SSR\u003C/li>\n\u003Cli>Les sites qui chargent leur contenu principal via des appels API côté client\u003C/li>\n\u003Cli>Les sites avec des frameworks JS lourds (Angular universel mal configuré, React sans Next.js/Remix)\u003C/li>\n\u003Cli>Les sites avec du lazy loading JavaScript sur le contenu above-the-fold\u003C/li>\n\u003C/ul>\n\u003Cp>Un site e-commerce sur Shopify, par exemple, n'a généralement pas de problème de rendering budget — le contenu produit est dans le HTML serveur. Le problème vient quand des apps tierces injectent du contenu critique via JavaScript.\u003C/p>\n\u003Ch3>Le rendering budget varie selon l'autorité du site\u003C/h3>\n\u003Cp>Google n'alloue pas les mêmes ressources WRS à tous les sites. Un site avec une forte autorité (backlinks de qualité, historique de crawl long, contenu frais fréquent) obtiendra plus de ressources rendering qu'un site récent avec peu de signaux. C'est le même mécanisme que le crawl budget qui dépend du \"crawl demand\" et du \"crawl rate limit\".\u003C/p>\n\u003Cp>En pratique, cela signifie qu'un site d'autorité moyenne peut migrer vers du full client-side rendering et ne voir les problèmes apparaître que 2-3 mois plus tard, quand Google réduit progressivement ses tentatives de rendering sur les pages qui ne convertissent pas en résultats de recherche.\u003C/p>\n\u003Ch3>L'impact des bots IA sur votre budget de ressources serveur\u003C/h3>\n\u003Cp>Un aspect connexe mais distinct : les \u003Ca href=\"/blog/llms-et-crawl-comment-les-bots-ia-crawlent-votre-site\">bots IA qui crawlent votre site\u003C/a> ne font généralement pas de rendering JavaScript. Ils récupèrent le HTML brut. Si votre contenu n'est pas dans le HTML, vous êtes invisible à la fois pour Google (rendering budget) et pour les moteurs de réponse IA (pas de rendering du tout). Double peine.\u003C/p>\n\u003Ch3>Le headless CMS mal configuré\u003C/h3>\n\u003Cp>Les architectures \u003Ca href=\"/blog/headless-cms-et-seo-avantages-et-risques-techniques\">headless CMS\u003C/a> sont un terrain fertile pour les problèmes de rendering budget. Le CMS fournit le contenu via API, le frontend le rend en JavaScript. Si le frontend est un SPA sans SSR, chaque page impose au WRS un cycle complet de rendering + appel API.\u003C/p>\n\u003Cp>La solution n'est pas de revenir à un CMS monolithique. C'est de s'assurer que la couche de présentation fait du SSR ou de la SSG, et que les appels API vers le CMS headless sont faits côté serveur au moment du build ou de la requête, pas côté client dans le navigateur.\u003C/p>\n\u003Ch2>Le rendering budget en résumé opérationnel\u003C/h2>\n\u003Cp>Le rendering budget est une contrainte réelle mais invisible. Google ne vous enverra jamais d'alerte \"rendering budget dépassé\". Vous le détecterez uniquement par ses symptômes : baisse du taux d'indexation, hausse des pages \"crawled not indexed\", contenu manquant dans le cache Google.\u003C/p>\n\u003Cp>La règle d'or : \u003Cstrong>tout contenu que vous voulez indexer doit être dans le HTML initial\u003C/strong>. Le JavaScript doit servir l'interactivité, pas le contenu. Si cette règle est respectée, le rendering budget n'est plus un problème — il devient un non-sujet.\u003C/p>\n\u003Cp>Pour les sites où une refonte SSR est impossible à court terme, le monitoring continu est la seule protection. Un outil comme Seogard qui compare périodiquement le HTML brut au DOM rendu détecte les dérives avant qu'elles n'impactent l'indexation. Couplé à des alertes sur le taux d'indexation Search Console, vous avez un filet de sécurité contre la régression silencieuse que représente un rendering budget saturé.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21,22],"rendering-budget","javascript","googlebot","limites","SEO technique","Rendering budget Google : combien de JS est trop pour Googlebot","Fri Apr 10 2026 12:02:31 GMT+0000 (Coordinated Universal Time)",[]]