[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fZMk9lTTiFCJ2YeClARo0y9Hw6w3ophgqcVIyAzqcCns":3,"$fNe-KiYG6fuvzZlN_Mq6S1v5LhWkBZzVG92mucZ20hIs":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"6a0f1eb5aa6b273b0c5d4dff","how-to-stress-test-a-staging-environment-to-surface-risks-pre-launch-ask-an-seo-via-sejournal-helenpollitt1",0,"Equipe Seogard","Un e-commerce mode de 22 000 pages migre de Magento 2 vers une stack headless Next.js + Shopify. Le staging est impeccable visuellement. L'équipe QA valide. Le go-live tombe un mardi matin. Jeudi, Google Search Console affiche une chute de 34% des impressions. Les canonical pointent toutes vers le staging. Les hreflang ont disparu. Le sitemap référence encore les anciennes URLs. Trois semaines de récupération, 180K€ de chiffre d'affaires perdus.\n\nCe scénario n'est pas hypothétique. C'est le pattern classique d'un stress-test staging bâclé — ou inexistant. Helen Pollitt a récemment abordé le sujet dans [sa chronique Ask An SEO sur Search Engine Journal](https://www.searchenginejournal.com/ask-an-seo-how-do-you-stress-test-a-staging-environment/573559/), en posant les bases méthodologiques. Cet article va plus loin : scripts d'audit automatisés, configs serveur, scénarios de crawl massif, et la méthodologie exacte pour transformer votre staging en filet de sécurité.\n\n## Préparer le staging comme un miroir de production fidèle\n\nLe premier piège est de croire que votre staging reflète la production. Dans la majorité des cas, ce n'est pas le cas — et les écarts sont précisément là où les régressions SEO se cachent.\n\n### Le problème des données tronquées\n\nUn staging avec 500 produits quand la production en compte 18 000 ne teste rien du tout en matière de SEO. Les problèmes de pagination, de faceted navigation, de crawl depth, de temps de génération des sitemaps — tout cela n'apparaît qu'à l'échelle réelle.\n\nExportez un dump complet de la base de production (anonymisé pour le RGPD) et injectez-le dans le staging. Si la volumétrie rend ça impossible (certaines bases e-commerce dépassent les 50 Go), prenez au minimum un échantillon représentatif : toutes les catégories L1/L2, un ratio réaliste de produits actifs/inactifs, et l'intégralité des redirections 301 existantes.\n\n### Bloquer les robots tout en permettant l'audit\n\nLe staging doit être invisible pour Google mais crawlable par vos outils. La méthode la plus fiable combine authentification HTTP et un `robots.txt` permissif derrière cette auth :\n\n```nginx\n# Configuration Nginx pour le staging\nserver {\n    listen 443 ssl;\n    server_name staging.votresite.fr;\n\n    # Auth HTTP basique — bloque tous les crawlers publics\n    auth_basic \"Staging Environment\";\n    auth_basic_user_file /etc/nginx/.htpasswd;\n\n    # Headers anti-indexation en doublon de sécurité\n    add_header X-Robots-Tag \"noindex, nofollow\" always;\n\n    # Permettre le crawl interne une fois authentifié\n    location /robots.txt {\n        auth_basic off;  # robots.txt accessible sans auth\n        return 200 \"User-agent: *\\nDisallow: /\\n\";\n    }\n\n    # Le reste de la config...\n    location / {\n        proxy_pass http://backend:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\nLe `X-Robots-Tag` en header HTTP est votre filet de sécurité si quelqu'un désactive l'auth ou si un lien staging fuite. Les deux mécanismes se complètent.\n\nPoint critique : vérifiez que votre crawleur (Screaming Frog, Sitebulb) supporte l'authentification HTTP. Dans Screaming Frog, c'est dans Configuration > Authentication > Forms Based / HTTP Basic. Si vous utilisez une auth par cookie ou OAuth, configurez un custom header ou un cookie persistant dans les paramètres du crawler.\n\n### Aligner les variables d'environnement\n\nLes frameworks modernes utilisent des variables d'environnement pour switch entre staging et production. C'est là que les canonical, les URLs de sitemap et les balises OG partent en vrille.\n\nVérifiez ces variables en priorité :\n\n```bash\n# Variables critiques à auditer dans le .env du staging\n# Recherchez toute référence au domaine de production\n\ngrep -rn \"votresite.fr\\|NEXT_PUBLIC_SITE_URL\\|CANONICAL_BASE\\|SITEMAP_HOST\" \\\n  .env .env.staging .env.local next.config.js nuxt.config.ts\n\n# Résultat attendu : toutes les URLs doivent pointer vers staging.votresite.fr\n# Si vous voyez www.votresite.fr dans le staging, c'est un bug pré-launch\n```\n\nDans un projet Next.js, le piège classique est `NEXT_PUBLIC_SITE_URL` utilisé pour générer les canonical et les URLs OG. Si cette variable pointe vers la production dans le build staging, vos tests valident des canonical corrects... qui ne le seront plus après le flip DNS.\n\n## Auditer les balises SEO critiques à l'échelle\n\nLe test manuel de 10 pages ne suffit pas. Vous avez besoin d'un crawl complet du staging, comparé au crawl de production, avec un diff automatisé.\n\n### Crawl comparatif production vs staging\n\nLancez un crawl Screaming Frog sur la production ET sur le staging. Exportez les deux en CSV, puis comparez avec un script :\n\n```python\nimport pandas as pd\nimport sys\n\ndef compare_crawls(prod_csv: str, staging_csv: str, output: str):\n    \"\"\"\n    Compare les crawls production et staging.\n    Détecte : titles modifiés, canonical manquants, meta robots changés,\n    status codes différents, hreflang supprimés.\n    \"\"\"\n    prod = pd.read_csv(prod_csv, usecols=[\n        'Address', 'Status Code', 'Title 1', 'Meta Description 1',\n        'Canonical Link Element 1', 'Meta Robots 1', 'Indexability',\n        'H1-1', 'Word Count'\n    ])\n    staging = pd.read_csv(staging_csv, usecols=[\n        'Address', 'Status Code', 'Title 1', 'Meta Description 1',\n        'Canonical Link Element 1', 'Meta Robots 1', 'Indexability',\n        'H1-1', 'Word Count'\n    ])\n\n    # Normaliser les URLs pour comparer (retirer le domaine)\n    prod['path'] = prod['Address'].str.replace(\n        r'https?://[^/]+', '', regex=True\n    )\n    staging['path'] = staging['Address'].str.replace(\n        r'https?://[^/]+', '', regex=True\n    )\n\n    merged = prod.merge(staging, on='path', suffixes=('_prod', '_staging'))\n\n    # Détecter les régressions critiques\n    regressions = []\n\n    # 1. Title disparu ou modifié\n    title_changes = merged[\n        merged['Title 1_prod'] != merged['Title 1_staging']\n    ]\n    for _, row in title_changes.iterrows():\n        regressions.append({\n            'path': row['path'],\n            'type': 'TITLE_CHANGED',\n            'prod_value': row['Title 1_prod'],\n            'staging_value': row['Title 1_staging'],\n            'severity': 'HIGH'\n        })\n\n    # 2. Canonical manquant ou modifié\n    canonical_changes = merged[\n        merged['Canonical Link Element 1_prod'] != merged['Canonical Link Element 1_staging']\n    ]\n    for _, row in canonical_changes.iterrows():\n        regressions.append({\n            'path': row['path'],\n            'type': 'CANONICAL_CHANGED',\n            'prod_value': row['Canonical Link Element 1_prod'],\n            'staging_value': row['Canonical Link Element 1_staging'],\n            'severity': 'CRITICAL'\n        })\n\n    # 3. Page devenue non-indexable\n    indexability_loss = merged[\n        (merged['Indexability_prod'] == 'Indexable') &\n        (merged['Indexability_staging'] != 'Indexable')\n    ]\n    for _, row in indexability_loss.iterrows():\n        regressions.append({\n            'path': row['path'],\n            'type': 'INDEXABILITY_LOST',\n            'prod_value': 'Indexable',\n            'staging_value': row['Indexability_staging'],\n            'severity': 'CRITICAL'\n        })\n\n    # 4. Pages production manquantes dans le staging (404/disparues)\n    prod_paths = set(prod['path'])\n    staging_paths = set(staging['path'])\n    missing = prod_paths - staging_paths\n    for path in missing:\n        regressions.append({\n            'path': path,\n            'type': 'PAGE_MISSING_IN_STAGING',\n            'prod_value': 'EXISTS',\n            'staging_value': '404 or GONE',\n            'severity': 'CRITICAL'\n        })\n\n    results = pd.DataFrame(regressions)\n    results = results.sort_values('severity')\n    results.to_csv(output, index=False)\n    \n    # Résumé\n    print(f\"\\n{'='*60}\")\n    print(f\"STAGING REGRESSION REPORT\")\n    print(f\"{'='*60}\")\n    print(f\"Total régressions détectées : {len(regressions)}\")\n    print(f\"  CRITICAL : {len(results[results.severity == 'CRITICAL'])}\")\n    print(f\"  HIGH     : {len(results[results.severity == 'HIGH'])}\")\n    print(f\"Pages manquantes : {len(missing)}\")\n    print(f\"Rapport exporté : {output}\")\n\nif __name__ == '__main__':\n    compare_crawls(sys.argv[1], sys.argv[2], sys.argv[3])\n```\n\nExécution : `python compare_crawls.py crawl_prod.csv crawl_staging.csv report.csv`\n\nCe script détecte quatre catégories de régressions. En pratique, sur une migration d'un site de 15 000 pages, attendez-vous à trouver entre 200 et 2 000 écarts. La plupart seront bénins (légères modifications de title intentionnelles), mais les CRITICAL — canonical cassés, pages disparues, indexability perdue — doivent être résolus avant toute mise en production.\n\n### Focus sur les pages à fort trafic\n\nToutes les pages ne méritent pas le même niveau d'attention. Croisez votre rapport de régressions avec les données Search Console pour prioriser :\n\nExportez les pages avec le plus d'impressions/clics depuis Search Console (Performance > Pages > Export). Les 200 premières pages représentent souvent 70-80% du trafic organique. Si une régression touche l'une d'elles, c'est un showstopper.\n\n## Valider le rendering et le JavaScript côté SEO\n\nLes migrations vers des frameworks JavaScript (React, Vue, Angular, ou leurs variantes SSR) introduisent un risque spécifique : le contenu visible dans le navigateur peut être invisible pour Googlebot. Le staging est le moment de le vérifier — pas après le launch.\n\n### Comparer le HTML servi vs le DOM rendu\n\nPour chaque template type (page produit, catégorie, article, landing page), comparez le HTML retourné par le serveur (ce que reçoit un curl) avec le DOM après exécution JavaScript :\n\n```bash\n# 1. Récupérer le HTML brut (ce que voit le serveur)\ncurl -s -A \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\" \\\n  \"https://staging.votresite.fr/categorie/chaussures-homme\" \\\n  -o raw_html.html\n\n# 2. Récupérer le DOM rendu via Puppeteer / Chrome headless\nnpx puppeteer-core --no-sandbox \u003C\u003C 'EOF' > rendered_dom.html\nconst browser = await require('puppeteer').launch({\n  args: ['--no-sandbox']\n});\nconst page = await browser.newPage();\nawait page.setUserAgent('Mozilla/5.0 (compatible; Googlebot/2.1)');\nawait page.goto('https://staging.votresite.fr/categorie/chaussures-homme', {\n  waitUntil: 'networkidle0',\n  timeout: 30000\n});\n// Attendre le rendering complet\nawait page.waitForTimeout(5000);\nconst content = await page.content();\nconsole.log(content);\nawait browser.close();\nEOF\n\n# 3. Comparer les balises critiques\necho \"=== TITLE ===\" \necho \"Raw:\"  && grep -oP '\u003Ctitle>[^\u003C]+\u003C/title>' raw_html.html\necho \"Rendered:\" && grep -oP '\u003Ctitle>[^\u003C]+\u003C/title>' rendered_dom.html\n\necho \"=== H1 ===\"\necho \"Raw:\"  && grep -oP '\u003Ch1[^>]*>[^\u003C]+\u003C/h1>' raw_html.html\necho \"Rendered:\" && grep -oP '\u003Ch1[^>]*>[^\u003C]+\u003C/h1>' rendered_dom.html\n\necho \"=== CANONICAL ===\"\necho \"Raw:\" && grep -oP '\u003Clink[^>]*rel=\"canonical\"[^>]*>' raw_html.html\necho \"Rendered:\" && grep -oP '\u003Clink[^>]*rel=\"canonical\"[^>]*>' rendered_dom.html\n\necho \"=== META ROBOTS ===\"\necho \"Raw:\" && grep -oP '\u003Cmeta[^>]*name=\"robots\"[^>]*>' raw_html.html\necho \"Rendered:\" && grep -oP '\u003Cmeta[^>]*name=\"robots\"[^>]*>' rendered_dom.html\n```\n\nSi le H1 ou le contenu principal n'apparaissent que dans le DOM rendu et pas dans le HTML brut, vous avez un problème de CSR (Client-Side Rendering). Google peut indexer du contenu CSR, mais avec un délai (la [file d'attente de rendering](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) peut prendre des jours voire des semaines) et un risque d'indexation partielle.\n\n### L'outil de test intégré : URL Inspection\n\nL'outil d'inspection d'URL de Google Search Console ne fonctionne pas sur un staging protégé par auth. Utilisez plutôt le [Rich Results Test](https://search.google.com/test/rich-results) de Google — il accepte du code HTML collé directement, ce qui permet de tester le HTML brut de votre staging sans l'exposer.\n\nPour un test plus réaliste, l'onglet Rendering de Screaming Frog (Configuration > Spider > Rendering > JavaScript) simule le rendering Chromium et vous montre exactement ce que le crawler voit. Activez-le pour votre crawl staging.\n\n## Tester les redirections à l'échelle\n\nSur une migration, les redirections 301 sont le nerf de la guerre. Un fichier de mapping de 8 000 lignes contient forcément des erreurs. Le staging est le moment de les trouver.\n\n### Validation bulk des redirections\n\nExportez votre fichier de mapping (ancien URL → nouveau URL) et testez chaque ligne :\n\n```bash\n#!/bin/bash\n# redirect_test.sh — Tester un fichier de mapping de redirections\n# Format attendu du CSV : old_url,new_url (sans header)\n\nINPUT_FILE=\"redirect_mapping.csv\"\nSTAGING_DOMAIN=\"https://staging.votresite.fr\"\nERRORS=0\nTOTAL=0\n\necho \"URL_ancienne,URL_attendue,Status_Code,URL_effective,Résultat\" > redirect_report.csv\n\nwhile IFS=',' read -r old_path new_path; do\n    TOTAL=$((TOTAL + 1))\n    \n    # Suivre la redirection et capturer le status + destination\n    RESPONSE=$(curl -s -o /dev/null -w \"%{http_code}|%{redirect_url}\" \\\n        -A \"Googlebot\" \\\n        \"${STAGING_DOMAIN}${old_path}\")\n    \n    STATUS=$(echo \"$RESPONSE\" | cut -d'|' -f1)\n    REDIRECT_TO=$(echo \"$RESPONSE\" | cut -d'|' -f2)\n    \n    # Normaliser l'URL de destination (retirer le domaine pour comparer)\n    EFFECTIVE_PATH=$(echo \"$REDIRECT_TO\" | sed \"s|${STAGING_DOMAIN}||\")\n    \n    if [ \"$STATUS\" = \"301\" ] && [ \"$EFFECTIVE_PATH\" = \"$new_path\" ]; then\n        RESULT=\"OK\"\n    elif [ \"$STATUS\" = \"301\" ] && [ \"$EFFECTIVE_PATH\" != \"$new_path\" ]; then\n        RESULT=\"WRONG_DESTINATION\"\n        ERRORS=$((ERRORS + 1))\n    elif [ \"$STATUS\" = \"302\" ]; then\n        RESULT=\"302_NOT_301\"\n        ERRORS=$((ERRORS + 1))\n    elif [ \"$STATUS\" = \"200\" ]; then\n        RESULT=\"NO_REDIRECT\"\n        ERRORS=$((ERRORS + 1))\n    else\n        RESULT=\"ERROR_${STATUS}\"\n        ERRORS=$((ERRORS + 1))\n    fi\n    \n    echo \"${old_path},${new_path},${STATUS},${EFFECTIVE_PATH},${RESULT}\" >> redirect_report.csv\n    \n    # Afficher les erreurs en temps réel\n    if [ \"$RESULT\" != \"OK\" ]; then\n        echo \"❌ ${old_path} → Status: ${STATUS}, Got: ${EFFECTIVE_PATH} (Expected: ${new_path})\"\n    fi\n\ndone \u003C \"$INPUT_FILE\"\n\necho \"\"\necho \"=== RÉSULTAT ===\"\necho \"Total testé : ${TOTAL}\"\necho \"Erreurs : ${ERRORS}\"\necho \"Taux de succès : $(( (TOTAL - ERRORS) * 100 / TOTAL ))%\"\n```\n\nLes erreurs les plus fréquentes :\n\n- **302 au lieu de 301** : le serveur utilise des redirections temporaires. Fréquent avec certains CDN (Cloudflare, Fastly) qui transforment les 301 en 302 en mode \"orange cloud\" ou en développement.\n- **Chaînes de redirections** : l'ancienne URL redirige vers une URL intermédiaire qui redirige vers la finale. Ajoutez `-L --max-redirs 5` à curl et vérifiez que la chaîne ne dépasse pas 2 sauts.\n- **Redirections vers des 404** : la destination du 301 n'existe pas dans la nouvelle arborescence. C'est le pire scénario — vous perdez l'equity des backlinks.\n\n### Le cas spécifique des trailing slashes\n\nUn classique sous-estimé : `/categorie/chaussures` vs `/categorie/chaussures/`. Si votre ancien site utilisait des trailing slashes et le nouveau non (ou l'inverse), chaque URL devient techniquement une redirection manquante. Testez les deux variantes dans votre script.\n\n## Stress-test de performance et de crawlability\n\nLa performance du staging sous charge simule ce qui se passera quand Googlebot crawlera votre site à plein régime après le launch. Un site qui met 4 secondes à répondre sous charge verra son [crawl rate](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) plafonné par Google.\n\n### Simuler la charge de crawl\n\nUtilisez un outil de load testing pour envoyer des requêtes concurrentes sur les URLs les plus critiques :\n\n```bash\n# Avec Apache Bench (ab) — test rapide\n# 100 requêtes, 10 en parallèle, sur une page catégorie lourde\nab -n 100 -c 10 -H \"User-Agent: Googlebot\" \\\n  https://staging.votresite.fr/categorie/chaussures-homme/\n\n# Avec k6 pour un test plus réaliste (crawl de multiples URLs)\n# Installez k6 : brew install k6 (macOS) / snap install k6 (Linux)\n```\n\nVisez un TTFB (Time To First Byte) sous les 200ms pour les pages HTML en conditions de charge. Au-delà de 500ms, Googlebot ralentit significativement son crawl. Au-delà de 2 secondes, vous avez un problème d'infrastructure qui impactera directement l'indexation post-launch.\n\n### Vérifier le comportement des caches\n\nLe staging tourne souvent sans cache (Varnish, Redis, CDN) ou avec un cache mal configuré. C'est un faux positif massif : les temps de réponse en staging ne reflètent pas la production.\n\nInversement, si vous activez le cache sur le staging, vérifiez que les pages de test ne sont pas servies depuis un cache périmé. Un piège fréquent : vous corrigez un canonical, mais le CDN staging sert encore la version cachée pendant 24h. Purgez systématiquement le cache entre chaque itération de test.\n\n### Crawl budget et découvrabilité\n\nLancez un crawl Screaming Frog en mode \"List\" (avec la liste complète de vos URLs de production) ET en mode \"Spider\" (en laissant le crawler découvrir les pages par les liens internes). Comparez les deux :\n\n- Les URLs présentes en mode List mais absentes en mode Spider sont des pages orphelines — aucun lien interne n'y mène. Si ces pages avaient du trafic en production, elles le perdront.\n- Les URLs découvertes en mode Spider mais absentes de votre sitemap doivent y être ajoutées (ou bloquées via robots.txt si elles ne doivent pas être indexées).\n\nSur un site de 22 000 pages, cette comparaison révèle typiquement 5 à 15% de pages orphelines après une migration. C'est souvent lié à une refonte de la navigation (mega menu, filtres de catégories) qui a supprimé des chemins de liens sans que personne ne s'en rende compte.\n\n## Automatiser les tests avec une CI/CD pipeline\n\nLes tests manuels ne scalent pas. Si votre migration implique des déploiements quotidiens sur le staging pendant 3 mois, vous avez besoin de tests SEO automatisés dans votre pipeline CI/CD.\n\n### Tests SEO dans GitHub Actions / GitLab CI\n\nIntégrez des vérifications SEO basiques comme étape de votre pipeline de déploiement :\n\n```yaml\n# .github/workflows/seo-checks.yml\nname: SEO Regression Tests\n\non:\n  push:\n    branches: [staging, develop]\n\njobs:\n  seo-audit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      \n      - name: Wait for deployment\n        run: |\n          echo \"Waiting for staging deployment...\"\n          sleep 60\n          # Vérifier que le staging répond\n          STATUS=$(curl -s -o /dev/null -w \"%{http_code}\" \\\n            -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\n            https://staging.votresite.fr/)\n          if [ \"$STATUS\" != \"200\" ]; then\n            echo \"Staging not responding (HTTP $STATUS)\"\n            exit 1\n          fi\n\n      - name: Check critical pages SEO tags\n        run: |\n          ERRORS=0\n          \n          # Liste des pages critiques (top 20 trafic)\n          PAGES=(\n            \"/\"\n            \"/categorie/chaussures-homme\"\n            \"/categorie/chaussures-femme\"\n            \"/produit/nike-air-max-90\"\n            \"/guide/taille-chaussures\"\n          )\n          \n          for PAGE in \"${PAGES[@]}\"; do\n            HTML=$(curl -s \\\n              -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\n              \"https://staging.votresite.fr${PAGE}\")\n            \n            # Vérifier la présence d'un title\n            if ! echo \"$HTML\" | grep -q '\u003Ctitle>'; then\n              echo \"FAIL: Missing \u003Ctitle> on ${PAGE}\"\n              ERRORS=$((ERRORS + 1))\n            fi\n            \n            # Vérifier la présence d'un canonical\n            if ! echo \"$HTML\" | grep -q 'rel=\"canonical\"'; then\n              echo \"FAIL: Missing canonical on ${PAGE}\"\n              ERRORS=$((ERRORS + 1))\n            fi\n            \n            # Vérifier que le canonical ne pointe PAS vers staging\n            if echo \"$HTML\" | grep -q 'canonical.*staging\\.'; then\n              echo \"FAIL: Canonical points to staging on ${PAGE}\"\n              ERRORS=$((ERRORS + 1))\n            fi\n            \n            # Vérifier l'absence de noindex\n            if echo \"$HTML\" | grep -q 'noindex'; then\n              echo \"FAIL: noindex found on ${PAGE}\"\n              ERRORS=$((ERRORS + 1))\n            fi\n            \n            # Vérifier la présence d'un H1\n            if ! echo \"$HTML\" | grep -q '\u003Ch1'; then\n              echo \"FAIL: Missing H1 on ${PAGE}\"\n              ERRORS=$((ERRORS + 1))\n            fi\n          done\n          \n          if [ \"$ERRORS\" -gt 0 ]; then\n            echo \"SEO CHECK FAILED: ${ERRORS} issues found\"\n            exit 1\n          fi\n          echo \"All SEO checks passed\"\n\n      - name: Validate sitemap\n        run: |\n          SITEMAP=$(curl -s \\\n            -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\n            \"https://staging.votresite.fr/sitemap.xml\")\n          \n          # Vérifier que le sitemap est du XML valide\n          echo \"$SITEMAP\" | xmllint --noout - 2>&1\n          if [ $? -ne 0 ]; then\n            echo \"FAIL: sitemap.xml is not valid XML\"\n            exit 1\n          fi\n          \n          # Compter les URLs\n          URL_COUNT=$(echo \"$SITEMAP\" | grep -c '\u003Cloc>')\n          echo \"Sitemap contains ${URL_COUNT} URLs\"\n          \n          # Alerter si le nombre chute significativement\n          # (stockez le count attendu en variable d'env ou fichier)\n          EXPECTED_MIN=18000\n          if [ \"$URL_COUNT\" -lt \"$EXPECTED_MIN\" ]; then\n            echo \"FAIL: Sitemap has ${URL_COUNT} URLs, expected at least ${EXPECTED_MIN}\"\n            exit 1\n          fi\n```\n\nCe pipeline bloque le déploiement si une page critique perd son title, son canonical, ou si un `noindex` apparaît accidentellement. C'est un filet de sécurité minimal mais efficace.\n\nPour aller plus loin, des outils de monitoring continu comme Seogard détectent automatiquement ces régressions entre deux crawls, avec des alertes en temps réel. L'avantage par rapport à un script CI/CD maison : la couverture s'étend à l'ensemble du site, pas seulement aux pages que vous avez pensé à lister.\n\n## Les edge cases qui piègent les équipes expérimentées\n\nMême avec une méthodologie solide, certains problèmes ne se révèlent qu'en conditions spécifiques.\n\n### Le piège du DNS et des certificats SSL\n\nLe jour du launch, le flip DNS change la résolution de votre domaine. Mais les CDN, les certificats SSL et les configurations HSTS ont des caches propres. Testez le parcours complet :\n\n- Le certificat SSL du staging couvre-t-il le domaine de production ? Si vous utilisez un certificat wildcard `*.votresite.fr`, pas de problème. Avec Let's Encrypt et un certificat dédié, vous devrez le provisionner avant le flip.\n- Les en-têtes HSTS du staging correspondent-ils à ceux de la production ? Un `max-age` trop court sur le staging qui se propage en production peut temporairement casser le HTTPS.\n\n### Les contenus conditionnels\n\nCertains sites affichent du contenu différent selon la géolocalisation IP, le device, ou des cookies. Si votre staging ne réplique pas ces conditions, vous ne testez qu'une fraction du site. Vérifiez en particulier :\n\n- Les variantes mobile/desktop si vous utilisez du dynamic serving (rare aujourd'hui, mais encore présent sur des sites legacy)\n- Les pages localisées servies via IP geolocation plutôt que hreflang\n- Les messages de consentement cookies qui injectent du JavaScript bloquant le rendering\n\n### Le timing post-launch\n\nAprès le go-live, certains problèmes n'apparaissent qu'au moment où Google recrawle effectivement les pages — ce qui peut prendre 24 à 72 heures pour les pages les plus crawlées, et plusieurs semaines pour les pages profondes. Ne déclarez pas victoire le jour du launch. Surveillez quotidiennement les rapports Coverage/Indexing de Search Console pendant au minimum 4 semaines. Si des problèmes identifiés en staging comme les [soft 404 ou les erreurs d'indexation](/blog/how-soft-404s-and-indexing-issues-caused-a-90-traffic-collapse) n'ont pas été corrigés, les conséquences sur le trafic peuvent être drastiques.\n\n### Les structured data oubliées\n\nLes données structurées (Product, Article, BreadcrumbList, FAQ, Organization) sont souvent générées dynamiquement et facilement cassées lors d'une migration. Validez-les sur le staging avec le [Rich Results Test](https://search.google.com/test/rich-results) pour chaque template. Google a d'ailleurs récemment [retiré les résultats enrichis FAQ](/blog/google-drops-faq-rich-results-from-search-via-sejournal-mattgsouthern) de la recherche pour la plupart des sites — vérifiez si vos schemas FAQ sont encore pertinents avant de dépenser de l'énergie à les migrer.\n\n## Scénario complet : migration e-commerce 18K pages\n\nPour rendre la méthodologie concrète, voici le déroulé d'un stress-test staging sur un cas réel.\n\n**Contexte** : site e-commerce outdoor, 18 200 pages indexées, migration de PrestaShop 1.7 vers Shopify Plus avec un thème custom. Trafic organique mensuel : 340 000 sessions. Top 3 catégories représentant 62% du trafic.\n\n**Semaine 1 — Setup** : import de la base produit complète dans le staging Shopify. Configuration de l'auth HTTP via le proxy Cloudflare Workers. Crawl de référence production avec Screaming Frog (18","https://seogard.io/blog/how-to-stress-test-a-staging-environment-to-surface-risks-pre-launch-ask-an-seo-via-sejournal-helenpollitt1","Actualités SEO","2026-05-21T15:03:17.960Z","2026-05-21","Méthodologie complète pour stress-tester un environnement staging avant migration ou refonte. Scripts, configs et scénarios concrets pour détecter les régressions SEO.","\u003Cp>Un e-commerce mode de 22 000 pages migre de Magento 2 vers une stack headless Next.js + Shopify. Le staging est impeccable visuellement. L'équipe QA valide. Le go-live tombe un mardi matin. Jeudi, Google Search Console affiche une chute de 34% des impressions. Les canonical pointent toutes vers le staging. Les hreflang ont disparu. Le sitemap référence encore les anciennes URLs. Trois semaines de récupération, 180K€ de chiffre d'affaires perdus.\u003C/p>\n\u003Cp>Ce scénario n'est pas hypothétique. C'est le pattern classique d'un stress-test staging bâclé — ou inexistant. Helen Pollitt a récemment abordé le sujet dans \u003Ca href=\"https://www.searchenginejournal.com/ask-an-seo-how-do-you-stress-test-a-staging-environment/573559/\">sa chronique Ask An SEO sur Search Engine Journal\u003C/a>, en posant les bases méthodologiques. Cet article va plus loin : scripts d'audit automatisés, configs serveur, scénarios de crawl massif, et la méthodologie exacte pour transformer votre staging en filet de sécurité.\u003C/p>\n\u003Ch2>Préparer le staging comme un miroir de production fidèle\u003C/h2>\n\u003Cp>Le premier piège est de croire que votre staging reflète la production. Dans la majorité des cas, ce n'est pas le cas — et les écarts sont précisément là où les régressions SEO se cachent.\u003C/p>\n\u003Ch3>Le problème des données tronquées\u003C/h3>\n\u003Cp>Un staging avec 500 produits quand la production en compte 18 000 ne teste rien du tout en matière de SEO. Les problèmes de pagination, de faceted navigation, de crawl depth, de temps de génération des sitemaps — tout cela n'apparaît qu'à l'échelle réelle.\u003C/p>\n\u003Cp>Exportez un dump complet de la base de production (anonymisé pour le RGPD) et injectez-le dans le staging. Si la volumétrie rend ça impossible (certaines bases e-commerce dépassent les 50 Go), prenez au minimum un échantillon représentatif : toutes les catégories L1/L2, un ratio réaliste de produits actifs/inactifs, et l'intégralité des redirections 301 existantes.\u003C/p>\n\u003Ch3>Bloquer les robots tout en permettant l'audit\u003C/h3>\n\u003Cp>Le staging doit être invisible pour Google mais crawlable par vos outils. La méthode la plus fiable combine authentification HTTP et un \u003Ccode>robots.txt\u003C/code> permissif derrière cette auth :\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 le staging\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\">staging.votresite.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Auth HTTP basique — bloque tous les crawlers publics\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    auth_basic \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Staging Environment\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    auth_basic_user_file \u003C/span>\u003Cspan style=\"color:#E1E4E8\">/etc/nginx/.htpasswd;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Headers anti-indexation en doublon de sécurité\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">X-Robots-Tag \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"noindex, nofollow\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> always;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Permettre le crawl interne une fois authentifié\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#B392F0\"> /robots.txt \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        auth_basic \u003C/span>\u003Cspan style=\"color:#79B8FF\">off\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;  \u003C/span>\u003Cspan style=\"color:#6A737D\"># robots.txt accessible sans auth\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 200\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-agent: *\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">Disallow: /\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Le reste de la config...\u003C/span>\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:#F97583\">        proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend:3000;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        proxy_set_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Host $host;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        proxy_set_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">X-Forwarded-Proto $scheme;\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 \u003Ccode>X-Robots-Tag\u003C/code> en header HTTP est votre filet de sécurité si quelqu'un désactive l'auth ou si un lien staging fuite. Les deux mécanismes se complètent.\u003C/p>\n\u003Cp>Point critique : vérifiez que votre crawleur (Screaming Frog, Sitebulb) supporte l'authentification HTTP. Dans Screaming Frog, c'est dans Configuration > Authentication > Forms Based / HTTP Basic. Si vous utilisez une auth par cookie ou OAuth, configurez un custom header ou un cookie persistant dans les paramètres du crawler.\u003C/p>\n\u003Ch3>Aligner les variables d'environnement\u003C/h3>\n\u003Cp>Les frameworks modernes utilisent des variables d'environnement pour switch entre staging et production. C'est là que les canonical, les URLs de sitemap et les balises OG partent en vrille.\u003C/p>\n\u003Cp>Vérifiez ces variables en priorité :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Variables critiques à auditer dans le .env du staging\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Recherchez toute référence au domaine de production\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -rn\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"votresite.fr\\|NEXT_PUBLIC_SITE_URL\\|CANONICAL_BASE\\|SITEMAP_HOST\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  .env\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> .env.staging\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> .env.local\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> next.config.js\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> nuxt.config.ts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Résultat attendu : toutes les URLs doivent pointer vers staging.votresite.fr\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Si vous voyez www.votresite.fr dans le staging, c'est un bug pré-launch\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Dans un projet Next.js, le piège classique est \u003Ccode>NEXT_PUBLIC_SITE_URL\u003C/code> utilisé pour générer les canonical et les URLs OG. Si cette variable pointe vers la production dans le build staging, vos tests valident des canonical corrects... qui ne le seront plus après le flip DNS.\u003C/p>\n\u003Ch2>Auditer les balises SEO critiques à l'échelle\u003C/h2>\n\u003Cp>Le test manuel de 10 pages ne suffit pas. Vous avez besoin d'un crawl complet du staging, comparé au crawl de production, avec un diff automatisé.\u003C/p>\n\u003Ch3>Crawl comparatif production vs staging\u003C/h3>\n\u003Cp>Lancez un crawl Screaming Frog sur la production ET sur le staging. Exportez les deux en CSV, puis comparez avec un script :\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\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sys\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> compare_crawls\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(prod_csv: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, staging_csv: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, output: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    Compare les crawls production et staging.\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    Détecte : titles modifiés, canonical manquants, meta robots changés,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    status codes différents, hreflang supprimés.\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    prod \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.read_csv(prod_csv, \u003C/span>\u003Cspan style=\"color:#FFAB70\">usecols\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Status Code'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Title 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Meta Description 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'Canonical Link Element 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Meta Robots 1'\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:#9ECBFF\">        'H1-1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    staging \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.read_csv(staging_csv, \u003C/span>\u003Cspan style=\"color:#FFAB70\">usecols\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Status Code'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Title 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Meta Description 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'Canonical Link Element 1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Meta Robots 1'\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:#9ECBFF\">        'H1-1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Word Count'\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\">    # Normaliser les URLs pour comparer (retirer le domaine)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    prod[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> prod[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].str.replace(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">https\u003C/span>\u003Cspan style=\"color:#F97583\">?\u003C/span>\u003Cspan style=\"color:#DBEDFF\">://\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\u003C/span>\u003Cspan style=\"color:#F97583\">^\u003C/span>\u003Cspan style=\"color:#79B8FF\">/]\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">regex\u003C/span>\u003Cspan style=\"color:#F97583\">=\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\">    staging[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> staging[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Address'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].str.replace(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">https\u003C/span>\u003Cspan style=\"color:#F97583\">?\u003C/span>\u003Cspan style=\"color:#DBEDFF\">://\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\u003C/span>\u003Cspan style=\"color:#F97583\">^\u003C/span>\u003Cspan style=\"color:#79B8FF\">/]\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">regex\u003C/span>\u003Cspan style=\"color:#F97583\">=\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\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    merged \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> prod.merge(staging, \u003C/span>\u003Cspan style=\"color:#FFAB70\">on\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\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\">'_prod'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'_staging'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Détecter les régressions critiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    regressions \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # 1. Title disparu ou modifié\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    title_changes \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\">'Title 1_prod'\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\">'Title 1_staging'\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:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> _, row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> title_changes.iterrows():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        regressions.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'TITLE_CHANGED'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'prod_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Title 1_prod'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'staging_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Title 1_staging'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'severity'\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>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # 2. Canonical manquant ou modifié\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    canonical_changes \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\">'Canonical Link Element 1_prod'\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\">'Canonical Link Element 1_staging'\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:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> _, row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> canonical_changes.iterrows():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        regressions.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'CANONICAL_CHANGED'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'prod_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Canonical Link Element 1_prod'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'staging_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Canonical Link Element 1_staging'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'severity'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'CRITICAL'\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\">    # 3. Page devenue non-indexable\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    indexability_loss \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_prod'\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_staging'\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\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> _, row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> indexability_loss.iterrows():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        regressions.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'INDEXABILITY_LOST'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'prod_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexable'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'staging_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Indexability_staging'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'severity'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'CRITICAL'\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\">    # 4. Pages production manquantes dans le staging (404/disparues)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    prod_paths \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> set\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(prod[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    staging_paths \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> set\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(staging[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    missing \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> prod_paths \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> staging_paths\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> path \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> missing:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        regressions.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'path'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: path,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'PAGE_MISSING_IN_STAGING'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'prod_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'EXISTS'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'staging_value'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'404 or GONE'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'severity'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'CRITICAL'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    results \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.DataFrame(regressions)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    results \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> results.sort_values(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'severity'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    results.to_csv(output, \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\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Résumé\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n{\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'='\u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\">60}\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\">\"STAGING REGRESSION REPORT\"\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\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'='\u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\">60}\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\">\"Total régressions détectées : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(regressions)\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\">\"  CRITICAL : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(results[results.severity \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'CRITICAL'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\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\">\"  HIGH     : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(results[results.severity \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'HIGH'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\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 manquantes : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(missing)\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\">\"Rapport exporté : \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">output\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:#F97583\">if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> __name__\u003C/span>\u003Cspan style=\"color:#F97583\"> ==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '__main__'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    compare_crawls(sys.argv[\u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">], sys.argv[\u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">], sys.argv[\u003C/span>\u003Cspan style=\"color:#79B8FF\">3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Exécution : \u003Ccode>python compare_crawls.py crawl_prod.csv crawl_staging.csv report.csv\u003C/code>\u003C/p>\n\u003Cp>Ce script détecte quatre catégories de régressions. En pratique, sur une migration d'un site de 15 000 pages, attendez-vous à trouver entre 200 et 2 000 écarts. La plupart seront bénins (légères modifications de title intentionnelles), mais les CRITICAL — canonical cassés, pages disparues, indexability perdue — doivent être résolus avant toute mise en production.\u003C/p>\n\u003Ch3>Focus sur les pages à fort trafic\u003C/h3>\n\u003Cp>Toutes les pages ne méritent pas le même niveau d'attention. Croisez votre rapport de régressions avec les données Search Console pour prioriser :\u003C/p>\n\u003Cp>Exportez les pages avec le plus d'impressions/clics depuis Search Console (Performance > Pages > Export). Les 200 premières pages représentent souvent 70-80% du trafic organique. Si une régression touche l'une d'elles, c'est un showstopper.\u003C/p>\n\u003Ch2>Valider le rendering et le JavaScript côté SEO\u003C/h2>\n\u003Cp>Les migrations vers des frameworks JavaScript (React, Vue, Angular, ou leurs variantes SSR) introduisent un risque spécifique : le contenu visible dans le navigateur peut être invisible pour Googlebot. Le staging est le moment de le vérifier — pas après le launch.\u003C/p>\n\u003Ch3>Comparer le HTML servi vs le DOM rendu\u003C/h3>\n\u003Cp>Pour chaque template type (page produit, catégorie, article, landing page), comparez le HTML retourné par le serveur (ce que reçoit un curl) avec le DOM après exécution JavaScript :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 1. Récupérer le HTML brut (ce que voit le serveur)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -A\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  \"https://staging.votresite.fr/categorie/chaussures-homme\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -o\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> raw_html.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 2. Récupérer le DOM rendu via Puppeteer / Chrome headless\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">npx\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> puppeteer-core\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --no-sandbox\u003C/span>\u003Cspan style=\"color:#F97583\"> &#x3C;&#x3C;\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'EOF'\u003C/span>\u003Cspan style=\"color:#F97583\"> >\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> rendered_dom.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">const browser = await require('puppeteer').launch({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  args: ['--no-sandbox']\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">});\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">const page = await browser.newPage();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">await page.setUserAgent('Mozilla/5.0 (compatible; Googlebot/2.1)');\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">await page.goto('https://staging.votresite.fr/categorie/chaussures-homme', {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  waitUntil: 'networkidle0',\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  timeout: 30000\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">});\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">// Attendre le rendering complet\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">await page.waitForTimeout(5000);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">const content = await page.content();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">console.log(content);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">await browser.close();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">EOF\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 3. Comparer les balises critiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"=== TITLE ===\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Raw:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">  &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;title>[^&#x3C;]+&#x3C;/title>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> raw_html.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Rendered:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;title>[^&#x3C;]+&#x3C;/title>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> rendered_dom.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"=== H1 ===\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Raw:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">  &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;h1[^>]*>[^&#x3C;]+&#x3C;/h1>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> raw_html.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Rendered:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;h1[^>]*>[^&#x3C;]+&#x3C;/h1>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> rendered_dom.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"=== CANONICAL ===\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Raw:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;link[^>]*rel=\"canonical\"[^>]*>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> raw_html.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Rendered:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;link[^>]*rel=\"canonical\"[^>]*>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> rendered_dom.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"=== META ROBOTS ===\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Raw:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;meta[^>]*name=\"robots\"[^>]*>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> raw_html.html\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Rendered:\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> &#x26;&#x26; \u003C/span>\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;meta[^>]*name=\"robots\"[^>]*>'\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> rendered_dom.html\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Si le H1 ou le contenu principal n'apparaissent que dans le DOM rendu et pas dans le HTML brut, vous avez un problème de CSR (Client-Side Rendering). Google peut indexer du contenu CSR, mais avec un délai (la \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics\">file d'attente de rendering\u003C/a> peut prendre des jours voire des semaines) et un risque d'indexation partielle.\u003C/p>\n\u003Ch3>L'outil de test intégré : URL Inspection\u003C/h3>\n\u003Cp>L'outil d'inspection d'URL de Google Search Console ne fonctionne pas sur un staging protégé par auth. Utilisez plutôt le \u003Ca href=\"https://search.google.com/test/rich-results\">Rich Results Test\u003C/a> de Google — il accepte du code HTML collé directement, ce qui permet de tester le HTML brut de votre staging sans l'exposer.\u003C/p>\n\u003Cp>Pour un test plus réaliste, l'onglet Rendering de Screaming Frog (Configuration > Spider > Rendering > JavaScript) simule le rendering Chromium et vous montre exactement ce que le crawler voit. Activez-le pour votre crawl staging.\u003C/p>\n\u003Ch2>Tester les redirections à l'échelle\u003C/h2>\n\u003Cp>Sur une migration, les redirections 301 sont le nerf de la guerre. Un fichier de mapping de 8 000 lignes contient forcément des erreurs. Le staging est le moment de les trouver.\u003C/p>\n\u003Ch3>Validation bulk des redirections\u003C/h3>\n\u003Cp>Exportez votre fichier de mapping (ancien URL → nouveau URL) et testez chaque ligne :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">#!/bin/bash\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># redirect_test.sh — Tester un fichier de mapping de redirections\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Format attendu du CSV : old_url,new_url (sans header)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">INPUT_FILE\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"redirect_mapping.csv\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">STAGING_DOMAIN\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://staging.votresite.fr\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">ERRORS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">TOTAL\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"URL_ancienne,URL_attendue,Status_Code,URL_effective,Résultat\"\u003C/span>\u003Cspan style=\"color:#F97583\"> >\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> redirect_report.csv\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">while\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> IFS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">','\u003C/span>\u003Cspan style=\"color:#79B8FF\"> read\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -r\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> old_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> new_path\u003C/span>\u003Cspan style=\"color:#E1E4E8\">; \u003C/span>\u003Cspan style=\"color:#F97583\">do\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    TOTAL\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$((\u003C/span>\u003Cspan style=\"color:#B392F0\">TOTAL\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> +\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Suivre la redirection et capturer le status + destination\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    RESPONSE\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$(\u003C/span>\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -o\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /dev/null\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -w\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"%{http_code}|%{redirect_url}\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        -A\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Googlebot\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">STAGING_DOMAIN\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">old_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    STATUS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$(\u003C/span>\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$RESPONSE\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> cut\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -d\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'|'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -f1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    REDIRECT_TO\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$(\u003C/span>\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$RESPONSE\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> cut\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -d\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'|'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -f2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Normaliser l'URL de destination (retirer le domaine pour comparer)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    EFFECTIVE_PATH\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$(\u003C/span>\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$REDIRECT_TO\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sed\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"s|${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">STAGING_DOMAIN\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}||\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"301\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ] &#x26;&#x26; [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$EFFECTIVE_PATH\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$new_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        RESULT\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"OK\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    elif\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"301\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ] &#x26;&#x26; [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$EFFECTIVE_PATH\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> !=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$new_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        RESULT\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"WRONG_DESTINATION\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ERRORS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$((\u003C/span>\u003Cspan style=\"color:#B392F0\">ERRORS\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> +\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    elif\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"302\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        RESULT\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"302_NOT_301\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ERRORS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$((\u003C/span>\u003Cspan style=\"color:#B392F0\">ERRORS\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> +\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    elif\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"200\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        RESULT\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"NO_REDIRECT\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ERRORS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$((\u003C/span>\u003Cspan style=\"color:#B392F0\">ERRORS\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> +\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    else\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        RESULT\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ERROR_${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ERRORS\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$((\u003C/span>\u003Cspan style=\"color:#B392F0\">ERRORS\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> +\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">old_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">},${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">new_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">},${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">},${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">EFFECTIVE_PATH\u003C/span>\u003Cspan style=\"color:#9ECBFF\">},${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">RESULT\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"\u003C/span>\u003Cspan style=\"color:#F97583\"> >>\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> redirect_report.csv\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Afficher les erreurs en temps réel\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$RESULT\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> !=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"OK\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"❌ ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">old_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} → Status: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">STATUS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}, Got: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">EFFECTIVE_PATH\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} (Expected: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">new_path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">})\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">done\u003C/span>\u003Cspan style=\"color:#F97583\"> &#x3C;\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$INPUT_FILE\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"=== RÉSULTAT ===\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Total testé : ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">TOTAL\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Erreurs : ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ERRORS\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Taux de succès : $(( (\u003C/span>\u003Cspan style=\"color:#B392F0\">TOTAL\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> - ERRORS) \u003C/span>\u003Cspan style=\"color:#B392F0\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> / TOTAL ))%\"\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Les erreurs les plus fréquentes :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>302 au lieu de 301\u003C/strong> : le serveur utilise des redirections temporaires. Fréquent avec certains CDN (Cloudflare, Fastly) qui transforment les 301 en 302 en mode \"orange cloud\" ou en développement.\u003C/li>\n\u003Cli>\u003Cstrong>Chaînes de redirections\u003C/strong> : l'ancienne URL redirige vers une URL intermédiaire qui redirige vers la finale. Ajoutez \u003Ccode>-L --max-redirs 5\u003C/code> à curl et vérifiez que la chaîne ne dépasse pas 2 sauts.\u003C/li>\n\u003Cli>\u003Cstrong>Redirections vers des 404\u003C/strong> : la destination du 301 n'existe pas dans la nouvelle arborescence. C'est le pire scénario — vous perdez l'equity des backlinks.\u003C/li>\n\u003C/ul>\n\u003Ch3>Le cas spécifique des trailing slashes\u003C/h3>\n\u003Cp>Un classique sous-estimé : \u003Ccode>/categorie/chaussures\u003C/code> vs \u003Ccode>/categorie/chaussures/\u003C/code>. Si votre ancien site utilisait des trailing slashes et le nouveau non (ou l'inverse), chaque URL devient techniquement une redirection manquante. Testez les deux variantes dans votre script.\u003C/p>\n\u003Ch2>Stress-test de performance et de crawlability\u003C/h2>\n\u003Cp>La performance du staging sous charge simule ce qui se passera quand Googlebot crawlera votre site à plein régime après le launch. Un site qui met 4 secondes à répondre sous charge verra son \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget\">crawl rate\u003C/a> plafonné par Google.\u003C/p>\n\u003Ch3>Simuler la charge de crawl\u003C/h3>\n\u003Cp>Utilisez un outil de load testing pour envoyer des requêtes concurrentes sur les URLs les plus critiques :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Avec Apache Bench (ab) — test rapide\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 100 requêtes, 10 en parallèle, sur une page catégorie lourde\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">ab\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -n\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -c\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 10\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Googlebot\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">  https://staging.votresite.fr/categorie/chaussures-homme/\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Avec k6 pour un test plus réaliste (crawl de multiples URLs)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Installez k6 : brew install k6 (macOS) / snap install k6 (Linux)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Visez un TTFB (Time To First Byte) sous les 200ms pour les pages HTML en conditions de charge. Au-delà de 500ms, Googlebot ralentit significativement son crawl. Au-delà de 2 secondes, vous avez un problème d'infrastructure qui impactera directement l'indexation post-launch.\u003C/p>\n\u003Ch3>Vérifier le comportement des caches\u003C/h3>\n\u003Cp>Le staging tourne souvent sans cache (Varnish, Redis, CDN) ou avec un cache mal configuré. C'est un faux positif massif : les temps de réponse en staging ne reflètent pas la production.\u003C/p>\n\u003Cp>Inversement, si vous activez le cache sur le staging, vérifiez que les pages de test ne sont pas servies depuis un cache périmé. Un piège fréquent : vous corrigez un canonical, mais le CDN staging sert encore la version cachée pendant 24h. Purgez systématiquement le cache entre chaque itération de test.\u003C/p>\n\u003Ch3>Crawl budget et découvrabilité\u003C/h3>\n\u003Cp>Lancez un crawl Screaming Frog en mode \"List\" (avec la liste complète de vos URLs de production) ET en mode \"Spider\" (en laissant le crawler découvrir les pages par les liens internes). Comparez les deux :\u003C/p>\n\u003Cul>\n\u003Cli>Les URLs présentes en mode List mais absentes en mode Spider sont des pages orphelines — aucun lien interne n'y mène. Si ces pages avaient du trafic en production, elles le perdront.\u003C/li>\n\u003Cli>Les URLs découvertes en mode Spider mais absentes de votre sitemap doivent y être ajoutées (ou bloquées via robots.txt si elles ne doivent pas être indexées).\u003C/li>\n\u003C/ul>\n\u003Cp>Sur un site de 22 000 pages, cette comparaison révèle typiquement 5 à 15% de pages orphelines après une migration. C'est souvent lié à une refonte de la navigation (mega menu, filtres de catégories) qui a supprimé des chemins de liens sans que personne ne s'en rende compte.\u003C/p>\n\u003Ch2>Automatiser les tests avec une CI/CD pipeline\u003C/h2>\n\u003Cp>Les tests manuels ne scalent pas. Si votre migration implique des déploiements quotidiens sur le staging pendant 3 mois, vous avez besoin de tests SEO automatisés dans votre pipeline CI/CD.\u003C/p>\n\u003Ch3>Tests SEO dans GitHub Actions / GitLab CI\u003C/h3>\n\u003Cp>Intégrez des vérifications SEO basiques comme étape de votre pipeline de déploiement :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># .github/workflows/seo-checks.yml\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">SEO Regression Tests\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">on\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  push\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    branches\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">staging\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">develop\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">jobs\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  seo-audit\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    runs-on\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">ubuntu-latest\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    steps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">uses\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">actions/checkout@v4\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>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Wait for deployment\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          echo \"Waiting for staging deployment...\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          sleep 60\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # Vérifier que le staging répond\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          STATUS=$(curl -s -o /dev/null -w \"%{http_code}\" \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            https://staging.votresite.fr/)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          if [ \"$STATUS\" != \"200\" ]; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            echo \"Staging not responding (HTTP $STATUS)\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            exit 1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Check critical pages SEO tags\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          ERRORS=0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # Liste des pages critiques (top 20 trafic)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          PAGES=(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"/\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"/categorie/chaussures-homme\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"/categorie/chaussures-femme\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"/produit/nike-air-max-90\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"/guide/taille-chaussures\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          for PAGE in \"${PAGES[@]}\"; do\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            HTML=$(curl -s \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              \"https://staging.votresite.fr${PAGE}\")\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            # Vérifier la présence d'un title\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            if ! echo \"$HTML\" | grep -q '&#x3C;title>'; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              echo \"FAIL: Missing &#x3C;title> on ${PAGE}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              ERRORS=$((ERRORS + 1))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            # Vérifier la présence d'un canonical\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            if ! echo \"$HTML\" | grep -q 'rel=\"canonical\"'; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              echo \"FAIL: Missing canonical on ${PAGE}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              ERRORS=$((ERRORS + 1))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            # Vérifier que le canonical ne pointe PAS vers staging\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            if echo \"$HTML\" | grep -q 'canonical.*staging\\.'; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              echo \"FAIL: Canonical points to staging on ${PAGE}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              ERRORS=$((ERRORS + 1))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            # Vérifier l'absence de noindex\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            if echo \"$HTML\" | grep -q 'noindex'; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              echo \"FAIL: noindex found on ${PAGE}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              ERRORS=$((ERRORS + 1))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            # Vérifier la présence d'un H1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            if ! echo \"$HTML\" | grep -q '&#x3C;h1'; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              echo \"FAIL: Missing H1 on ${PAGE}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">              ERRORS=$((ERRORS + 1))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          done\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          if [ \"$ERRORS\" -gt 0 ]; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            echo \"SEO CHECK FAILED: ${ERRORS} issues found\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            exit 1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          echo \"All SEO checks passed\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Validate sitemap\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          SITEMAP=$(curl -s \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            -u \"${{ secrets.STAGING_USER }}:${{ secrets.STAGING_PASS }}\" \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            \"https://staging.votresite.fr/sitemap.xml\")\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # Vérifier que le sitemap est du XML valide\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          echo \"$SITEMAP\" | xmllint --noout - 2>&#x26;1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          if [ $? -ne 0 ]; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            echo \"FAIL: sitemap.xml is not valid XML\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            exit 1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # Compter les URLs\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          URL_COUNT=$(echo \"$SITEMAP\" | grep -c '&#x3C;loc>')\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          echo \"Sitemap contains ${URL_COUNT} URLs\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # Alerter si le nombre chute significativement\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          # (stockez le count attendu en variable d'env ou fichier)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          EXPECTED_MIN=18000\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          if [ \"$URL_COUNT\" -lt \"$EXPECTED_MIN\" ]; then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            echo \"FAIL: Sitemap has ${URL_COUNT} URLs, expected at least ${EXPECTED_MIN}\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            exit 1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          fi\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Ce pipeline bloque le déploiement si une page critique perd son title, son canonical, ou si un \u003Ccode>noindex\u003C/code> apparaît accidentellement. C'est un filet de sécurité minimal mais efficace.\u003C/p>\n\u003Cp>Pour aller plus loin, des outils de monitoring continu comme Seogard détectent automatiquement ces régressions entre deux crawls, avec des alertes en temps réel. L'avantage par rapport à un script CI/CD maison : la couverture s'étend à l'ensemble du site, pas seulement aux pages que vous avez pensé à lister.\u003C/p>\n\u003Ch2>Les edge cases qui piègent les équipes expérimentées\u003C/h2>\n\u003Cp>Même avec une méthodologie solide, certains problèmes ne se révèlent qu'en conditions spécifiques.\u003C/p>\n\u003Ch3>Le piège du DNS et des certificats SSL\u003C/h3>\n\u003Cp>Le jour du launch, le flip DNS change la résolution de votre domaine. Mais les CDN, les certificats SSL et les configurations HSTS ont des caches propres. Testez le parcours complet :\u003C/p>\n\u003Cul>\n\u003Cli>Le certificat SSL du staging couvre-t-il le domaine de production ? Si vous utilisez un certificat wildcard \u003Ccode>*.votresite.fr\u003C/code>, pas de problème. Avec Let's Encrypt et un certificat dédié, vous devrez le provisionner avant le flip.\u003C/li>\n\u003Cli>Les en-têtes HSTS du staging correspondent-ils à ceux de la production ? Un \u003Ccode>max-age\u003C/code> trop court sur le staging qui se propage en production peut temporairement casser le HTTPS.\u003C/li>\n\u003C/ul>\n\u003Ch3>Les contenus conditionnels\u003C/h3>\n\u003Cp>Certains sites affichent du contenu différent selon la géolocalisation IP, le device, ou des cookies. Si votre staging ne réplique pas ces conditions, vous ne testez qu'une fraction du site. Vérifiez en particulier :\u003C/p>\n\u003Cul>\n\u003Cli>Les variantes mobile/desktop si vous utilisez du dynamic serving (rare aujourd'hui, mais encore présent sur des sites legacy)\u003C/li>\n\u003Cli>Les pages localisées servies via IP geolocation plutôt que hreflang\u003C/li>\n\u003Cli>Les messages de consentement cookies qui injectent du JavaScript bloquant le rendering\u003C/li>\n\u003C/ul>\n\u003Ch3>Le timing post-launch\u003C/h3>\n\u003Cp>Après le go-live, certains problèmes n'apparaissent qu'au moment où Google recrawle effectivement les pages — ce qui peut prendre 24 à 72 heures pour les pages les plus crawlées, et plusieurs semaines pour les pages profondes. Ne déclarez pas victoire le jour du launch. Surveillez quotidiennement les rapports Coverage/Indexing de Search Console pendant au minimum 4 semaines. Si des problèmes identifiés en staging comme les \u003Ca href=\"/blog/how-soft-404s-and-indexing-issues-caused-a-90-traffic-collapse\">soft 404 ou les erreurs d'indexation\u003C/a> n'ont pas été corrigés, les conséquences sur le trafic peuvent être drastiques.\u003C/p>\n\u003Ch3>Les structured data oubliées\u003C/h3>\n\u003Cp>Les données structurées (Product, Article, BreadcrumbList, FAQ, Organization) sont souvent générées dynamiquement et facilement cassées lors d'une migration. Validez-les sur le staging avec le \u003Ca href=\"https://search.google.com/test/rich-results\">Rich Results Test\u003C/a> pour chaque template. Google a d'ailleurs récemment \u003Ca href=\"/blog/google-drops-faq-rich-results-from-search-via-sejournal-mattgsouthern\">retiré les résultats enrichis FAQ\u003C/a> de la recherche pour la plupart des sites — vérifiez si vos schemas FAQ sont encore pertinents avant de dépenser de l'énergie à les migrer.\u003C/p>\n\u003Ch2>Scénario complet : migration e-commerce 18K pages\u003C/h2>\n\u003Cp>Pour rendre la méthodologie concrète, voici le déroulé d'un stress-test staging sur un cas réel.\u003C/p>\n\u003Cp>\u003Cstrong>Contexte\u003C/strong> : site e-commerce outdoor, 18 200 pages indexées, migration de PrestaShop 1.7 vers Shopify Plus avec un thème custom. Trafic organique mensuel : 340 000 sessions. Top 3 catégories représentant 62% du trafic.\u003C/p>\n\u003Cp>\u003Cstrong>Semaine 1 — Setup\u003C/strong> : import de la base produit complète dans le staging Shopify. Configuration de l'auth HTTP via le proxy Cloudflare Workers. Crawl de référence production avec Screaming Frog (18\u003C/p>",null,14,[18,19,20,21,22],"stress-test","staging","migration SEO","pre-launch","SEO technique","Stress-test staging SEO : checklist technique pré-launch","Thu May 21 2026 15:03:17 GMT+0000 (Coordinated Universal Time)",[26,40,53],{"_id":27,"slug":28,"__v":6,"author":7,"canonical":29,"category":10,"createdAt":30,"date":12,"description":31,"image":15,"imageAlt":15,"readingTime":32,"tags":33,"title":38,"updatedAt":39},"6a0ea00daa6b273b0cf4cc1a","wordpress-7-0-launches-with-native-ai-integration-via-sejournal-martinibuster","https://seogard.io/blog/wordpress-7-0-launches-with-native-ai-integration-via-sejournal-martinibuster","2026-05-21T06:02:53.501Z","Analyse technique de l'intégration AI native dans WordPress 7.0 : impacts sur le rendu HTML, le crawl budget, le SSR et les stratégies SEO à adapter.",12,[34,35,22,36,37],"wordpress","AI","SSR","crawl budget","WordPress 7.0 et AI native : impacts SEO techniques","Thu May 21 2026 06:02:53 GMT+0000 (Coordinated Universal Time)",{"_id":41,"slug":42,"__v":6,"author":7,"canonical":43,"category":10,"createdAt":44,"date":12,"description":45,"image":15,"imageAlt":15,"readingTime":32,"tags":46,"title":51,"updatedAt":52},"6a0f48cbaa6b273b0c80073c","google-begins-rolling-out-may-2026-core-update-via-sejournal-mattgsouthern","https://seogard.io/blog/google-begins-rolling-out-may-2026-core-update-via-sejournal-mattgsouthern","2026-05-21T18:02:51.223Z","Google déploie sa core update de mai 2026. Analyse technique, scripts de monitoring, scénarios d'impact et plan d'action pour sites à fort volume de pages.",[47,48,22,49,50],"google core update","mai 2026","monitoring","search console","May 2026 Core Update : analyse technique et plan d'action","Thu May 21 2026 18:02:51 GMT+0000 (Coordinated Universal Time)",{"_id":54,"slug":55,"__v":6,"author":7,"canonical":56,"category":10,"createdAt":57,"date":58,"description":59,"image":15,"imageAlt":15,"readingTime":32,"tags":60,"title":66,"updatedAt":67},"6a0d4ea4aa6b273b0cde629a","reasoning-lift-what-happens-to-brand-visibility-when-ai-thinks-harder","https://seogard.io/blog/reasoning-lift-what-happens-to-brand-visibility-when-ai-thinks-harder","2026-05-20T06:03:16.188Z","2026-05-20","Analyse technique de 200 réponses GPT-5.2 : le raisonnement élevé cite plus de sources, favorise le haut de funnel et redéfinit la visibilité de marque.",[61,62,63,64,65],"reasoning lift","AI search","brand visibility","GEO","LLM","Reasoning lift : impact du raisonnement IA sur la visibilité des marques","Wed May 20 2026 06:03:16 GMT+0000 (Coordinated Universal Time)"]