[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f7aZTb6LHkv00Iiw_Bh5CGEqWvBG39M5Zp71zymsRcbI":3,"$fA-AjVI52pLA2M-4DiotQ2VxDdKmZLzy6ca9ji4rNdVE":24,"$fcTFjTaPPT1sVcnh1wWEGoTWMSaOD4bwmwntkMae6w5s":101},{"_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":22,"updatedAt":23},"6a23b7d0aa6b273b0c6c840f","splash-screen-noscript-mal-place-qui-contient-le-vrai-contenu-pour-googlebot-sansjs",0,"Equipe Seogard","# Splash screen et noscript mal placé : quand aider Googlebot déclenche une alerte cloaking\n\nJeudi 14h. Un développeur front pousse un \"quick win SEO\" sur un site e-commerce français de 8 400 pages. L'idée : dupliquer le contenu principal dans une balise `\u003Cnoscript>` pour que les bots sans JavaScript voient quelque chose d'utile derrière le splash screen d'initialisation du SPA React. Le build passe, la review est validée en douze minutes. Dix jours plus tard, Search Console affiche une action manuelle pour cloaking. 1 200 URLs produit disparaissent de l'index.\n\n## Mardi, T+5 — \"Pourquoi les impressions chutent ?\"\n\nLe signal arrive par Slack, posté par la responsable acquisition à 8h52. Le dashboard Looker Studio montre un décrochage net des impressions organiques depuis le week-end. Moins 38 % sur les pages catégorie. Moins 61 % sur les fiches produit.\n\nPremier réflexe : vérifier Search Console. L'onglet Couverture ne montre rien d'anormal — les pages sont toujours \"Valides\". Pas d'erreur de crawl. Pas de pic de 5xx dans les logs Cloudflare. Le lead SEO ouvre l'onglet Performances et filtre par page. Les fiches produit les plus rentables sont à zéro clic depuis samedi.\n\nHypothèse initiale : un problème de rendering côté Google. L'équipe lance une inspection d'URL sur une fiche produit clé. Le rendu affiché par Search Console montre… le splash screen. Un cercle de chargement, un logo, et rien d'autre. Le contenu produit n'apparaît pas dans le HTML rendu par Google.\n\nLe lead SEO lance Screaming Frog en mode \"JavaScript rendering\" sur un échantillon de 200 URLs. Résultat : le contenu est bien présent dans le DOM final. Mais en basculant sur le mode \"HTML brut\", il découvre autre chose. Le `\u003Cbody>` contient deux versions du contenu : une dans le `\u003Cdiv id=\"root\">` (le SPA React), et une copie complète dans un bloc `\u003Cnoscript>` juste en dessous.\n\nÀ 10h15, le CTO reçoit un email de Search Console. Objet : \"Action manuelle — Cloaking et/ou redirections trompeuses\". Le message cible 1 247 URLs. Google considère que le site présente un contenu différent aux utilisateurs et aux robots d'exploration.\n\nLa panique s'installe. L'action manuelle signifie une désindexation active, pas un simple bug de rendering. Le trafic organique, qui représente 42 % du chiffre d'affaires du site, est en chute libre. Le lead SEO calcule l'impact : environ 14 000 clics organiques perdus par semaine, soit un manque à gagner estimé à 35 000 € sur la période.\n\nL'équipe remonte le fil Git. Le commit incriminé date de jeudi dernier. Un développeur front a ajouté un composant `\u003CSeoFallback>` qui injecte le contenu statique des pages dans une balise `\u003Cnoscript>`. Son intention était bonne : fournir un fallback lisible pour les crawlers qui n'exécutent pas JavaScript.\n\n## Le bug : deux contenus, une seule page, un diagnostic de cloaking\n\nPour comprendre pourquoi Google a déclenché l'action manuelle, il faut examiner ce que chaque visiteur reçoit — et ce que Googlebot interprète.\n\n### Ce que voit un utilisateur normal\n\nL'utilisateur arrive sur une fiche produit. Le navigateur charge le bundle React (847 Ko gzippé). Pendant le chargement, un splash screen CSS s'affiche via le HTML initial :\n\n```html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"fr\">\n\u003Chead>\n  \u003Cmeta charset=\"utf-8\">\n  \u003Ctitle>Chargement...\u003C/title>\n  \u003Cmeta name=\"description\" content=\"\">\n  \u003Clink rel=\"stylesheet\" href=\"/assets/splash.css\">\n\u003C/head>\n\u003Cbody>\n  \u003Cdiv id=\"root\">\n    \u003Cdiv class=\"splash-screen\">\n      \u003Cimg src=\"/logo.svg\" alt=\"Logo\" class=\"splash-logo\">\n      \u003Cdiv class=\"spinner\">\u003C/div>\n    \u003C/div>\n  \u003C/div>\n  \u003Cnoscript>\n    \u003Carticle class=\"product-detail\">\n      \u003Ch1>Sneakers running TrailMax 3000 — Noir/Rouge\u003C/h1>\n      \u003Cp class=\"price\">129,90 €\u003C/p>\n      \u003Cdiv class=\"description\">\n        \u003Cp>Conçue pour les trails techniques, la TrailMax 3000 offre\n        un amorti renforcé et une semelle Vibram...\u003C/p>\n      \u003C/div>\n      \u003Cul class=\"specs\">\n        \u003Cli>Poids : 285g\u003C/li>\n        \u003Cli>Drop : 8mm\u003C/li>\n        \u003Cli>Semelle : Vibram Megagrip\u003C/li>\n      \u003C/ul>\n    \u003C/article>\n  \u003C/noscript>\n  \u003Cscript src=\"/assets/app.bundle.js\">\u003C/script>\n\u003C/body>\n\u003C/html>\n```\n\nUne fois React hydraté, le `\u003Cdiv id=\"root\">` se remplit avec le vrai contenu produit. Le splash screen disparaît. L'utilisateur voit la fiche complète. La balise `\u003Cnoscript>` est ignorée par le navigateur puisque JavaScript est actif.\n\nLe `\u003Ctitle>` reste \"Chargement...\" jusqu'à ce que React le remplace via `react-helmet-async`. La meta description est vide dans le HTML initial.\n\n### Ce que voit Googlebot (et pourquoi c'est un problème)\n\nGooglebot utilise un processus en deux phases. D'abord le crawl HTTP brut, puis le rendering via Chromium headless (WRS — Web Rendering Service). Mais ces deux phases ne sont pas simultanées. Le rendering peut intervenir des heures ou des jours après le crawl initial.\n\nLors du crawl HTTP brut, Googlebot voit :\n- Un `\u003Ctitle>` qui dit \"Chargement...\"\n- Une meta description vide\n- Un `\u003Cdiv id=\"root\">` contenant uniquement un spinner\n- Un `\u003Cnoscript>` contenant un article complet avec H1, prix, description, specs\n\nLe problème fondamental : le contenu dans `\u003Cnoscript>` est **structurellement différent** de ce qu'un utilisateur avec JavaScript voit. Pas dans le fond — c'est le même texte — mais dans la **condition d'affichage**. Pour un navigateur avec JS activé, ce contenu est invisible. Pour un crawler HTTP brut, c'est le seul contenu disponible.\n\nGoogle interprète ce pattern comme du cloaking : un contenu riche est servi spécifiquement aux agents qui ne rendent pas JavaScript, tandis que les utilisateurs réels voient un splash screen suivi d'un contenu rendu côté client. La divergence entre les deux expériences déclenche l'alerte.\n\n### Le composant fautif\n\nLe développeur avait créé un composant React dédié, injecté via un plugin Webpack `html-webpack-plugin` :\n\n```javascript\n// webpack.config.js — extrait\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst generateNoscriptFallback = require('./scripts/noscript-fallback');\n\nmodule.exports = {\n  // ...\n  plugins: [\n    new HtmlWebpackPlugin({\n      template: './public/index.html',\n      templateParameters: async (compilation, assets, assetTags, options) => {\n        const pages = await fetchAllProductPages(); // appel API Strapi\n        return {\n          noscriptBlocks: pages.map(page => generateNoscriptFallback(page)),\n        };\n      },\n    }),\n  ],\n};\n```\n\nLe script `noscript-fallback.js` générait un bloc HTML complet pour chaque page :\n\n```javascript\n// scripts/noscript-fallback.js\nfunction generateNoscriptFallback(page) {\n  return `\n    \u003Cnoscript>\n      \u003Carticle class=\"product-detail\">\n        \u003Ch1>${page.title}\u003C/h1>\n        \u003Cp class=\"price\">${page.price} €\u003C/p>\n        \u003Cdiv class=\"description\">${page.description}\u003C/div>\n        \u003Cul class=\"specs\">\n          ${page.specs.map(s => `\u003Cli>${s}\u003C/li>`).join('')}\n        \u003C/ul>\n      \u003C/article>\n    \u003C/noscript>\n  `;\n}\nmodule.exports = generateNoscriptFallback;\n```\n\nL'intention du développeur venait d'un article de blog de 2019 recommandant les fallbacks `\u003Cnoscript>` pour le SEO des SPA. Ce conseil était déjà discutable à l'époque. En 2026, avec la sophistication du WRS de Google, c'est une bombe à retardement.\n\n### Pourquoi les tests n'ont rien détecté\n\nL'équipe avait des tests Cypress end-to-end. Mais tous les tests s'exécutent avec JavaScript activé. Personne n'a jamais testé le HTML brut servi au premier hit HTTP.\n\nLe pipeline CI/CD incluait un check Lighthouse. Lighthouse exécute JavaScript — il ne voit jamais le contenu `\u003Cnoscript>` comme contenu principal.\n\nLe seul outil qui aurait pu détecter la divergence : un diff entre le HTML brut (`curl`) et le DOM rendu après exécution JS. Ce test n'existait pas.\n\nPour reproduire le problème, une simple commande suffisait :\n\n```bash\ncurl -s https://www.example.com/produit/trailmax-3000 \\\n  -H \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\" \\\n  | grep -A 20 \"\u003Cnoscript>\"\n```\n\nLe résultat affiche l'intégralité du contenu produit dans le `\u003Cnoscript>`. En comparant avec ce que voit un utilisateur réel avant l'exécution JS (un spinner), la divergence est flagrante.\n\nCe pattern rappelle un autre piège courant des SPA : le contenu principal caché derrière des directives conditionnelles. Sur Vue.js, le même problème survient avec `v-if` sur les sections critiques, comme documenté dans [cet incident sur une section hero invisible au fetch HTTP brut](/blog/lazy-load-du-hero-h1-dans-une-section-v-if-invisible-au-fetch-http-brut).\n\n### La nuance cloaking vs. fallback légitime\n\nLa [documentation Google sur le cloaking](https://developers.google.com/search/docs/essentials/cloaking) est explicite : servir un contenu différent aux moteurs de recherche et aux utilisateurs constitue une violation des consignes. Le fait que l'intention soit \"bonne\" (aider le bot à comprendre la page) ne change rien au diagnostic.\n\nUn usage légitime de `\u003Cnoscript>` existe : afficher un message du type \"Ce site nécessite JavaScript\" ou fournir un lien alternatif. Injecter le contenu complet de la page — avec H1, prix, description structurée — dans un `\u003Cnoscript>` alors que ce contenu n'est visible qu'après exécution JS pour les vrais utilisateurs, c'est exactement ce que Google considère comme du cloaking.\n\nCe problème est distinct mais connexe d'un autre pattern toxique : les balises meta manipulées par des conditions CSS ou JS. Un cas similaire a été documenté lors d'un [A/B test qui servait un noindex à 50 % du trafic](/blog/a-b-test-header-la-variante-b-sert-un-noindex-a-50-du-trafic-pendant-9-jours), où la divergence entre les versions provoquait une désindexation progressive.\n\n## Le fix : supprimer, rendre en SSR, et demander la réexamen\n\nLe correctif s'est déroulé en trois phases, étalées sur 48 heures de travail intense.\n\n### Phase 1 — Suppression immédiate du noscript (T+0)\n\nPriorité absolue : retirer le composant `\u003CSeoFallback>` et tous les blocs `\u003Cnoscript>` contenant du contenu produit. Le revert Git est propre — un seul commit à annuler.\n\n```bash\ngit revert 3f7a2e1 --no-edit\ngit push origin main\n```\n\nLe déploiement est poussé en production en 8 minutes via le pipeline Vercel. Le CDN Cloudflare est purgé manuellement pour les 1 247 URLs concernées :\n\n```bash\n# Purge Cloudflare via API — batch de 30 URLs max par appel\ncat affected-urls.txt | xargs -n 30 -I {} \\\n  curl -s -X POST \"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache\" \\\n  -H \"Authorization: Bearer $CF_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"files\": [{}]}'\n```\n\n### Phase 2 — Mise en place d'un pre-rendering SSR minimal (T+12h)\n\nSupprimer le `\u003Cnoscript>` ne suffit pas. Le problème initial reste : le HTML brut servi par le SPA React ne contient que le splash screen. Googlebot WRS finira par rendre la page, mais le HTML initial doit être propre.\n\nL'équipe opte pour un pré-rendu statique via `react-snap`, configuré pour générer le HTML complet de chaque page produit au build :\n\n```javascript\n// package.json — extrait\n{\n  \"scripts\": {\n    \"postbuild\": \"react-snap\"\n  },\n  \"reactSnap\": {\n    \"source\": \"build\",\n    \"puppeteerArgs\": [\"--no-sandbox\"],\n    \"include\": [\"/produit/\"],\n    \"minifyHtml\": {\n      \"collapseWhitespace\": true,\n      \"removeComments\": true\n    },\n    \"inlineCss\": true,\n    \"skipThirdPartyRequests\": true\n  }\n}\n```\n\nAvec cette configuration, chaque URL `/produit/*` est pré-rendue avec le contenu complet directement dans le `\u003Cdiv id=\"root\">`. Le `\u003Ctitle>` et la meta description sont corrects dès le premier octet. Plus de splash screen pour Googlebot. Plus de divergence.\n\nL'alternative aurait été de migrer vers Next.js ou Remix pour du vrai SSR. L'équipe n'avait pas le budget de temps. `react-snap` a résolu le problème en une demi-journée. Ce n'est pas élégant. C'est efficace.\n\nCe choix d'architecture rejoint les problématiques rencontrées lors de [migrations vers le SSR Angular où le `provideServerRendering` mal configuré provoque des hydration mismatches invisibles](/blog/migration-angular-17-vers-ssr-provideserverrendering-mal-configure-et-hydration-mismatch-invisible).\n\n### Phase 3 — Demande de réexamen (T+24h)\n\nL'action manuelle nécessite une demande de réexamen dans Search Console. L'équipe rédige un message précis :\n\n- Description du problème identifié (blocs `\u003Cnoscript>` contenant du contenu dupliqué)\n- Date du commit fautif et date du revert\n- Preuve que le HTML brut actuel sert le contenu identique à ce que voit un utilisateur\n- URL d'exemple avant/après\n\nLa demande est soumise le mercredi. La réponse de Google arrive le lundi suivant : action manuelle levée. Délai total : 5 jours ouvrés.\n\n### Récupération du trafic\n\nLa réindexation des 1 247 URLs prend 9 jours après la levée de l'action manuelle. Le trafic organique retrouve 85 % de son niveau d'avant-incident au bout de 14 jours. Les 15 % restants reviennent progressivement sur les 3 semaines suivantes, à mesure que Google recrawle et re-rank les pages.\n\nImpact total estimé : 21 jours de perturbation, environ 42 000 clics organiques perdus, soit un manque à gagner de 95 000 € pour ce e-commerce.\n\n### Les garde-fous mis en place\n\nL'équipe ajoute trois contrôles au pipeline CI/CD :\n\n1. **Lint HTML statique** : un script Node qui parse le HTML généré et échoue si un `\u003Cnoscript>` contient plus de 50 caractères de texte (seuil pour un simple message \"Activez JavaScript\").\n\n2. **Diff SSR/CSR automatisé** : à chaque PR, un job compare le HTML brut (`curl`) avec le DOM rendu par Puppeteer. Si la divergence textuelle dépasse 20 %, le build échoue.\n\n3. **Alerte Search Console** : notification Slack immédiate sur les actions manuelles et les chutes d'indexation supérieures à 5 % sur 48h.\n\nCe type de vérification SSR/CSR rejoint la logique de surveillance décrite dans l'incident de [migration Next.js Pages Router vers App Router, où les metadata étaient ignorées sur les pages client](/blog/migration-next-js-pages-router-vers-app-router-les-metadata-ignorees-sur-les-pages-client) faute de monitoring du HTML initial.\n\n## Ce qu'on en retient\n\nLe `\u003Cnoscript>` n'est pas un outil SEO. C'est un mécanisme de fallback pour les navigateurs sans JavaScript. Y injecter du contenu riche quand le reste de la page est un SPA vide, c'est créer une divergence que Google qualifie de cloaking — même sans intention malveillante.\n\nTrois règles pour les SPA :\n- Le HTML initial doit contenir le même contenu que le DOM rendu. Pas un spinner. Pas un placeholder.\n- Si le SSR complet n'est pas envisageable, le pré-rendu statique est un compromis viable.\n- La divergence entre HTML brut et DOM final doit être testée automatiquement, à chaque déploiement.\n\nUn outil de monitoring continu comme Seogard détecte ce type de divergence SSR/CSR en quelques minutes après le déploiement — pas onze jours plus tard, quand Search Console envoie un email d'action manuelle.\n```","https://seogard.io/blog/splash-screen-noscript-mal-place-qui-contient-le-vrai-contenu-pour-googlebot-sansjs","Rendering","2026-06-06T06:01:52.615Z","2026-06-06","Un e-commerce SPA cache son contenu dans une balise noscript pour les bots. Google détecte du cloaking. Récit, diagnostic et fix complet.","\u003Ch1>Splash screen et noscript mal placé : quand aider Googlebot déclenche une alerte cloaking\u003C/h1>\n\u003Cp>Jeudi 14h. Un développeur front pousse un \"quick win SEO\" sur un site e-commerce français de 8 400 pages. L'idée : dupliquer le contenu principal dans une balise \u003Ccode>&#x3C;noscript>\u003C/code> pour que les bots sans JavaScript voient quelque chose d'utile derrière le splash screen d'initialisation du SPA React. Le build passe, la review est validée en douze minutes. Dix jours plus tard, Search Console affiche une action manuelle pour cloaking. 1 200 URLs produit disparaissent de l'index.\u003C/p>\n\u003Ch2>Mardi, T+5 — \"Pourquoi les impressions chutent ?\"\u003C/h2>\n\u003Cp>Le signal arrive par Slack, posté par la responsable acquisition à 8h52. Le dashboard Looker Studio montre un décrochage net des impressions organiques depuis le week-end. Moins 38 % sur les pages catégorie. Moins 61 % sur les fiches produit.\u003C/p>\n\u003Cp>Premier réflexe : vérifier Search Console. L'onglet Couverture ne montre rien d'anormal — les pages sont toujours \"Valides\". Pas d'erreur de crawl. Pas de pic de 5xx dans les logs Cloudflare. Le lead SEO ouvre l'onglet Performances et filtre par page. Les fiches produit les plus rentables sont à zéro clic depuis samedi.\u003C/p>\n\u003Cp>Hypothèse initiale : un problème de rendering côté Google. L'équipe lance une inspection d'URL sur une fiche produit clé. Le rendu affiché par Search Console montre… le splash screen. Un cercle de chargement, un logo, et rien d'autre. Le contenu produit n'apparaît pas dans le HTML rendu par Google.\u003C/p>\n\u003Cp>Le lead SEO lance Screaming Frog en mode \"JavaScript rendering\" sur un échantillon de 200 URLs. Résultat : le contenu est bien présent dans le DOM final. Mais en basculant sur le mode \"HTML brut\", il découvre autre chose. Le \u003Ccode>&#x3C;body>\u003C/code> contient deux versions du contenu : une dans le \u003Ccode>&#x3C;div id=\"root\">\u003C/code> (le SPA React), et une copie complète dans un bloc \u003Ccode>&#x3C;noscript>\u003C/code> juste en dessous.\u003C/p>\n\u003Cp>À 10h15, le CTO reçoit un email de Search Console. Objet : \"Action manuelle — Cloaking et/ou redirections trompeuses\". Le message cible 1 247 URLs. Google considère que le site présente un contenu différent aux utilisateurs et aux robots d'exploration.\u003C/p>\n\u003Cp>La panique s'installe. L'action manuelle signifie une désindexation active, pas un simple bug de rendering. Le trafic organique, qui représente 42 % du chiffre d'affaires du site, est en chute libre. Le lead SEO calcule l'impact : environ 14 000 clics organiques perdus par semaine, soit un manque à gagner estimé à 35 000 € sur la période.\u003C/p>\n\u003Cp>L'équipe remonte le fil Git. Le commit incriminé date de jeudi dernier. Un développeur front a ajouté un composant \u003Ccode>&#x3C;SeoFallback>\u003C/code> qui injecte le contenu statique des pages dans une balise \u003Ccode>&#x3C;noscript>\u003C/code>. Son intention était bonne : fournir un fallback lisible pour les crawlers qui n'exécutent pas JavaScript.\u003C/p>\n\u003Ch2>Le bug : deux contenus, une seule page, un diagnostic de cloaking\u003C/h2>\n\u003Cp>Pour comprendre pourquoi Google a déclenché l'action manuelle, il faut examiner ce que chaque visiteur reçoit — et ce que Googlebot interprète.\u003C/p>\n\u003Ch3>Ce que voit un utilisateur normal\u003C/h3>\n\u003Cp>L'utilisateur arrive sur une fiche produit. Le navigateur charge le bundle React (847 Ko gzippé). Pendant le chargement, un splash screen CSS s'affiche via le HTML initial :\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\">>Chargement...&#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:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">meta\u003C/span>\u003Cspan style=\"color:#B392F0\"> name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"description\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> content\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\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\">link\u003C/span>\u003Cspan style=\"color:#B392F0\"> rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"stylesheet\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/assets/splash.css\"\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\">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\">>\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\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"splash-screen\"\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\">img\u003C/span>\u003Cspan style=\"color:#B392F0\"> src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/logo.svg\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> alt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Logo\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"splash-logo\"\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\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"spinner\"\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:#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:#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:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">noscript\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\">article\u003C/span>\u003Cspan style=\"color:#B392F0\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"product-detail\"\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\">>Sneakers running TrailMax 3000 — Noir/Rouge&#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\">p\u003C/span>\u003Cspan style=\"color:#B392F0\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"price\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>129,90 €&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">p\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\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"description\"\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\">p\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>Conçue pour les trails techniques, la TrailMax 3000 offre\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        un amorti renforcé et une semelle Vibram...&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">p\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:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">ul\u003C/span>\u003Cspan style=\"color:#B392F0\"> class\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"specs\"\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\">li\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>Poids : 285g&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">li\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\">li\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>Drop : 8mm&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">li\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\">li\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>Semelle : Vibram Megagrip&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">li\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\">ul\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\">article\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\">noscript\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\">script\u003C/span>\u003Cspan style=\"color:#B392F0\"> src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/assets/app.bundle.js\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">script\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\">html\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Une fois React hydraté, le \u003Ccode>&#x3C;div id=\"root\">\u003C/code> se remplit avec le vrai contenu produit. Le splash screen disparaît. L'utilisateur voit la fiche complète. La balise \u003Ccode>&#x3C;noscript>\u003C/code> est ignorée par le navigateur puisque JavaScript est actif.\u003C/p>\n\u003Cp>Le \u003Ccode>&#x3C;title>\u003C/code> reste \"Chargement...\" jusqu'à ce que React le remplace via \u003Ccode>react-helmet-async\u003C/code>. La meta description est vide dans le HTML initial.\u003C/p>\n\u003Ch3>Ce que voit Googlebot (et pourquoi c'est un problème)\u003C/h3>\n\u003Cp>Googlebot utilise un processus en deux phases. D'abord le crawl HTTP brut, puis le rendering via Chromium headless (WRS — Web Rendering Service). Mais ces deux phases ne sont pas simultanées. Le rendering peut intervenir des heures ou des jours après le crawl initial.\u003C/p>\n\u003Cp>Lors du crawl HTTP brut, Googlebot voit :\u003C/p>\n\u003Cul>\n\u003Cli>Un \u003Ccode>&#x3C;title>\u003C/code> qui dit \"Chargement...\"\u003C/li>\n\u003Cli>Une meta description vide\u003C/li>\n\u003Cli>Un \u003Ccode>&#x3C;div id=\"root\">\u003C/code> contenant uniquement un spinner\u003C/li>\n\u003Cli>Un \u003Ccode>&#x3C;noscript>\u003C/code> contenant un article complet avec H1, prix, description, specs\u003C/li>\n\u003C/ul>\n\u003Cp>Le problème fondamental : le contenu dans \u003Ccode>&#x3C;noscript>\u003C/code> est \u003Cstrong>structurellement différent\u003C/strong> de ce qu'un utilisateur avec JavaScript voit. Pas dans le fond — c'est le même texte — mais dans la \u003Cstrong>condition d'affichage\u003C/strong>. Pour un navigateur avec JS activé, ce contenu est invisible. Pour un crawler HTTP brut, c'est le seul contenu disponible.\u003C/p>\n\u003Cp>Google interprète ce pattern comme du cloaking : un contenu riche est servi spécifiquement aux agents qui ne rendent pas JavaScript, tandis que les utilisateurs réels voient un splash screen suivi d'un contenu rendu côté client. La divergence entre les deux expériences déclenche l'alerte.\u003C/p>\n\u003Ch3>Le composant fautif\u003C/h3>\n\u003Cp>Le développeur avait créé un composant React dédié, injecté via un plugin Webpack \u003Ccode>html-webpack-plugin\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// webpack.config.js — extrait\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> HtmlWebpackPlugin\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> require\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'html-webpack-plugin'\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\"> generateNoscriptFallback\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> require\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'./scripts/noscript-fallback'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">module\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#79B8FF\">exports\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // ...\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  plugins: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    new\u003C/span>\u003Cspan style=\"color:#B392F0\"> HtmlWebpackPlugin\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      template: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'./public/index.html'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">      templateParameters\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#F97583\">async\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#FFAB70\">compilation\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">assets\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">assetTags\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">options\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\"> pages\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetchAllProductPages\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(); \u003C/span>\u003Cspan style=\"color:#6A737D\">// appel API Strapi\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\">          noscriptBlocks: pages.\u003C/span>\u003Cspan style=\"color:#B392F0\">map\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#FFAB70\">page\u003C/span>\u003Cspan style=\"color:#F97583\"> =>\u003C/span>\u003Cspan style=\"color:#B392F0\"> generateNoscriptFallback\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(page)),\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\">\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>\u003C/code>\u003C/pre>\n\u003Cp>Le script \u003Ccode>noscript-fallback.js\u003C/code> générait un bloc HTML complet pour chaque page :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// scripts/noscript-fallback.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">function\u003C/span>\u003Cspan style=\"color:#B392F0\"> generateNoscriptFallback\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#FFAB70\">page\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> `\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    &#x3C;noscript>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      &#x3C;article class=\"product-detail\">\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        &#x3C;h1>${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">page\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">title\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}&#x3C;/h1>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        &#x3C;p class=\"price\">${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">page\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">price\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} €&#x3C;/p>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        &#x3C;div class=\"description\">${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">page\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">description\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}&#x3C;/div>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        &#x3C;ul class=\"specs\">\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">page\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">specs\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">map\u003C/span>\u003Cspan style=\"color:#9ECBFF\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">s\u003C/span>\u003Cspan style=\"color:#F97583\"> =>\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> `&#x3C;li>${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">s\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}&#x3C;/li>`\u003C/span>\u003Cspan style=\"color:#9ECBFF\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">join\u003C/span>\u003Cspan style=\"color:#9ECBFF\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#9ECBFF\">)\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        &#x3C;/ul>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      &#x3C;/article>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    &#x3C;/noscript>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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:#79B8FF\">module\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#79B8FF\">exports\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> generateNoscriptFallback;\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>L'intention du développeur venait d'un article de blog de 2019 recommandant les fallbacks \u003Ccode>&#x3C;noscript>\u003C/code> pour le SEO des SPA. Ce conseil était déjà discutable à l'époque. En 2026, avec la sophistication du WRS de Google, c'est une bombe à retardement.\u003C/p>\n\u003Ch3>Pourquoi les tests n'ont rien détecté\u003C/h3>\n\u003Cp>L'équipe avait des tests Cypress end-to-end. Mais tous les tests s'exécutent avec JavaScript activé. Personne n'a jamais testé le HTML brut servi au premier hit HTTP.\u003C/p>\n\u003Cp>Le pipeline CI/CD incluait un check Lighthouse. Lighthouse exécute JavaScript — il ne voit jamais le contenu \u003Ccode>&#x3C;noscript>\u003C/code> comme contenu principal.\u003C/p>\n\u003Cp>Le seul outil qui aurait pu détecter la divergence : un diff entre le HTML brut (\u003Ccode>curl\u003C/code>) et le DOM rendu après exécution JS. Ce test n'existait pas.\u003C/p>\n\u003Cp>Pour reproduire le problème, une simple commande suffisait :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://www.example.com/produit/trailmax-3000\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\"\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:#79B8FF\"> -A\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 20\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"&#x3C;noscript>\"\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le résultat affiche l'intégralité du contenu produit dans le \u003Ccode>&#x3C;noscript>\u003C/code>. En comparant avec ce que voit un utilisateur réel avant l'exécution JS (un spinner), la divergence est flagrante.\u003C/p>\n\u003Cp>Ce pattern rappelle un autre piège courant des SPA : le contenu principal caché derrière des directives conditionnelles. Sur Vue.js, le même problème survient avec \u003Ccode>v-if\u003C/code> sur les sections critiques, comme documenté dans \u003Ca href=\"/blog/lazy-load-du-hero-h1-dans-une-section-v-if-invisible-au-fetch-http-brut\">cet incident sur une section hero invisible au fetch HTTP brut\u003C/a>.\u003C/p>\n\u003Ch3>La nuance cloaking vs. fallback légitime\u003C/h3>\n\u003Cp>La \u003Ca href=\"https://developers.google.com/search/docs/essentials/cloaking\">documentation Google sur le cloaking\u003C/a> est explicite : servir un contenu différent aux moteurs de recherche et aux utilisateurs constitue une violation des consignes. Le fait que l'intention soit \"bonne\" (aider le bot à comprendre la page) ne change rien au diagnostic.\u003C/p>\n\u003Cp>Un usage légitime de \u003Ccode>&#x3C;noscript>\u003C/code> existe : afficher un message du type \"Ce site nécessite JavaScript\" ou fournir un lien alternatif. Injecter le contenu complet de la page — avec H1, prix, description structurée — dans un \u003Ccode>&#x3C;noscript>\u003C/code> alors que ce contenu n'est visible qu'après exécution JS pour les vrais utilisateurs, c'est exactement ce que Google considère comme du cloaking.\u003C/p>\n\u003Cp>Ce problème est distinct mais connexe d'un autre pattern toxique : les balises meta manipulées par des conditions CSS ou JS. Un cas similaire a été documenté lors d'un \u003Ca href=\"/blog/a-b-test-header-la-variante-b-sert-un-noindex-a-50-du-trafic-pendant-9-jours\">A/B test qui servait un noindex à 50 % du trafic\u003C/a>, où la divergence entre les versions provoquait une désindexation progressive.\u003C/p>\n\u003Ch2>Le fix : supprimer, rendre en SSR, et demander la réexamen\u003C/h2>\n\u003Cp>Le correctif s'est déroulé en trois phases, étalées sur 48 heures de travail intense.\u003C/p>\n\u003Ch3>Phase 1 — Suppression immédiate du noscript (T+0)\u003C/h3>\n\u003Cp>Priorité absolue : retirer le composant \u003Ccode>&#x3C;SeoFallback>\u003C/code> et tous les blocs \u003Ccode>&#x3C;noscript>\u003C/code> contenant du contenu produit. Le revert Git est propre — un seul commit à annuler.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">git\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> revert\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 3f7a2e1\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --no-edit\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">git\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> push\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> origin\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> main\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le déploiement est poussé en production en 8 minutes via le pipeline Vercel. Le CDN Cloudflare est purgé manuellement pour les 1 247 URLs concernées :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Purge Cloudflare via API — batch de 30 URLs max par appel\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">cat\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> affected-urls.txt\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> xargs\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -n\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 30\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -I\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> {}\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -X\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> POST\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://api.cloudflare.com/client/v4/zones/\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$ZONE_ID\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/purge_cache\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Authorization: Bearer \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$CF_TOKEN\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Content-Type: application/json\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --data\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '{\"files\": [{}]}'\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Phase 2 — Mise en place d'un pre-rendering SSR minimal (T+12h)\u003C/h3>\n\u003Cp>Supprimer le \u003Ccode>&#x3C;noscript>\u003C/code> ne suffit pas. Le problème initial reste : le HTML brut servi par le SPA React ne contient que le splash screen. Googlebot WRS finira par rendre la page, mais le HTML initial doit être propre.\u003C/p>\n\u003Cp>L'équipe opte pour un pré-rendu statique via \u003Ccode>react-snap\u003C/code>, configuré pour générer le HTML complet de chaque page produit au build :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// package.json — extrait\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  \"scripts\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"postbuild\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"react-snap\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  \"reactSnap\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"source\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"build\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"puppeteerArgs\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"--no-sandbox\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"include\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/produit/\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"minifyHtml\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      \"collapseWhitespace\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      \"removeComments\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"inlineCss\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"skipThirdPartyRequests\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\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>\u003C/code>\u003C/pre>\n\u003Cp>Avec cette configuration, chaque URL \u003Ccode>/produit/*\u003C/code> est pré-rendue avec le contenu complet directement dans le \u003Ccode>&#x3C;div id=\"root\">\u003C/code>. Le \u003Ccode>&#x3C;title>\u003C/code> et la meta description sont corrects dès le premier octet. Plus de splash screen pour Googlebot. Plus de divergence.\u003C/p>\n\u003Cp>L'alternative aurait été de migrer vers Next.js ou Remix pour du vrai SSR. L'équipe n'avait pas le budget de temps. \u003Ccode>react-snap\u003C/code> a résolu le problème en une demi-journée. Ce n'est pas élégant. C'est efficace.\u003C/p>\n\u003Cp>Ce choix d'architecture rejoint les problématiques rencontrées lors de \u003Ca href=\"/blog/migration-angular-17-vers-ssr-provideserverrendering-mal-configure-et-hydration-mismatch-invisible\">migrations vers le SSR Angular où le \u003Ccode>provideServerRendering\u003C/code> mal configuré provoque des hydration mismatches invisibles\u003C/a>.\u003C/p>\n\u003Ch3>Phase 3 — Demande de réexamen (T+24h)\u003C/h3>\n\u003Cp>L'action manuelle nécessite une demande de réexamen dans Search Console. L'équipe rédige un message précis :\u003C/p>\n\u003Cul>\n\u003Cli>Description du problème identifié (blocs \u003Ccode>&#x3C;noscript>\u003C/code> contenant du contenu dupliqué)\u003C/li>\n\u003Cli>Date du commit fautif et date du revert\u003C/li>\n\u003Cli>Preuve que le HTML brut actuel sert le contenu identique à ce que voit un utilisateur\u003C/li>\n\u003Cli>URL d'exemple avant/après\u003C/li>\n\u003C/ul>\n\u003Cp>La demande est soumise le mercredi. La réponse de Google arrive le lundi suivant : action manuelle levée. Délai total : 5 jours ouvrés.\u003C/p>\n\u003Ch3>Récupération du trafic\u003C/h3>\n\u003Cp>La réindexation des 1 247 URLs prend 9 jours après la levée de l'action manuelle. Le trafic organique retrouve 85 % de son niveau d'avant-incident au bout de 14 jours. Les 15 % restants reviennent progressivement sur les 3 semaines suivantes, à mesure que Google recrawle et re-rank les pages.\u003C/p>\n\u003Cp>Impact total estimé : 21 jours de perturbation, environ 42 000 clics organiques perdus, soit un manque à gagner de 95 000 € pour ce e-commerce.\u003C/p>\n\u003Ch3>Les garde-fous mis en place\u003C/h3>\n\u003Cp>L'équipe ajoute trois contrôles au pipeline CI/CD :\u003C/p>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Lint HTML statique\u003C/strong> : un script Node qui parse le HTML généré et échoue si un \u003Ccode>&#x3C;noscript>\u003C/code> contient plus de 50 caractères de texte (seuil pour un simple message \"Activez JavaScript\").\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Diff SSR/CSR automatisé\u003C/strong> : à chaque PR, un job compare le HTML brut (\u003Ccode>curl\u003C/code>) avec le DOM rendu par Puppeteer. Si la divergence textuelle dépasse 20 %, le build échoue.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Alerte Search Console\u003C/strong> : notification Slack immédiate sur les actions manuelles et les chutes d'indexation supérieures à 5 % sur 48h.\u003C/p>\n\u003C/li>\n\u003C/ol>\n\u003Cp>Ce type de vérification SSR/CSR rejoint la logique de surveillance décrite dans l'incident de \u003Ca href=\"/blog/migration-next-js-pages-router-vers-app-router-les-metadata-ignorees-sur-les-pages-client\">migration Next.js Pages Router vers App Router, où les metadata étaient ignorées sur les pages client\u003C/a> faute de monitoring du HTML initial.\u003C/p>\n\u003Ch2>Ce qu'on en retient\u003C/h2>\n\u003Cp>Le \u003Ccode>&#x3C;noscript>\u003C/code> n'est pas un outil SEO. C'est un mécanisme de fallback pour les navigateurs sans JavaScript. Y injecter du contenu riche quand le reste de la page est un SPA vide, c'est créer une divergence que Google qualifie de cloaking — même sans intention malveillante.\u003C/p>\n\u003Cp>Trois règles pour les SPA :\u003C/p>\n\u003Cul>\n\u003Cli>Le HTML initial doit contenir le même contenu que le DOM rendu. Pas un spinner. Pas un placeholder.\u003C/li>\n\u003Cli>Si le SSR complet n'est pas envisageable, le pré-rendu statique est un compromis viable.\u003C/li>\n\u003Cli>La divergence entre HTML brut et DOM final doit être testée automatiquement, à chaque déploiement.\u003C/li>\n\u003C/ul>\n\u003Cp>Un outil de monitoring continu comme Seogard détecte ce type de divergence SSR/CSR en quelques minutes après le déploiement — pas onze jours plus tard, quand Search Console envoie un email d'action manuelle.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21],"noscript","cloaking","spa","splash","noscript cloaking : splash screen SPA piège Google","Sat Jun 06 2026 06:01:52 GMT+0000 (Coordinated Universal Time)",[25,41,56,65,77,90],{"_id":26,"slug":27,"__v":6,"author":7,"canonical":28,"category":10,"createdAt":29,"date":30,"description":31,"image":15,"imageAlt":15,"readingTime":32,"tags":33,"title":39,"updatedAt":40},"6a21a15daa6b273b0cb36647","lazy-load-du-hero-h1-dans-une-section-v-if-invisible-au-fetch-http-brut","https://seogard.io/blog/lazy-load-du-hero-h1-dans-une-section-v-if-invisible-au-fetch-http-brut","2026-06-04T16:01:33.508Z","2026-06-04","Un hero section en v-if masque le H1 au SSR. Récit d'une régression silencieuse sur 320 pages, diagnostic technique et fix Nuxt complet.",11,[34,35,36,37,38],"lazy load","hero","ssr","h1","vue","Lazy-load du hero Vue : H1 invisible pour Google","Thu Jun 04 2026 16:01:33 GMT+0000 (Coordinated Universal Time)",{"_id":42,"slug":43,"__v":6,"author":7,"canonical":44,"category":10,"createdAt":45,"date":46,"description":47,"image":15,"imageAlt":15,"readingTime":16,"tags":48,"title":54,"updatedAt":55},"69d1b816c84600c5cb7faaad","ssr-vs-csr-impact-reel-sur-le-seo","https://seogard.io/blog/ssr-vs-csr-impact-reel-sur-le-seo","2026-04-05T01:17:10.132Z","2026-04-05","Comparaison technique SSR et CSR avec exemples de crawl, code et scénarios concrets. Ce que Googlebot voit vraiment selon votre mode de rendering.",[36,49,50,51,52,53],"csr","rendering","seo","javascript","googlebot","SSR vs CSR : impact réel sur le SEO technique","Sun Apr 05 2026 01:19:09 GMT+0000 (Coordinated Universal Time)",{"_id":57,"slug":58,"__v":6,"author":7,"canonical":59,"category":10,"createdAt":60,"date":46,"description":61,"image":15,"imageAlt":15,"readingTime":16,"tags":62,"title":63,"updatedAt":64},"69d1b986c84600c5cb8052d0","pourquoi-google-voit-une-page-blanche-sur-votre-spa","https://seogard.io/blog/pourquoi-google-voit-une-page-blanche-sur-votre-spa","2026-04-05T01:23:18.128Z","Diagnostic technique complet des problèmes de rendering JavaScript sur les SPA. Solutions SSR, prerendering et monitoring pour Googlebot.",[20,52,50,53,36],"Google voit une page blanche sur votre SPA : diagnostic et solutions","Sun Apr 05 2026 01:25:04 GMT+0000 (Coordinated Universal Time)",{"_id":66,"slug":67,"__v":6,"author":7,"canonical":68,"category":10,"createdAt":69,"date":46,"description":70,"image":15,"imageAlt":15,"readingTime":16,"tags":71,"title":75,"updatedAt":76},"69d1bae9c84600c5cb80f61b","hydration-mismatch-le-bug-invisible-qui-tue-votre-seo","https://seogard.io/blog/hydration-mismatch-le-bug-invisible-qui-tue-votre-seo","2026-04-05T01:29:13.018Z","Détectez et corrigez les erreurs d'hydratation SSR qui dégradent silencieusement votre indexation. Méthodes, outils et code pour debug avancé.",[72,36,73,74,50],"hydration","mismatch","debug","Hydration mismatch : le bug invisible qui tue votre SEO","Sun Apr 05 2026 01:31:19 GMT+0000 (Coordinated Universal Time)",{"_id":78,"slug":79,"__v":6,"author":7,"canonical":80,"category":10,"createdAt":81,"date":46,"description":82,"image":15,"imageAlt":15,"readingTime":16,"tags":83,"title":88,"updatedAt":89},"69d1bc48c84600c5cb819800","isr-ssr-ssg-quel-mode-de-rendering-pour-le-seo","https://seogard.io/blog/isr-ssr-ssg-quel-mode-de-rendering-pour-le-seo","2026-04-05T01:35:04.805Z","Guide technique pour choisir entre ISR, SSR et SSG selon votre type de site. Comparatif, code Next.js/Nuxt, et scénarios réels e-commerce, média, SaaS.",[84,36,85,50,86,87],"isr","ssg","nextjs","nuxt","ISR, SSR, SSG : quel rendering choisir pour le SEO","Sun Apr 05 2026 01:37:13 GMT+0000 (Coordinated Universal Time)",{"_id":91,"slug":92,"__v":6,"author":7,"canonical":93,"category":10,"createdAt":94,"date":46,"description":95,"image":15,"imageAlt":15,"readingTime":16,"tags":96,"title":99,"updatedAt":100},"69d1be04c84600c5cb8262f0","prerendering-quand-et-comment-l-utiliser-pour-le-seo","https://seogard.io/blog/prerendering-quand-et-comment-l-utiliser-pour-le-seo","2026-04-05T01:42:28.066Z","Guide technique du prerendering pour le SEO : cas d'usage concrets, implémentation avec Next.js, Nuxt, Astro, et pièges à éviter sur les SPA.",[97,51,20,98,50],"prerendering","crawl","Prerendering SEO : quand et comment l'implémenter","Sun Apr 05 2026 01:44:05 GMT+0000 (Coordinated Universal Time)",{"categories":102},[103,107,111,113,117,120,122,125,129,133,136,139,143,146,149,152,155,159],{"category":104,"slug":105,"count":106},"Actualités SEO","actualites-seo",161,{"category":108,"slug":109,"count":110},"Migration","migration",18,{"category":10,"slug":50,"count":112},9,{"category":114,"slug":115,"count":116},"Performance","performance",7,{"category":118,"slug":119,"count":116},"SEO Technique","seo-technique",{"category":121,"slug":98,"count":116},"Crawl",{"category":123,"slug":124,"count":116},"Meta Tags","meta-tags",{"category":126,"slug":127,"count":128},"Architecture","architecture",6,{"category":130,"slug":131,"count":132},"JavaScript SEO","javascript-seo",5,{"category":134,"slug":135,"count":132},"Monitoring","monitoring",{"category":137,"slug":138,"count":132},"Structured Data","structured-data",{"category":140,"slug":141,"count":142},"Refonte","refonte",4,{"category":144,"slug":145,"count":142},"Redirections","redirections",{"category":147,"slug":148,"count":142},"Outils","outils",{"category":150,"slug":151,"count":142},"E-commerce","e-commerce",{"category":153,"slug":154,"count":142},"Avancé","avance",{"category":156,"slug":157,"count":158},"IA & SEO","ia-seo",3,{"category":160,"slug":161,"count":158},"Contenu","contenu"]