[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ffFmrWpMu6uOXQi1dKsmlMh9QikcH0_Tjnc3jtnwV0Zk":3,"$f7SQgzBlQpBjxFc1QKSeAgatL1tg6fjYGF3TVKcnnNhs":24},{"_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},"69d08d563c1c869ceaa62f45","mobile-first-indexing-verifier-que-votre-site-est-pret",0,"Equipe Seogard","Google crawle et indexe votre site avec un user-agent mobile. Depuis mars 2025, il n'y a plus d'exception : tous les sites sont passés au mobile-first indexing. Si la version mobile de vos pages omet du contenu, des liens internes ou des données structurées présentes sur desktop, vous avez un problème d'indexation — pas un problème de responsive design.\n\n## Ce que mobile-first indexing signifie réellement (et ce qu'il ne signifie pas)\n\nMobile-first indexing ne veut pas dire \"Google favorise les sites mobiles dans le classement\". Cela signifie que Googlebot utilise exclusivement le user-agent smartphone pour découvrir, crawler et indexer vos pages. Le contenu que Googlebot voit sur la version mobile est celui qui entre dans l'index. Point.\n\nConcrètement, Googlebot envoie des requêtes avec cet user-agent :\n\n```\nMozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\n```\n\nSi votre serveur détecte cet user-agent et retourne une version allégée de la page — moins de texte, moins de liens internes, pas de structured data — c'est cette version appauvrie qui sera indexée. Pas celle que vous voyez sur votre desktop.\n\n### Les trois architectures et leurs risques\n\n**Responsive design** (une seule URL, même HTML, CSS media queries) : c'est le cas le plus sûr. Le contenu est identique ; seule la présentation change. Mais attention : du contenu masqué via `display: none` ou des tabs/accordéons JavaScript peut ne pas être rendu par Googlebot si le JS échoue.\n\n**Dynamic serving** (une seule URL, HTML différent selon le user-agent) : le risque principal est la parité de contenu. Un middleware qui détecte le user-agent et sert un template mobile simplifié peut supprimer des sections entières — FAQ, tableaux comparatifs, blocs de liens internes — sans que personne ne s'en rende compte.\n\n**URLs séparées** (`m.example.com`) : le cas le plus dangereux. Vous maintenez deux bases de contenu. La version mobile est souvent un sous-ensemble appauvri de la version desktop. Et les canonical/alternate `rel` doivent être irréprochables pour éviter les problèmes de [contenu dupliqué](/blog/contenu-duplique-causes-techniques-et-solutions).\n\nLa recommandation officielle de Google depuis des années est le responsive design. Si vous êtes encore sur une architecture `m.` en 2026, la migration devrait être votre priorité.\n\n## Vérifier la parité de contenu entre mobile et desktop\n\nC'est le point le plus critique. Quand on parle de parité, on parle de trois choses distinctes : le contenu textuel, les liens internes, et les données structurées.\n\n### Contenu textuel\n\nChaque élément de contenu visible sur desktop doit être présent dans le DOM de la version mobile. Cela inclut les textes dans les accordéons, les onglets, les sections \"lire la suite\".\n\nGoogle a confirmé que le contenu dans les accordéons et les tabs est traité comme du contenu normal, même s'il nécessite une interaction utilisateur pour être affiché. Mais — et c'est un edge case fréquent — si ce contenu est chargé via un appel AJAX au clic (lazy-loaded), Googlebot ne le verra pas. Le contenu doit être dans le DOM initial ou dans le HTML rendu côté serveur.\n\nVoici un pattern problématique courant en React :\n\n```tsx\n// ❌ Problème : le contenu de l'accordéon n'est chargé qu'au clic\nfunction ProductFAQ({ productId }: { productId: string }) {\n  const [faqData, setFaqData] = useState(null);\n  const [isOpen, setIsOpen] = useState(false);\n\n  const handleClick = async () => {\n    if (!faqData) {\n      const res = await fetch(`/api/faq/${productId}`);\n      setFaqData(await res.json());\n    }\n    setIsOpen(!isOpen);\n  };\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={handleClick}>Questions fréquentes\u003C/button>\n      {isOpen && faqData && (\n        \u003Cdiv className=\"faq-content\">\n          {faqData.map((item: any) => (\n            \u003Cdiv key={item.id}>\n              \u003Ch3>{item.question}\u003C/h3>\n              \u003Cp>{item.answer}\u003C/p>\n            \u003C/div>\n          ))}\n        \u003C/div>\n      )}\n    \u003C/div>\n  );\n}\n```\n\nLa version corrigée pré-charge les données et inclut le contenu dans le DOM initial :\n\n```tsx\n// ✅ Le contenu est dans le DOM dès le rendu initial (SSR ou SSG)\nfunction ProductFAQ({ faqData }: { faqData: FAQItem[] }) {\n  const [isOpen, setIsOpen] = useState(false);\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => setIsOpen(!isOpen)} aria-expanded={isOpen}>\n        Questions fréquentes\n      \u003C/button>\n      {/* Contenu dans le DOM, masqué visuellement par CSS */}\n      \u003Cdiv\n        className=\"faq-content\"\n        style={{ display: isOpen ? 'block' : 'none' }}\n      >\n        {faqData.map((item) => (\n          \u003Cdiv key={item.id}>\n            \u003Ch3>{item.question}\u003C/h3>\n            \u003Cp>{item.answer}\u003C/p>\n          \u003C/div>\n        ))}\n      \u003C/div>\n    \u003C/div>\n  );\n}\n\n// Les données sont chargées côté serveur (Next.js App Router)\nexport async function generateStaticParams() { /* ... */ }\n```\n\nPour comparer systématiquement le DOM mobile et desktop, la méthode la plus fiable est d'utiliser Screaming Frog en mode rendu JavaScript, en faisant deux crawls : un avec le user-agent Googlebot desktop, un avec Googlebot mobile. Exportez les deux datasets et comparez le word count, le nombre de liens internes, et la présence des balises `\u003Ch1>` à `\u003Ch3>` par URL. Sur un site de 8 000 pages, cela prend environ 45 minutes de crawl par passe (avec un crawl rate raisonnable de 3 URLs/seconde).\n\nPour des comparaisons plus fines entre le rendu SSR et le rendu CSR, [cet article sur les divergences SSR/CSR](/blog/comparer-ssr-et-csr-detecter-les-divergences-invisibles) détaille la méthodologie complète.\n\n### Liens internes\n\nC'est le piège le plus fréquent. Beaucoup de sites desktop utilisent des [mega menus](/blog/mega-menus-et-seo-attention-au-crawl-budget) avec des centaines de liens internes. Sur mobile, ce mega menu est remplacé par un hamburger menu simplifié qui ne contient que les catégories de premier niveau.\n\nRésultat : des dizaines de pages profondes perdent leurs liens internes entrants depuis la navigation. Pour Googlebot mobile, ces pages deviennent plus difficiles à découvrir et à crawler. L'impact sur le crawl budget est direct — sujet que nous détaillons dans l'article sur les [mega menus et le crawl budget](/blog/mega-menus-et-seo-attention-au-crawl-budget).\n\nVérifiez la distribution des liens internes entre les deux versions. Si votre menu desktop contient 120 liens et votre menu mobile 25, vous avez un delta de 95 liens internes sur chaque page du site. Multipliez par 8 000 pages et vous comprenez l'impact sur la structure de liens interne.\n\n### Données structurées\n\nToutes les balises `\u003Cscript type=\"application/ld+json\">` présentes sur desktop doivent être identiques sur mobile. Pas \"similaires\" — identiques. Même chose pour les microdonnées `itemscope` si vous les utilisez encore.\n\nUn cas typique : un site e-commerce injecte les données Product structured data uniquement via un composant qui n'est rendu que sur desktop. La version mobile affiche bien le prix et les avis, mais le JSON-LD est absent du HTML.\n\nUtilisez le test de résultat enrichi de Google ([search.google.com/test/rich-results](https://search.google.com/test/rich-results)) en mode mobile pour vérifier chaque template.\n\n## Diagnostiquer les problèmes de rendu mobile avec les bons outils\n\n### Search Console : l'inspection d'URL en mode mobile\n\nL'outil \"Inspection d'URL\" de la Search Console affiche le HTML rendu tel que Googlebot le voit. Depuis le passage complet au mobile-first, ce rendu est celui du user-agent mobile. Regardez deux choses :\n\n1. **Le HTML rendu** : cliquez sur \"Afficher la page testée\" > \"HTML\". Cherchez-y vos contenus critiques (descriptions produit, FAQ, liens internes de navigation).\n2. **La capture d'écran** : elle montre le rendu visuel. Si des éléments se chevauchent, débordent de l'écran ou sont trop petits pour être tapés, Google le voit aussi.\n\nSi vous constatez des écarts entre ce que vous attendez et ce que Google voit, il y a probablement un problème de rendu JavaScript ou de serving conditionnel. L'article sur les [rapports Search Console souvent ignorés](/blog/google-search-console-les-rapports-que-vous-ignorez) couvre d'autres checks utiles dans la console.\n\n### Chrome DevTools : simuler Googlebot mobile\n\nLa méthode la plus rapide pour un diagnostic ponctuel :\n\n1. Ouvrez Chrome DevTools (F12)\n2. Activez le Device Mode (Ctrl+Shift+M)\n3. Sélectionnez \"Nexus 5X\" comme device (c'est le device de référence de Googlebot)\n4. Dans l'onglet Network, cochez \"Disable cache\"\n5. Dans le menu à trois points > More tools > Network conditions, remplacez le user-agent par celui de Googlebot mobile\n\nCela vous donne une approximation du rendu. Ce n'est pas identique au rendu de Googlebot (qui utilise une version headless de Chrome avec des spécificités), mais cela détecte les problèmes les plus flagrants. Pour aller plus loin, consultez nos [astuces avancées avec Chrome DevTools pour le SEO](/blog/chrome-devtools-pour-le-seo-astuces-avancees).\n\n### Screaming Frog : crawl comparatif desktop vs mobile\n\nVoici la configuration pour un crawl mobile-first dans Screaming Frog :\n\n1. **Configuration > User-Agent** : sélectionnez \"Googlebot Smartphone\"\n2. **Configuration > Spider > Rendering** : activez \"JavaScript\" avec le moteur Chromium\n3. **Configuration > Spider > Crawl** : limitez à vos templates principaux via des regex d'inclusion si le site est volumineux\n\nLancez un premier crawl en Googlebot Smartphone, exportez en CSV. Lancez un second en Googlebot Desktop, exportez. Comparez ces colonnes par URL :\n\n- `Word Count` (parité de contenu)\n- `Inlinks` (parité de liens internes)\n- `Structured Data` (parité des données structurées)\n- `H1`, `H2 Count` (structure de heading)\n- `Indexability` (mêmes pages indexables dans les deux versions)\n\nUn script Python rapide pour automatiser la comparaison :\n\n```python\nimport pandas as pd\n\nmobile = pd.read_csv('crawl_mobile.csv', usecols=['Address', 'Word Count', 'Inlinks', 'Indexability'])\ndesktop = pd.read_csv('crawl_desktop.csv', usecols=['Address', 'Word Count', 'Inlinks', 'Indexability'])\n\nmerged = mobile.merge(desktop, on='Address', suffixes=('_mobile', '_desktop'))\n\n# Pages où le contenu mobile est significativement plus court\nmerged['word_delta'] = merged['Word Count_desktop'] - merged['Word Count_mobile']\ncontent_gaps = merged[merged['word_delta'] > 100].sort_values('word_delta', ascending=False)\n\n# Pages où les liens internes diffèrent fortement\nmerged['link_delta'] = merged['Inlinks_desktop'] - merged['Inlinks_mobile']\nlink_gaps = merged[merged['link_delta'] > 10].sort_values('link_delta', ascending=False)\n\n# Pages indexables sur desktop mais pas sur mobile\nindexability_issues = merged[\n    (merged['Indexability_desktop'] == 'Indexable') &\n    (merged['Indexability_mobile'] != 'Indexable')\n]\n\nprint(f\"Pages avec écart de contenu > 100 mots : {len(content_gaps)}\")\nprint(f\"Pages avec écart de liens internes > 10 : {len(link_gaps)}\")\nprint(f\"Pages indexables desktop mais pas mobile : {len(indexability_issues)}\")\n\ncontent_gaps.to_csv('content_parity_issues.csv', index=False)\nlink_gaps.to_csv('link_parity_issues.csv', index=False)\nindexability_issues.to_csv('indexability_issues.csv', index=False)\n```\n\n## Scénario concret : migration d'un e-commerce de 15K pages\n\nVoici un cas réaliste. Un site e-commerce de vêtements — 15 000 pages produit, 200 pages catégorie, 50 pages de contenu éditorial — tourne sur une architecture dynamic serving. Le backend Django détecte le user-agent et sert des templates différents pour mobile et desktop.\n\n### Le diagnostic\n\nUn crawl Screaming Frog comparatif révèle :\n\n- **2 300 pages produit** ont un word count mobile inférieur de 150+ mots au desktop. Cause : le tableau des caractéristiques techniques (`\u003Ctable class=\"specs\">`) est supprimé du template mobile pour \"gagner de la place\".\n- **180 pages catégorie** perdent entre 40 et 90 liens internes sur mobile. Cause : la sidebar desktop affiche des liens vers les sous-catégories et les filtres populaires. Sur mobile, cette sidebar est purement supprimée (pas repliée — supprimée du DOM).\n- **Toutes les pages produit** perdent le JSON-LD `Product`. Le bloc `\u003Cscript type=\"application/ld+json\">` est inclus dans un partial Django chargé uniquement dans le template desktop : `{% include \"partials/structured_data.html\" %}` est absent du template `product_mobile.html`.\n\n### L'impact mesuré\n\nAprès investigation dans la Search Console, les résultats enrichis (prix, disponibilité, avis) ont progressivement disparu des SERPs sur 4 mois — période pendant laquelle Google a basculé l'indexation du site vers mobile-first. Le trafic organique a baissé de 23% sur les pages produit, non pas à cause d'une pénalité, mais parce que :\n\n1. La perte des résultats enrichis a réduit le CTR d'environ 15-20% (les snippets avec prix et étoiles attirent mécaniquement plus de clics).\n2. Le contenu indexé étant plus pauvre (pas de tableau de spécifications), les pages se positionnent moins bien sur les requêtes longue traîne liées aux caractéristiques techniques.\n3. La perte de liens internes sur mobile réduit le PageRank interne vers les sous-catégories, affectant leur crawl et leur indexation.\n\n### La correction\n\nLa solution retenue : migration vers un responsive design unique. Plus de dynamic serving, un seul template par type de page. Le tableau de spécifications est affiché dans un accordéon sur mobile (contenu dans le DOM, masqué par CSS). La sidebar est transformée en section horizontale scrollable en haut de la page catégorie sur mobile. Le JSON-LD est inclus dans le template de base, indépendamment du device.\n\nTemps de correction : 3 semaines de développement, 1 semaine de QA, déploiement progressif par template. Récupération du trafic perdu en environ 6 semaines après le re-crawl complet par Google.\n\nCe type de régression silencieuse — où le contenu disparaît sans qu'aucune alerte ne se déclenche — est exactement ce qui justifie un [monitoring SEO continu](/blog/monitoring-seo-pourquoi-les-audits-ponctuels-ne-suffisent-plus) plutôt que des audits trimestriels. Un outil comme Seogard aurait détecté la disparition du structured data et la chute du word count dès le premier crawl post-changement.\n\n## Performance mobile : au-delà du responsive\n\nAvoir un contenu paritaire ne suffit pas si vos pages mettent 8 secondes à se charger sur mobile. Google utilise les Core Web Vitals mesurés sur les sessions mobiles réelles (données CrUX) comme signal de classement.\n\n### LCP mobile : le goulot d'étranglement le plus fréquent\n\nLe Largest Contentful Paint sur mobile est presque toujours plus élevé que sur desktop, pour trois raisons : bande passante plus faible, CPU moins puissant, et images souvent surdimensionnées.\n\nVérifiez votre config d'images responsive :\n\n```html\n\u003C!-- ❌ Image unique en pleine résolution -->\n\u003Cimg src=\"/images/product-hero-1920x1080.jpg\" alt=\"Robe en lin naturel\">\n\n\u003C!-- ✅ Images responsive avec srcset et sizes -->\n\u003Cimg\n  src=\"/images/product-hero-800x450.webp\"\n  srcset=\"\n    /images/product-hero-400x225.webp 400w,\n    /images/product-hero-800x450.webp 800w,\n    /images/product-hero-1200x675.webp 1200w,\n    /images/product-hero-1920x1080.webp 1920w\n  \"\n  sizes=\"(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px\"\n  alt=\"Robe en lin naturel\"\n  width=\"1920\"\n  height=\"1080\"\n  loading=\"eager\"\n  fetchpriority=\"high\"\n>\n```\n\nL'attribut `fetchpriority=\"high\"` sur l'image LCP est sous-utilisé. Il indique au navigateur de prioriser le téléchargement de cette ressource, ce qui réduit le LCP de 100-300ms sur des connexions 4G selon les tests documentés sur [web.dev](https://web.dev/articles/fetch-priority).\n\nPour un suivi continu de ces métriques, [Lighthouse CI](/blog/lighthouse-ci-surveiller-la-performance-en-continu) est l'approche la plus robuste en intégration continue.\n\n### Le viewport meta tag\n\nBasique mais toujours source d'erreurs. Si cette balise est absente ou mal configurée, Google considère que la page n'est pas adaptée au mobile :\n\n```html\n\u003C!-- La seule valeur correcte -->\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n\u003C!-- ❌ Problématique : empêche le zoom (accessibilité + signal négatif) -->\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n```\n\nLe `user-scalable=no` et `maximum-scale=1` ne sont pas des signaux négatifs directs pour le classement, mais ils sont flaggés dans Lighthouse comme des problèmes d'accessibilité. Et un site qui bloque le zoom sera moins bien noté sur le rapport \"Expérience sur la page\" de la Search Console.\n\n### Ressources bloquées par robots.txt\n\nUn piège classique : votre fichier `robots.txt` bloque des fichiers CSS ou JavaScript nécessaires au rendu mobile. Si Googlebot ne peut pas charger votre framework CSS responsive, il voit la page sans mise en forme — et peut considérer que le contenu n'est pas mobile-friendly.\n\nVérifiez avec l'outil d'inspection d'URL de la Search Console. Dans la section \"Plus d'informations\" > \"Ressources de la page\", cherchez les ressources marquées comme bloquées. Les CDN tiers (polices, CSS frameworks) sont les coupables les plus fréquents.\n\n## Configuration serveur : les pièges du dynamic serving\n\nSi vous servez un contenu différent selon le user-agent (dynamic serving), vous devez envoyer le header HTTP `Vary: User-Agent`. Sans ce header, les caches intermédiaires (CDN, proxy) peuvent servir la version desktop à Googlebot mobile, ou inversement.\n\n```nginx\n# Configuration Nginx pour dynamic serving\nserver {\n    listen 443 ssl;\n    server_name shop.example.fr;\n\n    # Détection mobile via user-agent\n    set $mobile_rewrite do_not_perform;\n\n    if ($http_user_agent ~* \"(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino\") {\n        set $mobile_rewrite perform;\n    }\n\n    location / {\n        # Header Vary obligatoire pour le cache\n        add_header Vary \"User-Agent\";\n\n        if ($mobile_rewrite = perform) {\n            # Servir le template mobile\n            proxy_pass http://backend_mobile;\n            break;\n        }\n\n        proxy_pass http://backend_desktop;\n    }\n}\n```\n\nDeux points importants :\n\n1. **Le header `Vary: User-Agent`** est essentiel. Sans lui, Cloudflare ou Fastly pourraient cacher la version desktop et la servir à tous les user-agents pendant la durée du TTL. C'est documenté dans les [recommandations Google sur le mobile-first indexing](https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing).\n\n2. **Les regex de détection user-agent** doivent inclure le user-agent Googlebot mobile. Testez explicitement que votre condition matche `\"Googlebot/2.1\"` combiné avec `\"Mobile\"`. Un mauvais regex est une cause fréquente de serving incorrect.\n\nCela dit, la recommandation reste de migrer vers du responsive design pur. Le dynamic serving est une source constante de bugs et de [régressions SEO](/blog/regressions-seo-les-10-types-les-plus-frequents) difficiles à détecter — surtout lors des déploiements. Un [déploiement mal préparé](/blog/deploiement-vendredi-soir-comment-eviter-la-catastrophe-seo) peut casser la logique de serving sans que personne ne s'en aperçoive pendant des jours.\n\n## Checklist mobile-first : les vérifications à automatiser\n\nVoici les vérifications à intégrer dans votre pipeline CI/CD ou votre routine de monitoring. Chaque point est un signal binaire — ça passe ou ça casse.\n\n### Vérifications de parité\n\n- [ ] Le word count de chaque template en user-agent mobile est ≥ 95% du word count desktop\n- [ ] Le nombre de liens internes dans la navigation est identique (± 5%) entre mobile et desktop\n- [ ] Tous les blocs `\u003Cscript type=\"application/ld+json\">` présents sur desktop sont présents sur mobile\n- [ ] Les balises `\u003Ch1>` sont identiques entre les deux versions\n- [ ] Les balises canonical, hreflang, et meta robots sont identiques\n- [ ] Les images ont des attributs `alt` identiques dans les deux versions\n\n### Vérifications techniques\n\n- [ ] La balise `\u003Cmeta name=\"viewport\">` est présente et correctement configurée\n- [ ] Aucune ressource CSS/JS critique n'est bloquée par `robots.txt`\n- [ ] Le temps de réponse serveur (TTFB) en user-agent mobile est \u003C 800ms\n- [ ] Le LCP mobile est \u003C 2.5s sur les templates principaux\n- [ ] Les éléments interactifs ont une zone de tap ≥ 48x48px\n- [ ] Pas de contenu qui déborde horizontalement (pas de scrollbar horizontal)\n\n### Vérifications de configuration\n\n- [ ] Le header `Vary: User-Agent` est envoyé si le site utilise du dynamic serving\n- [ ] Les redirections mobile (si existantes) fonctionnent dans les deux sens (desktop → mobile ET mobile → desktop)\n- [ ] Les pages `m.` ont des balises `rel=\"canonical\"` qui pointent vers la version desktop, et les pages desktop ont des balises `rel=\"alternate\" media=\"only screen and (max-width: 640px)\"` qui pointent vers la version mobile\n\nL'automatisation de ces checks dans votre pipeline CI/CD est la meilleure assurance contre les régressions. Nous détaillons l'implémentation dans l'article sur [l'automatisation des checks SEO en CI/CD](/blog/automatiser-les-checks-seo-dans-le-ci-cd).\n\nPour les vérifications continues post-déploiement, la configuration des [alertes SEO avec les bons seuils](/blog/alertes-seo-quels-seuils-et-quelle-frequence) est critique. Une chute de 20% du word count sur un template détectée en 24h est récupérable. La même chute détectée 3 mois plus tard lors d'un audit trimestriel a déjà causé des dégâts durables.\n\n## Les edge cases à ne pas ignorer\n\n**Les lazy-loaded iframes** : si vous intégrez des vidéos YouTube ou des widgets tiers via des iframes lazy-loaded, vérifiez que le placeholder contient du contenu textuel pertinent. Googlebot ne chargera pas forcément l'iframe sur la version mobile s'il juge que le viewport ne l'atteint pas. Pour les pages qui reposent sur le [infinite scroll](/blog/infinite-scroll-et-seo-le-guide-technique), le problème est amplifié.\n\n**Les interstitiels mobile** : les pop-ups qui couvrent le contenu principal sur mobile sont un signal négatif explicite depuis janvier 2017. Google ne pénalise pas les interstitiels liés aux cookies (obligation légale), mais un interstitiel marketing qui recouvre 80% de l'écran mobile va impacter votre classement.\n\n**Les pages AMP** : si vous avez encore des pages AMP couplées à des pages canoniques non-AMP, sachez que Google indexe la version canonical (non-AMP). Assurez-vous que la canonical est elle-même mobile-friendly. L'AMP a été en grande partie abandonné comme format prioritaire par Google, mais les implementations legacy restent une source de confusion dans les rapports Search Console.\n\n**Le contenu conditionnel par géolocalisation** : si vous servez du contenu différent en fonction de l'IP (variante de dynamic serving), assurez-vous que le contenu servi à Googlebot — qui crawle depuis des IP américaines — est représentatif de votre contenu cible. C'est particulièrement pertinent si votre [structure d'URL](/blog/url-structure-bonnes-pratiques-seo-pour-les-urls) utilise des sous-répertoires de langue.\n\n---\n\nLe mobile-first indexing n'est plus une migration à planifier — c'est l'état par défaut. La question n'est pas \"quand est-ce que ça arrive\" mais \"est-ce que mon site sert exactement le même contenu, les mêmes liens, les mêmes données structurées à Googlebot mobile qu'à Googlebot desktop\". Si vous n'avez pas fait ce diagnostic récemment, lancez un crawl comparatif cette semaine. Et pour éviter que ce type de divergence ne se réinstalle silencieusement après chaque déploiement, un monitoring continu avec Seogard vous alerte dès qu'une parité de contenu est rompue — avant que l'impact SEO ne soit visible dans vos dashboards de trafic.","https://seogard.io/blog/mobile-first-indexing-verifier-que-votre-site-est-pret","Mobile","2026-04-04T04:02:30.466Z","2026-04-04","Checklist technique pour vérifier que votre site est prêt pour l'indexation mobile-first : parité de contenu, structured data, performance et monitoring.","\u003Cp>Google crawle et indexe votre site avec un user-agent mobile. Depuis mars 2025, il n'y a plus d'exception : tous les sites sont passés au mobile-first indexing. Si la version mobile de vos pages omet du contenu, des liens internes ou des données structurées présentes sur desktop, vous avez un problème d'indexation — pas un problème de responsive design.\u003C/p>\n\u003Ch2>Ce que mobile-first indexing signifie réellement (et ce qu'il ne signifie pas)\u003C/h2>\n\u003Cp>Mobile-first indexing ne veut pas dire \"Google favorise les sites mobiles dans le classement\". Cela signifie que Googlebot utilise exclusivement le user-agent smartphone pour découvrir, crawler et indexer vos pages. Le contenu que Googlebot voit sur la version mobile est celui qui entre dans l'index. Point.\u003C/p>\n\u003Cp>Concrètement, Googlebot envoie des requêtes avec cet user-agent :\u003C/p>\n\u003Cpre>\u003Ccode>Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\n\u003C/code>\u003C/pre>\n\u003Cp>Si votre serveur détecte cet user-agent et retourne une version allégée de la page — moins de texte, moins de liens internes, pas de structured data — c'est cette version appauvrie qui sera indexée. Pas celle que vous voyez sur votre desktop.\u003C/p>\n\u003Ch3>Les trois architectures et leurs risques\u003C/h3>\n\u003Cp>\u003Cstrong>Responsive design\u003C/strong> (une seule URL, même HTML, CSS media queries) : c'est le cas le plus sûr. Le contenu est identique ; seule la présentation change. Mais attention : du contenu masqué via \u003Ccode>display: none\u003C/code> ou des tabs/accordéons JavaScript peut ne pas être rendu par Googlebot si le JS échoue.\u003C/p>\n\u003Cp>\u003Cstrong>Dynamic serving\u003C/strong> (une seule URL, HTML différent selon le user-agent) : le risque principal est la parité de contenu. Un middleware qui détecte le user-agent et sert un template mobile simplifié peut supprimer des sections entières — FAQ, tableaux comparatifs, blocs de liens internes — sans que personne ne s'en rende compte.\u003C/p>\n\u003Cp>\u003Cstrong>URLs séparées\u003C/strong> (\u003Ccode>m.example.com\u003C/code>) : le cas le plus dangereux. Vous maintenez deux bases de contenu. La version mobile est souvent un sous-ensemble appauvri de la version desktop. Et les canonical/alternate \u003Ccode>rel\u003C/code> doivent être irréprochables pour éviter les problèmes de \u003Ca href=\"/blog/contenu-duplique-causes-techniques-et-solutions\">contenu dupliqué\u003C/a>.\u003C/p>\n\u003Cp>La recommandation officielle de Google depuis des années est le responsive design. Si vous êtes encore sur une architecture \u003Ccode>m.\u003C/code> en 2026, la migration devrait être votre priorité.\u003C/p>\n\u003Ch2>Vérifier la parité de contenu entre mobile et desktop\u003C/h2>\n\u003Cp>C'est le point le plus critique. Quand on parle de parité, on parle de trois choses distinctes : le contenu textuel, les liens internes, et les données structurées.\u003C/p>\n\u003Ch3>Contenu textuel\u003C/h3>\n\u003Cp>Chaque élément de contenu visible sur desktop doit être présent dans le DOM de la version mobile. Cela inclut les textes dans les accordéons, les onglets, les sections \"lire la suite\".\u003C/p>\n\u003Cp>Google a confirmé que le contenu dans les accordéons et les tabs est traité comme du contenu normal, même s'il nécessite une interaction utilisateur pour être affiché. Mais — et c'est un edge case fréquent — si ce contenu est chargé via un appel AJAX au clic (lazy-loaded), Googlebot ne le verra pas. Le contenu doit être dans le DOM initial ou dans le HTML rendu côté serveur.\u003C/p>\n\u003Cp>Voici un pattern problématique courant en React :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// ❌ Problème : le contenu de l'accordéon n'est chargé qu'au clic\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">function\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductFAQ\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">productId\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">productId\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:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#79B8FF\">faqData\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">setFaqData\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#B392F0\"> useState\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">null\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#79B8FF\">isOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">setIsOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#B392F0\"> useState\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\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:#B392F0\"> handleClick\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> async\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\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">faqData) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> res\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetch\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`/api/faq/${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">productId\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">      setFaqData\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">await\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">json\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:#B392F0\">    setIsOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">isOpen);\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\">  return\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\">button\u003C/span>\u003Cspan style=\"color:#B392F0\"> onClick\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{handleClick}>Questions fréquentes&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">button\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      {isOpen \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;&#x26;\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> faqData \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;&#x26;\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\">\"faq-content\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          {faqData.\u003C/span>\u003Cspan style=\"color:#B392F0\">map\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#FFAB70\">item\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> any\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:#E1E4E8\">            &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">div\u003C/span>\u003Cspan style=\"color:#B392F0\"> key\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{item.id}>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">              &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">h3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>{item.question}&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">h3\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\">>{item.answer}&#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\">          ))}\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\">      )}\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\">  );\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>La version corrigée pré-charge les données et inclut le contenu dans le DOM initial :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// ✅ Le contenu est dans le DOM dès le rendu initial (SSR ou SSG)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">function\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductFAQ\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">faqData\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">faqData\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> FAQItem\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[] }) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#79B8FF\">isOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">setIsOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#B392F0\"> useState\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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>\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\">button\u003C/span>\u003Cspan style=\"color:#B392F0\"> onClick\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{() \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#B392F0\"> setIsOpen\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">isOpen)} \u003C/span>\u003Cspan style=\"color:#B392F0\">aria-expanded\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{isOpen}>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        Questions fréquentes\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">button\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      {\u003C/span>\u003Cspan style=\"color:#6A737D\">/* Contenu dans le DOM, masqué visuellement par 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\">div\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">        className\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"faq-content\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">        style\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{{ display: isOpen \u003C/span>\u003Cspan style=\"color:#F97583\">?\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'block'\u003C/span>\u003Cspan style=\"color:#F97583\"> :\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'none'\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\">        {faqData.\u003C/span>\u003Cspan style=\"color:#B392F0\">map\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#FFAB70\">item\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:#E1E4E8\">          &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">div\u003C/span>\u003Cspan style=\"color:#B392F0\"> key\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{item.id}>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">h3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>{item.question}&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">h3\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\">>{item.answer}&#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\">        ))}\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\">  );\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:#6A737D\">// Les données sont chargées côté serveur (Next.js App Router)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> async\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> generateStaticParams\u003C/span>\u003Cspan style=\"color:#E1E4E8\">() { \u003C/span>\u003Cspan style=\"color:#6A737D\">/* ... */\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Pour comparer systématiquement le DOM mobile et desktop, la méthode la plus fiable est d'utiliser Screaming Frog en mode rendu JavaScript, en faisant deux crawls : un avec le user-agent Googlebot desktop, un avec Googlebot mobile. Exportez les deux datasets et comparez le word count, le nombre de liens internes, et la présence des balises \u003Ccode>&#x3C;h1>\u003C/code> à \u003Ccode>&#x3C;h3>\u003C/code> par URL. Sur un site de 8 000 pages, cela prend environ 45 minutes de crawl par passe (avec un crawl rate raisonnable de 3 URLs/seconde).\u003C/p>\n\u003Cp>Pour des comparaisons plus fines entre le rendu SSR et le rendu CSR, \u003Ca href=\"/blog/comparer-ssr-et-csr-detecter-les-divergences-invisibles\">cet article sur les divergences SSR/CSR\u003C/a> détaille la méthodologie complète.\u003C/p>\n\u003Ch3>Liens internes\u003C/h3>\n\u003Cp>C'est le piège le plus fréquent. Beaucoup de sites desktop utilisent des \u003Ca href=\"/blog/mega-menus-et-seo-attention-au-crawl-budget\">mega menus\u003C/a> avec des centaines de liens internes. Sur mobile, ce mega menu est remplacé par un hamburger menu simplifié qui ne contient que les catégories de premier niveau.\u003C/p>\n\u003Cp>Résultat : des dizaines de pages profondes perdent leurs liens internes entrants depuis la navigation. Pour Googlebot mobile, ces pages deviennent plus difficiles à découvrir et à crawler. L'impact sur le crawl budget est direct — sujet que nous détaillons dans l'article sur les \u003Ca href=\"/blog/mega-menus-et-seo-attention-au-crawl-budget\">mega menus et le crawl budget\u003C/a>.\u003C/p>\n\u003Cp>Vérifiez la distribution des liens internes entre les deux versions. Si votre menu desktop contient 120 liens et votre menu mobile 25, vous avez un delta de 95 liens internes sur chaque page du site. Multipliez par 8 000 pages et vous comprenez l'impact sur la structure de liens interne.\u003C/p>\n\u003Ch3>Données structurées\u003C/h3>\n\u003Cp>Toutes les balises \u003Ccode>&#x3C;script type=\"application/ld+json\">\u003C/code> présentes sur desktop doivent être identiques sur mobile. Pas \"similaires\" — identiques. Même chose pour les microdonnées \u003Ccode>itemscope\u003C/code> si vous les utilisez encore.\u003C/p>\n\u003Cp>Un cas typique : un site e-commerce injecte les données Product structured data uniquement via un composant qui n'est rendu que sur desktop. La version mobile affiche bien le prix et les avis, mais le JSON-LD est absent du HTML.\u003C/p>\n\u003Cp>Utilisez le test de résultat enrichi de Google (\u003Ca href=\"https://search.google.com/test/rich-results\">search.google.com/test/rich-results\u003C/a>) en mode mobile pour vérifier chaque template.\u003C/p>\n\u003Ch2>Diagnostiquer les problèmes de rendu mobile avec les bons outils\u003C/h2>\n\u003Ch3>Search Console : l'inspection d'URL en mode mobile\u003C/h3>\n\u003Cp>L'outil \"Inspection d'URL\" de la Search Console affiche le HTML rendu tel que Googlebot le voit. Depuis le passage complet au mobile-first, ce rendu est celui du user-agent mobile. Regardez deux choses :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Le HTML rendu\u003C/strong> : cliquez sur \"Afficher la page testée\" > \"HTML\". Cherchez-y vos contenus critiques (descriptions produit, FAQ, liens internes de navigation).\u003C/li>\n\u003Cli>\u003Cstrong>La capture d'écran\u003C/strong> : elle montre le rendu visuel. Si des éléments se chevauchent, débordent de l'écran ou sont trop petits pour être tapés, Google le voit aussi.\u003C/li>\n\u003C/ol>\n\u003Cp>Si vous constatez des écarts entre ce que vous attendez et ce que Google voit, il y a probablement un problème de rendu JavaScript ou de serving conditionnel. L'article sur les \u003Ca href=\"/blog/google-search-console-les-rapports-que-vous-ignorez\">rapports Search Console souvent ignorés\u003C/a> couvre d'autres checks utiles dans la console.\u003C/p>\n\u003Ch3>Chrome DevTools : simuler Googlebot mobile\u003C/h3>\n\u003Cp>La méthode la plus rapide pour un diagnostic ponctuel :\u003C/p>\n\u003Col>\n\u003Cli>Ouvrez Chrome DevTools (F12)\u003C/li>\n\u003Cli>Activez le Device Mode (Ctrl+Shift+M)\u003C/li>\n\u003Cli>Sélectionnez \"Nexus 5X\" comme device (c'est le device de référence de Googlebot)\u003C/li>\n\u003Cli>Dans l'onglet Network, cochez \"Disable cache\"\u003C/li>\n\u003Cli>Dans le menu à trois points > More tools > Network conditions, remplacez le user-agent par celui de Googlebot mobile\u003C/li>\n\u003C/ol>\n\u003Cp>Cela vous donne une approximation du rendu. Ce n'est pas identique au rendu de Googlebot (qui utilise une version headless de Chrome avec des spécificités), mais cela détecte les problèmes les plus flagrants. Pour aller plus loin, consultez nos \u003Ca href=\"/blog/chrome-devtools-pour-le-seo-astuces-avancees\">astuces avancées avec Chrome DevTools pour le SEO\u003C/a>.\u003C/p>\n\u003Ch3>Screaming Frog : crawl comparatif desktop vs mobile\u003C/h3>\n\u003Cp>Voici la configuration pour un crawl mobile-first dans Screaming Frog :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Configuration > User-Agent\u003C/strong> : sélectionnez \"Googlebot Smartphone\"\u003C/li>\n\u003Cli>\u003Cstrong>Configuration > Spider > Rendering\u003C/strong> : activez \"JavaScript\" avec le moteur Chromium\u003C/li>\n\u003Cli>\u003Cstrong>Configuration > Spider > Crawl\u003C/strong> : limitez à vos templates principaux via des regex d'inclusion si le site est volumineux\u003C/li>\n\u003C/ol>\n\u003Cp>Lancez un premier crawl en Googlebot Smartphone, exportez en CSV. Lancez un second en Googlebot Desktop, exportez. Comparez ces colonnes par URL :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Ccode>Word Count\u003C/code> (parité de contenu)\u003C/li>\n\u003Cli>\u003Ccode>Inlinks\u003C/code> (parité de liens internes)\u003C/li>\n\u003Cli>\u003Ccode>Structured Data\u003C/code> (parité des données structurées)\u003C/li>\n\u003Cli>\u003Ccode>H1\u003C/code>, \u003Ccode>H2 Count\u003C/code> (structure de heading)\u003C/li>\n\u003Cli>\u003Ccode>Indexability\u003C/code> (mêmes pages indexables dans les deux versions)\u003C/li>\n\u003C/ul>\n\u003Cp>Un script Python rapide pour automatiser la comparaison :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pandas \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">mobile \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.read_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'crawl_mobile.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">usecols\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Inlinks'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexability'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">desktop \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.read_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'crawl_desktop.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">usecols\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Inlinks'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexability'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">merged \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> mobile.merge(desktop, \u003C/span>\u003Cspan style=\"color:#FFAB70\">on\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">suffixes\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'_mobile'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'_desktop'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Pages où le contenu mobile est significativement plus court\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'word_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count_desktop'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count_mobile'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">content_gaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'word_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].sort_values(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'word_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">ascending\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Pages où les liens internes diffèrent fortement\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'link_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Inlinks_desktop'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Inlinks_mobile'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">link_gaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'link_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 10\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].sort_values(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'link_delta'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">ascending\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Pages indexables sur desktop mais pas sur mobile\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">indexability_issues \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> merged[\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexability_desktop'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'Indexable'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (merged[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexability_mobile'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'Indexable'\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:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Pages avec écart de contenu > 100 mots : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(content_gaps)\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:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Pages avec écart de liens internes > 10 : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(link_gaps)\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:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Pages indexables desktop mais pas mobile : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(indexability_issues)\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\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">content_gaps.to_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'content_parity_issues.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">index\u003C/span>\u003Cspan style=\"color:#F97583\">=\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\">link_gaps.to_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'link_parity_issues.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">index\u003C/span>\u003Cspan style=\"color:#F97583\">=\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\">indexability_issues.to_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'indexability_issues.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">index\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch2>Scénario concret : migration d'un e-commerce de 15K pages\u003C/h2>\n\u003Cp>Voici un cas réaliste. Un site e-commerce de vêtements — 15 000 pages produit, 200 pages catégorie, 50 pages de contenu éditorial — tourne sur une architecture dynamic serving. Le backend Django détecte le user-agent et sert des templates différents pour mobile et desktop.\u003C/p>\n\u003Ch3>Le diagnostic\u003C/h3>\n\u003Cp>Un crawl Screaming Frog comparatif révèle :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>2 300 pages produit\u003C/strong> ont un word count mobile inférieur de 150+ mots au desktop. Cause : le tableau des caractéristiques techniques (\u003Ccode>&#x3C;table class=\"specs\">\u003C/code>) est supprimé du template mobile pour \"gagner de la place\".\u003C/li>\n\u003Cli>\u003Cstrong>180 pages catégorie\u003C/strong> perdent entre 40 et 90 liens internes sur mobile. Cause : la sidebar desktop affiche des liens vers les sous-catégories et les filtres populaires. Sur mobile, cette sidebar est purement supprimée (pas repliée — supprimée du DOM).\u003C/li>\n\u003Cli>\u003Cstrong>Toutes les pages produit\u003C/strong> perdent le JSON-LD \u003Ccode>Product\u003C/code>. Le bloc \u003Ccode>&#x3C;script type=\"application/ld+json\">\u003C/code> est inclus dans un partial Django chargé uniquement dans le template desktop : \u003Ccode>{% include \"partials/structured_data.html\" %}\u003C/code> est absent du template \u003Ccode>product_mobile.html\u003C/code>.\u003C/li>\n\u003C/ul>\n\u003Ch3>L'impact mesuré\u003C/h3>\n\u003Cp>Après investigation dans la Search Console, les résultats enrichis (prix, disponibilité, avis) ont progressivement disparu des SERPs sur 4 mois — période pendant laquelle Google a basculé l'indexation du site vers mobile-first. Le trafic organique a baissé de 23% sur les pages produit, non pas à cause d'une pénalité, mais parce que :\u003C/p>\n\u003Col>\n\u003Cli>La perte des résultats enrichis a réduit le CTR d'environ 15-20% (les snippets avec prix et étoiles attirent mécaniquement plus de clics).\u003C/li>\n\u003Cli>Le contenu indexé étant plus pauvre (pas de tableau de spécifications), les pages se positionnent moins bien sur les requêtes longue traîne liées aux caractéristiques techniques.\u003C/li>\n\u003Cli>La perte de liens internes sur mobile réduit le PageRank interne vers les sous-catégories, affectant leur crawl et leur indexation.\u003C/li>\n\u003C/ol>\n\u003Ch3>La correction\u003C/h3>\n\u003Cp>La solution retenue : migration vers un responsive design unique. Plus de dynamic serving, un seul template par type de page. Le tableau de spécifications est affiché dans un accordéon sur mobile (contenu dans le DOM, masqué par CSS). La sidebar est transformée en section horizontale scrollable en haut de la page catégorie sur mobile. Le JSON-LD est inclus dans le template de base, indépendamment du device.\u003C/p>\n\u003Cp>Temps de correction : 3 semaines de développement, 1 semaine de QA, déploiement progressif par template. Récupération du trafic perdu en environ 6 semaines après le re-crawl complet par Google.\u003C/p>\n\u003Cp>Ce type de régression silencieuse — où le contenu disparaît sans qu'aucune alerte ne se déclenche — est exactement ce qui justifie un \u003Ca href=\"/blog/monitoring-seo-pourquoi-les-audits-ponctuels-ne-suffisent-plus\">monitoring SEO continu\u003C/a> plutôt que des audits trimestriels. Un outil comme Seogard aurait détecté la disparition du structured data et la chute du word count dès le premier crawl post-changement.\u003C/p>\n\u003Ch2>Performance mobile : au-delà du responsive\u003C/h2>\n\u003Cp>Avoir un contenu paritaire ne suffit pas si vos pages mettent 8 secondes à se charger sur mobile. Google utilise les Core Web Vitals mesurés sur les sessions mobiles réelles (données CrUX) comme signal de classement.\u003C/p>\n\u003Ch3>LCP mobile : le goulot d'étranglement le plus fréquent\u003C/h3>\n\u003Cp>Le Largest Contentful Paint sur mobile est presque toujours plus élevé que sur desktop, pour trois raisons : bande passante plus faible, CPU moins puissant, et images souvent surdimensionnées.\u003C/p>\n\u003Cp>Vérifiez votre config d'images responsive :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">&#x3C;!-- ❌ Image unique en pleine résolution -->\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\">\"/images/product-hero-1920x1080.jpg\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> alt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Robe en lin naturel\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">&#x3C;!-- ✅ Images responsive avec srcset et sizes -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">img\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/images/product-hero-800x450.webp\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  srcset\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    /images/product-hero-400x225.webp 400w,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    /images/product-hero-800x450.webp 800w,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    /images/product-hero-1200x675.webp 1200w,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    /images/product-hero-1920x1080.webp 1920w\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  \"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  sizes\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  alt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Robe en lin naturel\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  width\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"1920\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  height\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"1080\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  loading\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"eager\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  fetchpriority\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"high\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>L'attribut \u003Ccode>fetchpriority=\"high\"\u003C/code> sur l'image LCP est sous-utilisé. Il indique au navigateur de prioriser le téléchargement de cette ressource, ce qui réduit le LCP de 100-300ms sur des connexions 4G selon les tests documentés sur \u003Ca href=\"https://web.dev/articles/fetch-priority\">web.dev\u003C/a>.\u003C/p>\n\u003Cp>Pour un suivi continu de ces métriques, \u003Ca href=\"/blog/lighthouse-ci-surveiller-la-performance-en-continu\">Lighthouse CI\u003C/a> est l'approche la plus robuste en intégration continue.\u003C/p>\n\u003Ch3>Le viewport meta tag\u003C/h3>\n\u003Cp>Basique mais toujours source d'erreurs. Si cette balise est absente ou mal configurée, Google considère que la page n'est pas adaptée au mobile :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">&#x3C;!-- La seule valeur correcte -->\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\">\"viewport\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> content\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"width=device-width, initial-scale=1\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">&#x3C;!-- ❌ Problématique : empêche le zoom (accessibilité + signal négatif) -->\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\">\"viewport\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> content\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le \u003Ccode>user-scalable=no\u003C/code> et \u003Ccode>maximum-scale=1\u003C/code> ne sont pas des signaux négatifs directs pour le classement, mais ils sont flaggés dans Lighthouse comme des problèmes d'accessibilité. Et un site qui bloque le zoom sera moins bien noté sur le rapport \"Expérience sur la page\" de la Search Console.\u003C/p>\n\u003Ch3>Ressources bloquées par robots.txt\u003C/h3>\n\u003Cp>Un piège classique : votre fichier \u003Ccode>robots.txt\u003C/code> bloque des fichiers CSS ou JavaScript nécessaires au rendu mobile. Si Googlebot ne peut pas charger votre framework CSS responsive, il voit la page sans mise en forme — et peut considérer que le contenu n'est pas mobile-friendly.\u003C/p>\n\u003Cp>Vérifiez avec l'outil d'inspection d'URL de la Search Console. Dans la section \"Plus d'informations\" > \"Ressources de la page\", cherchez les ressources marquées comme bloquées. Les CDN tiers (polices, CSS frameworks) sont les coupables les plus fréquents.\u003C/p>\n\u003Ch2>Configuration serveur : les pièges du dynamic serving\u003C/h2>\n\u003Cp>Si vous servez un contenu différent selon le user-agent (dynamic serving), vous devez envoyer le header HTTP \u003Ccode>Vary: User-Agent\u003C/code>. Sans ce header, les caches intermédiaires (CDN, proxy) peuvent servir la version desktop à Googlebot mobile, ou inversement.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Configuration Nginx pour dynamic serving\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">server\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    listen \u003C/span>\u003Cspan style=\"color:#79B8FF\">443\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ssl;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">shop.example.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Détection mobile via user-agent\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    set \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$mobile_rewrite do_not_perform;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ($http_user_agent \u003C/span>\u003Cspan style=\"color:#F97583\">~* \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        set \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$mobile_rewrite perform;\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\">    location\u003C/span>\u003Cspan style=\"color:#B392F0\"> / \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Header Vary obligatoire pour le cache\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Vary \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"User-Agent\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ($mobile_rewrite \u003C/span>\u003Cspan style=\"color:#F97583\">= \u003C/span>\u003Cspan style=\"color:#E1E4E8\">perform) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            # Servir le template mobile\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend_mobile;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            break\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\">        proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend_desktop;\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>Deux points importants :\u003C/p>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Le header \u003Ccode>Vary: User-Agent\u003C/code>\u003C/strong> est essentiel. Sans lui, Cloudflare ou Fastly pourraient cacher la version desktop et la servir à tous les user-agents pendant la durée du TTL. C'est documenté dans les \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing\">recommandations Google sur le mobile-first indexing\u003C/a>.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Les regex de détection user-agent\u003C/strong> doivent inclure le user-agent Googlebot mobile. Testez explicitement que votre condition matche \u003Ccode>\"Googlebot/2.1\"\u003C/code> combiné avec \u003Ccode>\"Mobile\"\u003C/code>. Un mauvais regex est une cause fréquente de serving incorrect.\u003C/p>\n\u003C/li>\n\u003C/ol>\n\u003Cp>Cela dit, la recommandation reste de migrer vers du responsive design pur. Le dynamic serving est une source constante de bugs et de \u003Ca href=\"/blog/regressions-seo-les-10-types-les-plus-frequents\">régressions SEO\u003C/a> difficiles à détecter — surtout lors des déploiements. Un \u003Ca href=\"/blog/deploiement-vendredi-soir-comment-eviter-la-catastrophe-seo\">déploiement mal préparé\u003C/a> peut casser la logique de serving sans que personne ne s'en aperçoive pendant des jours.\u003C/p>\n\u003Ch2>Checklist mobile-first : les vérifications à automatiser\u003C/h2>\n\u003Cp>Voici les vérifications à intégrer dans votre pipeline CI/CD ou votre routine de monitoring. Chaque point est un signal binaire — ça passe ou ça casse.\u003C/p>\n\u003Ch3>Vérifications de parité\u003C/h3>\n\u003Cul class=\"contains-task-list\">\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Le word count de chaque template en user-agent mobile est ≥ 95% du word count desktop\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Le nombre de liens internes dans la navigation est identique (± 5%) entre mobile et desktop\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Tous les blocs \u003Ccode>&#x3C;script type=\"application/ld+json\">\u003C/code> présents sur desktop sont présents sur mobile\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les balises \u003Ccode>&#x3C;h1>\u003C/code> sont identiques entre les deux versions\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les balises canonical, hreflang, et meta robots sont identiques\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les images ont des attributs \u003Ccode>alt\u003C/code> identiques dans les deux versions\u003C/li>\n\u003C/ul>\n\u003Ch3>Vérifications techniques\u003C/h3>\n\u003Cul class=\"contains-task-list\">\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> La balise \u003Ccode>&#x3C;meta name=\"viewport\">\u003C/code> est présente et correctement configurée\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Aucune ressource CSS/JS critique n'est bloquée par \u003Ccode>robots.txt\u003C/code>\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Le temps de réponse serveur (TTFB) en user-agent mobile est &#x3C; 800ms\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Le LCP mobile est &#x3C; 2.5s sur les templates principaux\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les éléments interactifs ont une zone de tap ≥ 48x48px\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Pas de contenu qui déborde horizontalement (pas de scrollbar horizontal)\u003C/li>\n\u003C/ul>\n\u003Ch3>Vérifications de configuration\u003C/h3>\n\u003Cul class=\"contains-task-list\">\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Le header \u003Ccode>Vary: User-Agent\u003C/code> est envoyé si le site utilise du dynamic serving\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les redirections mobile (si existantes) fonctionnent dans les deux sens (desktop → mobile ET mobile → desktop)\u003C/li>\n\u003Cli class=\"task-list-item\">\u003Cinput type=\"checkbox\" disabled> Les pages \u003Ccode>m.\u003C/code> ont des balises \u003Ccode>rel=\"canonical\"\u003C/code> qui pointent vers la version desktop, et les pages desktop ont des balises \u003Ccode>rel=\"alternate\" media=\"only screen and (max-width: 640px)\"\u003C/code> qui pointent vers la version mobile\u003C/li>\n\u003C/ul>\n\u003Cp>L'automatisation de ces checks dans votre pipeline CI/CD est la meilleure assurance contre les régressions. Nous détaillons l'implémentation dans l'article sur \u003Ca href=\"/blog/automatiser-les-checks-seo-dans-le-ci-cd\">l'automatisation des checks SEO en CI/CD\u003C/a>.\u003C/p>\n\u003Cp>Pour les vérifications continues post-déploiement, la configuration des \u003Ca href=\"/blog/alertes-seo-quels-seuils-et-quelle-frequence\">alertes SEO avec les bons seuils\u003C/a> est critique. Une chute de 20% du word count sur un template détectée en 24h est récupérable. La même chute détectée 3 mois plus tard lors d'un audit trimestriel a déjà causé des dégâts durables.\u003C/p>\n\u003Ch2>Les edge cases à ne pas ignorer\u003C/h2>\n\u003Cp>\u003Cstrong>Les lazy-loaded iframes\u003C/strong> : si vous intégrez des vidéos YouTube ou des widgets tiers via des iframes lazy-loaded, vérifiez que le placeholder contient du contenu textuel pertinent. Googlebot ne chargera pas forcément l'iframe sur la version mobile s'il juge que le viewport ne l'atteint pas. Pour les pages qui reposent sur le \u003Ca href=\"/blog/infinite-scroll-et-seo-le-guide-technique\">infinite scroll\u003C/a>, le problème est amplifié.\u003C/p>\n\u003Cp>\u003Cstrong>Les interstitiels mobile\u003C/strong> : les pop-ups qui couvrent le contenu principal sur mobile sont un signal négatif explicite depuis janvier 2017. Google ne pénalise pas les interstitiels liés aux cookies (obligation légale), mais un interstitiel marketing qui recouvre 80% de l'écran mobile va impacter votre classement.\u003C/p>\n\u003Cp>\u003Cstrong>Les pages AMP\u003C/strong> : si vous avez encore des pages AMP couplées à des pages canoniques non-AMP, sachez que Google indexe la version canonical (non-AMP). Assurez-vous que la canonical est elle-même mobile-friendly. L'AMP a été en grande partie abandonné comme format prioritaire par Google, mais les implementations legacy restent une source de confusion dans les rapports Search Console.\u003C/p>\n\u003Cp>\u003Cstrong>Le contenu conditionnel par géolocalisation\u003C/strong> : si vous servez du contenu différent en fonction de l'IP (variante de dynamic serving), assurez-vous que le contenu servi à Googlebot — qui crawle depuis des IP américaines — est représentatif de votre contenu cible. C'est particulièrement pertinent si votre \u003Ca href=\"/blog/url-structure-bonnes-pratiques-seo-pour-les-urls\">structure d'URL\u003C/a> utilise des sous-répertoires de langue.\u003C/p>\n\u003Chr>\n\u003Cp>Le mobile-first indexing n'est plus une migration à planifier — c'est l'état par défaut. La question n'est pas \"quand est-ce que ça arrive\" mais \"est-ce que mon site sert exactement le même contenu, les mêmes liens, les mêmes données structurées à Googlebot mobile qu'à Googlebot desktop\". Si vous n'avez pas fait ce diagnostic récemment, lancez un crawl comparatif cette semaine. Et pour éviter que ce type de divergence ne se réinstalle silencieusement après chaque déploiement, un monitoring continu avec Seogard vous alerte dès qu'une parité de contenu est rompue — avant que l'impact SEO ne soit visible dans vos dashboards de trafic.\u003C/p>",null,12,[18,19,20,21],"mobile-first","indexing","responsive","seo","Mobile-first indexing : checklist technique complète","Sat Apr 04 2026 04:02:30 GMT+0000 (Coordinated Universal Time)",[]]