[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fQwY_5LbSXB83q43iM0d_Rw1Mn4yDr2xxBFJKTyf3JM0":3,"$fvkHncHWyL_WsCSp92S9Mcrv9nGn8xa3_BrO_uN_hHHE":24},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":22,"updatedAt":23},"6a19b85daa6b273b0c2a585c","migration-www-vers-no-www-google-indexe-les-deux-versions-pendant-3-mois",0,"Equipe Seogard","# Migration www vers no-www : Google indexe les deux versions pendant 3 mois\n\nMardi 14 mars, 11h. L'équipe infra d'une plateforme e-commerce française — 12 000 pages produit, 380 000 visites organiques mensuelles — bascule le domaine principal de `www.example.fr` vers `example.fr`. Le CDN répond. Le certificat couvre les deux variantes. Le navigateur affiche la bonne URL. Tout le monde passe à autre chose. Douze semaines plus tard, le trafic organique a perdu 31 %. Personne n'a vu le problème arriver. Google, lui, l'a vu dès le premier jour.\n\n## Mercredi 7 juin — T+84 jours : le tableau de bord vire au rouge\n\nC'est la responsable acquisition qui lève l'alerte. Le dashboard GA4 du mois de mai affiche 262 000 sessions organiques. En février, avant la migration, le compteur tournait à 381 000. L'écart se creuse depuis mars, mais l'équipe l'avait attribué à la saisonnalité printanière — le catalogue est orienté équipement d'intérieur, et le printemps ralentit toujours un peu.\n\nSauf que cette fois, le ralentissement ne correspond pas à la courbe des années précédentes. Il est deux fois plus marqué.\n\nLe lead SEO ouvre Search Console à 14h20. Premier réflexe : vérifier les propriétés. Il en trouve deux. `https://www.example.fr` et `https://example.fr`. Les deux remontent des données. Les deux affichent des impressions. Et les deux montrent des pages indexées.\n\nRequête `site:www.example.fr` dans Google : 8 400 résultats.\nRequête `site:example.fr` : 9 100 résultats.\n\nLe catalogue complet compte 12 000 URLs. Google en a indexé 17 500 — dont environ 5 000 en double. Certaines fiches produit apparaissent deux fois dans l'index, une fois avec le préfixe `www`, une fois sans.\n\nL'hypothèse initiale — un bug de sitemap — tombe vite. Le fichier `sitemap.xml` pointe bien vers `example.fr`. Pas de trace de `www` dedans. Le problème est ailleurs.\n\nÀ 15h45, un développeur backend lance un `curl -I` sur une URL produit en version `www` :\n\n```bash\ncurl -sI \"https://www.example.fr/produit/lampe-articulee-noir\" | grep -iE \"^(HTTP|location|x-redirect)\"\n```\n\nRésultat :\n\n```\nHTTP/2 200\n```\n\nPas de redirection. Pas de 301. Le serveur répond 200 sur les deux variantes. Le CDN sert le contenu identique sur `www.example.fr` et `example.fr`, sans aucune distinction.\n\nLe lead SEO vérifie alors la balise canonical dans le HTML rendu. Il inspecte le source de la page `www` :\n\n```html\n\u003Clink rel=\"canonical\" href=\"https://www.example.fr/produit/lampe-articulee-noir\" />\n```\n\nLa canonical est auto-référente — elle pointe vers elle-même, en version `www`. Sur la version `example.fr`, la canonical pointe bien vers `example.fr`. Chaque version se déclare légitime. Google n'a aucun signal pour trancher.\n\nÀ 16h30, le diagnostic tombe : la migration DNS a bien eu lieu, mais aucune règle de redirection n'a été posée sur le serveur ou le CDN. Et le template HTML génère la canonical dynamiquement à partir de l'URL de la requête entrante, pas d'une variable d'environnement fixe.\n\nCe n'est pas un bug mineur. C'est une duplication massive de l'index, active depuis 84 jours.\n\n## Le bug : un canonical dynamique et zéro 301\n\nPour comprendre comment 12 000 pages se retrouvent dupliquées dans l'index pendant trois mois sans que personne ne s'en aperçoive, il faut démonter trois couches : le DNS, le serveur HTTP, et le rendu HTML.\n\n### Couche 1 — Le DNS\n\nAvant la migration, la configuration DNS ressemblait à ça :\n\n```\nwww.example.fr.    A     203.0.113.42\nexample.fr.        CNAME www.example.fr.\n```\n\nLe domaine nu (`example.fr`) renvoyait vers `www`. Après la migration, l'équipe infra a inversé :\n\n```\nexample.fr.        A     203.0.113.42\nwww.example.fr.    CNAME example.fr.\n```\n\nLe `CNAME` de `www` pointe désormais vers le domaine nu. Les deux résolvent vers la même IP. Les deux atteignent le même serveur. Les deux reçoivent le même contenu. Le DNS fait son travail — il résout. Mais il ne redirige pas. Ce n'est pas son rôle.\n\n### Couche 2 — Le serveur HTTP (Nginx derrière le CDN)\n\nLe fichier de configuration Nginx contenait deux `server` blocks. L'ancien, pour `www`, et le nouveau, pour le domaine nu. Le plan initial prévoyait de transformer l'ancien block en redirection 301. Le ticket Jira existait. Il était même assigné. Mais il est resté en statut \"Ready for Dev\" pendant toute la durée de l'incident.\n\nVoici ce que le bloc `www` aurait dû contenir :\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name www.example.fr;\n\n    ssl_certificate     /etc/ssl/certs/example.fr.pem;\n    ssl_certificate_key /etc/ssl/private/example.fr.key;\n\n    return 301 https://example.fr$request_uri;\n}\n```\n\nVoici ce qu'il contenait réellement :\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name www.example.fr;\n\n    ssl_certificate     /etc/ssl/certs/example.fr.pem;\n    ssl_certificate_key /etc/ssl/private/example.fr.key;\n\n    root /var/www/html;\n    index index.html;\n\n    location / {\n        proxy_pass http://backend:3000;\n    }\n}\n```\n\nLe serveur `www` servait le site normalement. Même backend, même contenu, même rendu. Deux portes d'entrée, un seul magasin.\n\n### Couche 3 — Le canonical dynamique\n\nLe moteur de templates (un setup Node.js / Express avec un rendu SSR maison) générait la balise canonical à partir de l'objet `req` :\n\n```javascript\n// middleware/seo.js\napp.use((req, res, next) => {\n  res.locals.canonicalUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;\n  next();\n});\n```\n\nEt dans le template EJS :\n\n```html\n\u003Clink rel=\"canonical\" href=\"\u003C%= canonicalUrl %>\" />\n```\n\nQuand Googlebot crawlait `https://www.example.fr/produit/X`, la canonical renvoyait `https://www.example.fr/produit/X`. Quand il crawlait `https://example.fr/produit/X`, elle renvoyait `https://example.fr/produit/X`. Les deux pages se déclaraient canoniques. Google recevait deux signaux contradictoires et choisissait l'un ou l'autre au cas par cas — souvent en gardant les deux.\n\n### Pourquoi les tests n'ont rien vu\n\nL'équipe QA testait en environnement staging, derrière un sous-domaine `staging.example.fr`. La variable `www` n'existait pas dans cet environnement. Les tests Lighthouse, les audits Screaming Frog, les snapshots Puppeteer : tout ciblait `example.fr` directement. Personne n'a crawlé `www.example.fr` après la mise en production pour vérifier qu'il renvoyait bien un 301.\n\nScreaming Frog aurait détecté le problème en 30 secondes. Il suffisait de lancer un crawl sur `https://www.example.fr` et d'observer la colonne \"Status Code\". 200 partout au lieu de 301. Mais le crawl post-migration n'a jamais été exécuté sur la variante `www`.\n\n### L'impact sur le crawl budget\n\nLes logs serveur (analysés a posteriori avec GoAccess) montrent que Googlebot a crawlé 4 200 URLs en `www` et 5 800 en `example.fr` sur le mois de mai. Le budget total — environ 10 000 fetches par jour — se répartissait entre les deux variantes. Les pages profondes du catalogue (catégories de niveau 3, fiches produit longue traîne) recevaient moitié moins de passages. Certaines fiches n'avaient pas été recrawlées depuis 6 semaines.\n\nL'outil d'inspection d'URL dans Search Console confirmait le problème : pour une même fiche produit, Google avait sélectionné la version `www` comme canonical sur 2 300 URLs et la version nue sur 3 100. Les 6 600 restantes n'avaient pas encore été retraitées. L'autorité des backlinks — majoritairement pointés vers `www.example.fr` car c'était l'ancien domaine — ne se transférait pas vers `example.fr`. [Le même type de dilution d'autorité avait été documenté lors de migrations de structure URL](/blog/migration-magento-2-vers-shopify-structure-url-non-redirigee-autorite-dispersee).\n\n## Le fix : 301, canonical dur, et patience\n\nLe correctif a nécessité trois interventions simultanées, déployées le jeudi 8 juin à 9h.\n\n### Intervention 1 — Redirection 301 sur Nginx\n\nLe bloc serveur `www` a été remplacé par une redirection stricte :\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name www.example.fr;\n\n    ssl_certificate     /etc/ssl/certs/example.fr.pem;\n    ssl_certificate_key /etc/ssl/private/example.fr.key;\n\n    return 301 https://example.fr$request_uri;\n}\n\nserver {\n    listen 80;\n    server_name www.example.fr example.fr;\n\n    return 301 https://example.fr$request_uri;\n}\n```\n\nLa règle couvre HTTPS et HTTP. Tout appel à `www` renvoie un 301 permanent vers le domaine nu, en conservant le chemin et les query strings.\n\n### Intervention 2 — Canonical en dur\n\nLe middleware a été modifié pour forcer le domaine canonique via une variable d'environnement, indépendamment de la requête entrante :\n\n```javascript\n// middleware/seo.js\nconst CANONICAL_ORIGIN = process.env.CANONICAL_ORIGIN || 'https://example.fr';\n\napp.use((req, res, next) => {\n  const path = req.originalUrl.split('?')[0]; // strip query params\n  res.locals.canonicalUrl = `${CANONICAL_ORIGIN}${path}`;\n  next();\n});\n```\n\nPlus de dépendance à `req.get('host')`. Même si une requête arrive par un chemin inattendu — proxy interne, preview Cloudflare, outil de monitoring — la canonical pointe toujours vers `https://example.fr`.\n\n### Intervention 3 — Search Console et sitemap\n\nLe sitemap `sitemap.xml` pointait déjà vers `example.fr`, mais le lead SEO a soumis une demande de changement d'adresse dans Search Console (outil \"Changement d'adresse\" dans les paramètres de la propriété `www`). Il a également supprimé le sitemap de la propriété `www` et vérifié que la propriété `example.fr` était définie comme propriété principale du domaine.\n\nEnfin, un crawl Screaming Frog complet a été lancé sur `https://www.example.fr` pour valider que chaque URL renvoyait bien un 301 vers son équivalent sans `www` :\n\n```bash\n# Vérification rapide sur un échantillon de 50 URLs extraites du sitemap\ncat sitemap-urls.txt | head -50 | sed 's|https://example.fr|https://www.example.fr|' | \\\n  xargs -I {} curl -sI {} | grep -E \"^(HTTP|location)\" \n```\n\nRésultat attendu pour chaque ligne :\n\n```\nHTTP/2 301\nlocation: https://example.fr/produit/lampe-articulee-noir\n```\n\n### Temps de récupération\n\nLa récupération n'a pas été immédiate. [Comme lors d'autres migrations où les signaux d'autorité sont dispersés](/blog/migration-wordpress-vers-headless-strapi-4000-redirections-htaccess-oubliees), Google a mis du temps à consolider.\n\n- **Semaine 1 (8-15 juin)** : Googlebot a commencé à suivre les 301. Les logs montrent une chute brutale des fetches sur `www` — de 4 200/jour à 600/jour.\n- **Semaine 3 (22-29 juin)** : Le nombre d'URLs indexées en `www` dans Search Console est passé de 8 400 à 3 100. La propriété `example.fr` affiche 11 200 pages indexées.\n- **Semaine 6 (13-20 juillet)** : Plus que 180 URLs `www` dans l'index. Le trafic organique remonte à 340 000 sessions mensuelles.\n- **Semaine 10 (10-17 août)** : Retour à 375 000 sessions. Les positions moyennes sur les requêtes top 100 ont récupéré 92 % de leur niveau pré-migration. L'index `www` est vide.\n\nLe total de la perte estimée : environ 200 000 sessions organiques sur la période mars-juillet. Pour un site avec un panier moyen à 85 € et un taux de conversion organique de 2,1 %, le manque à gagner approche les 350 000 € de chiffre d'affaires.\n\n### Leçons opérationnelles\n\nL'équipe a ajouté trois gardes-fous dans son pipeline CI/CD :\n\n1. **Test de redirection post-deploy** : un script curl qui vérifie le status code de `www` + `http://` + domaine nu HTTP, sur 5 URLs échantillon. Bloque le déploiement si un seul retourne autre chose qu'un 301.\n2. **Crawl Screaming Frog hebdomadaire** sur les deux variantes (`www` et non-`www`), avec alerte si le nombre de 200 dépasse 0 sur la variante morte.\n3. **Canonical audit** : vérification automatisée que 100 % des balises canonical en production pointent vers le `CANONICAL_ORIGIN` défini en variable d'environnement.\n\nPour ceux qui veulent aller plus loin sur les tests pré-migration, [le guide sur le stress-test d'environnements staging](/blog/how-to-stress-test-a-staging-environment-to-surface-risks-pre-launch-ask-an-seo-via-sejournal-helenpollitt1) détaille une méthodologie applicable.\n\n## Ce qu'on en retient\n\nUne migration `www` vers no-`www` ressemble à une opération triviale. Deux lignes DNS. Un bloc Nginx. Et pourtant, sans 301 explicite et sans canonical en dur, Google traite les deux variantes comme deux sites distincts. Le crawl budget se divise. L'autorité se dilue. Et le trafic fond en silence pendant des semaines avant que quiconque ne regarde le bon graphique.\n\nLe signal d'alerte était là dès le jour 1 : un `curl -I` sur la variante `www` qui retourne 200 au lieu de 301. Un monitoring continu type Seogard détecte cette divergence en quelques minutes — pas 84 jours plus tard devant un dashboard GA4 en chute libre.\n\nLes 301 ne sont pas un détail d'implémentation. Ce sont le mécanisme de transfert d'autorité. Les oublier, c'est couper le fil entre l'ancien monde et le nouveau — et laisser Google décider seul lequel garder.\n```","https://seogard.io/blog/migration-www-vers-no-www-google-indexe-les-deux-versions-pendant-3-mois","Migration","2026-05-29T16:01:33.766Z","2026-05-29","Un e-commerce migre de www vers no-www. Sans 301 ni canonical stricts, Google indexe les deux versions pendant 3 mois. Récit, diagnostic, fix.","\u003Ch1>Migration www vers no-www : Google indexe les deux versions pendant 3 mois\u003C/h1>\n\u003Cp>Mardi 14 mars, 11h. L'équipe infra d'une plateforme e-commerce française — 12 000 pages produit, 380 000 visites organiques mensuelles — bascule le domaine principal de \u003Ccode>www.example.fr\u003C/code> vers \u003Ccode>example.fr\u003C/code>. Le CDN répond. Le certificat couvre les deux variantes. Le navigateur affiche la bonne URL. Tout le monde passe à autre chose. Douze semaines plus tard, le trafic organique a perdu 31 %. Personne n'a vu le problème arriver. Google, lui, l'a vu dès le premier jour.\u003C/p>\n\u003Ch2>Mercredi 7 juin — T+84 jours : le tableau de bord vire au rouge\u003C/h2>\n\u003Cp>C'est la responsable acquisition qui lève l'alerte. Le dashboard GA4 du mois de mai affiche 262 000 sessions organiques. En février, avant la migration, le compteur tournait à 381 000. L'écart se creuse depuis mars, mais l'équipe l'avait attribué à la saisonnalité printanière — le catalogue est orienté équipement d'intérieur, et le printemps ralentit toujours un peu.\u003C/p>\n\u003Cp>Sauf que cette fois, le ralentissement ne correspond pas à la courbe des années précédentes. Il est deux fois plus marqué.\u003C/p>\n\u003Cp>Le lead SEO ouvre Search Console à 14h20. Premier réflexe : vérifier les propriétés. Il en trouve deux. \u003Ccode>https://www.example.fr\u003C/code> et \u003Ccode>https://example.fr\u003C/code>. Les deux remontent des données. Les deux affichent des impressions. Et les deux montrent des pages indexées.\u003C/p>\n\u003Cp>Requête \u003Ccode>site:www.example.fr\u003C/code> dans Google : 8 400 résultats.\nRequête \u003Ccode>site:example.fr\u003C/code> : 9 100 résultats.\u003C/p>\n\u003Cp>Le catalogue complet compte 12 000 URLs. Google en a indexé 17 500 — dont environ 5 000 en double. Certaines fiches produit apparaissent deux fois dans l'index, une fois avec le préfixe \u003Ccode>www\u003C/code>, une fois sans.\u003C/p>\n\u003Cp>L'hypothèse initiale — un bug de sitemap — tombe vite. Le fichier \u003Ccode>sitemap.xml\u003C/code> pointe bien vers \u003Ccode>example.fr\u003C/code>. Pas de trace de \u003Ccode>www\u003C/code> dedans. Le problème est ailleurs.\u003C/p>\n\u003Cp>À 15h45, un développeur backend lance un \u003Ccode>curl -I\u003C/code> sur une URL produit en version \u003Ccode>www\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -sI\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://www.example.fr/produit/lampe-articulee-noir\"\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -iE\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"^(HTTP|location|x-redirect)\"\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Résultat :\u003C/p>\n\u003Cpre>\u003Ccode>HTTP/2 200\n\u003C/code>\u003C/pre>\n\u003Cp>Pas de redirection. Pas de 301. Le serveur répond 200 sur les deux variantes. Le CDN sert le contenu identique sur \u003Ccode>www.example.fr\u003C/code> et \u003Ccode>example.fr\u003C/code>, sans aucune distinction.\u003C/p>\n\u003Cp>Le lead SEO vérifie alors la balise canonical dans le HTML rendu. Il inspecte le source de la page \u003Ccode>www\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003Cspan style=\"color:#B392F0\"> rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"canonical\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://www.example.fr/produit/lampe-articulee-noir\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> />\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>La canonical est auto-référente — elle pointe vers elle-même, en version \u003Ccode>www\u003C/code>. Sur la version \u003Ccode>example.fr\u003C/code>, la canonical pointe bien vers \u003Ccode>example.fr\u003C/code>. Chaque version se déclare légitime. Google n'a aucun signal pour trancher.\u003C/p>\n\u003Cp>À 16h30, le diagnostic tombe : la migration DNS a bien eu lieu, mais aucune règle de redirection n'a été posée sur le serveur ou le CDN. Et le template HTML génère la canonical dynamiquement à partir de l'URL de la requête entrante, pas d'une variable d'environnement fixe.\u003C/p>\n\u003Cp>Ce n'est pas un bug mineur. C'est une duplication massive de l'index, active depuis 84 jours.\u003C/p>\n\u003Ch2>Le bug : un canonical dynamique et zéro 301\u003C/h2>\n\u003Cp>Pour comprendre comment 12 000 pages se retrouvent dupliquées dans l'index pendant trois mois sans que personne ne s'en aperçoive, il faut démonter trois couches : le DNS, le serveur HTTP, et le rendu HTML.\u003C/p>\n\u003Ch3>Couche 1 — Le DNS\u003C/h3>\n\u003Cp>Avant la migration, la configuration DNS ressemblait à ça :\u003C/p>\n\u003Cpre>\u003Ccode>www.example.fr.    A     203.0.113.42\nexample.fr.        CNAME www.example.fr.\n\u003C/code>\u003C/pre>\n\u003Cp>Le domaine nu (\u003Ccode>example.fr\u003C/code>) renvoyait vers \u003Ccode>www\u003C/code>. Après la migration, l'équipe infra a inversé :\u003C/p>\n\u003Cpre>\u003Ccode>example.fr.        A     203.0.113.42\nwww.example.fr.    CNAME example.fr.\n\u003C/code>\u003C/pre>\n\u003Cp>Le \u003Ccode>CNAME\u003C/code> de \u003Ccode>www\u003C/code> pointe désormais vers le domaine nu. Les deux résolvent vers la même IP. Les deux atteignent le même serveur. Les deux reçoivent le même contenu. Le DNS fait son travail — il résout. Mais il ne redirige pas. Ce n'est pas son rôle.\u003C/p>\n\u003Ch3>Couche 2 — Le serveur HTTP (Nginx derrière le CDN)\u003C/h3>\n\u003Cp>Le fichier de configuration Nginx contenait deux \u003Ccode>server\u003C/code> blocks. L'ancien, pour \u003Ccode>www\u003C/code>, et le nouveau, pour le domaine nu. Le plan initial prévoyait de transformer l'ancien block en redirection 301. Le ticket Jira existait. Il était même assigné. Mais il est resté en statut \"Ready for Dev\" pendant toute la durée de l'incident.\u003C/p>\n\u003Cp>Voici ce que le bloc \u003Ccode>www\u003C/code> aurait dû contenir :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\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 http2;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">www.example.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate \u003C/span>\u003Cspan style=\"color:#E1E4E8\">    /etc/ssl/certs/example.fr.pem;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate_key \u003C/span>\u003Cspan style=\"color:#E1E4E8\">/etc/ssl/private/example.fr.key;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 301\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> https://example.fr$request_uri;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Voici ce qu'il contenait réellement :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\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 http2;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">www.example.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate \u003C/span>\u003Cspan style=\"color:#E1E4E8\">    /etc/ssl/certs/example.fr.pem;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate_key \u003C/span>\u003Cspan style=\"color:#E1E4E8\">/etc/ssl/private/example.fr.key;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    root \u003C/span>\u003Cspan style=\"color:#E1E4E8\">/var/www/html;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    index \u003C/span>\u003Cspan style=\"color:#E1E4E8\">index.html;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#B392F0\"> / \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend:3000;\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 serveur \u003Ccode>www\u003C/code> servait le site normalement. Même backend, même contenu, même rendu. Deux portes d'entrée, un seul magasin.\u003C/p>\n\u003Ch3>Couche 3 — Le canonical dynamique\u003C/h3>\n\u003Cp>Le moteur de templates (un setup Node.js / Express avec un rendu SSR maison) générait la balise canonical à partir de l'objet \u003Ccode>req\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// middleware/seo.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">app.\u003C/span>\u003Cspan style=\"color:#B392F0\">use\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#FFAB70\">req\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">res\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  res.locals.canonicalUrl \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> `${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">protocol\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}://${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">get\u003C/span>\u003Cspan style=\"color:#9ECBFF\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'host'\u003C/span>\u003Cspan style=\"color:#9ECBFF\">)\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">originalUrl\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  next\u003C/span>\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>Et dans le template EJS :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003Cspan style=\"color:#B392F0\"> rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"canonical\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#FDAEB7;font-style:italic\">&#x3C;\u003C/span>\u003Cspan style=\"color:#9ECBFF\">%= canonicalUrl %>\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> />\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Quand Googlebot crawlait \u003Ccode>https://www.example.fr/produit/X\u003C/code>, la canonical renvoyait \u003Ccode>https://www.example.fr/produit/X\u003C/code>. Quand il crawlait \u003Ccode>https://example.fr/produit/X\u003C/code>, elle renvoyait \u003Ccode>https://example.fr/produit/X\u003C/code>. Les deux pages se déclaraient canoniques. Google recevait deux signaux contradictoires et choisissait l'un ou l'autre au cas par cas — souvent en gardant les deux.\u003C/p>\n\u003Ch3>Pourquoi les tests n'ont rien vu\u003C/h3>\n\u003Cp>L'équipe QA testait en environnement staging, derrière un sous-domaine \u003Ccode>staging.example.fr\u003C/code>. La variable \u003Ccode>www\u003C/code> n'existait pas dans cet environnement. Les tests Lighthouse, les audits Screaming Frog, les snapshots Puppeteer : tout ciblait \u003Ccode>example.fr\u003C/code> directement. Personne n'a crawlé \u003Ccode>www.example.fr\u003C/code> après la mise en production pour vérifier qu'il renvoyait bien un 301.\u003C/p>\n\u003Cp>Screaming Frog aurait détecté le problème en 30 secondes. Il suffisait de lancer un crawl sur \u003Ccode>https://www.example.fr\u003C/code> et d'observer la colonne \"Status Code\". 200 partout au lieu de 301. Mais le crawl post-migration n'a jamais été exécuté sur la variante \u003Ccode>www\u003C/code>.\u003C/p>\n\u003Ch3>L'impact sur le crawl budget\u003C/h3>\n\u003Cp>Les logs serveur (analysés a posteriori avec GoAccess) montrent que Googlebot a crawlé 4 200 URLs en \u003Ccode>www\u003C/code> et 5 800 en \u003Ccode>example.fr\u003C/code> sur le mois de mai. Le budget total — environ 10 000 fetches par jour — se répartissait entre les deux variantes. Les pages profondes du catalogue (catégories de niveau 3, fiches produit longue traîne) recevaient moitié moins de passages. Certaines fiches n'avaient pas été recrawlées depuis 6 semaines.\u003C/p>\n\u003Cp>L'outil d'inspection d'URL dans Search Console confirmait le problème : pour une même fiche produit, Google avait sélectionné la version \u003Ccode>www\u003C/code> comme canonical sur 2 300 URLs et la version nue sur 3 100. Les 6 600 restantes n'avaient pas encore été retraitées. L'autorité des backlinks — majoritairement pointés vers \u003Ccode>www.example.fr\u003C/code> car c'était l'ancien domaine — ne se transférait pas vers \u003Ccode>example.fr\u003C/code>. \u003Ca href=\"/blog/migration-magento-2-vers-shopify-structure-url-non-redirigee-autorite-dispersee\">Le même type de dilution d'autorité avait été documenté lors de migrations de structure URL\u003C/a>.\u003C/p>\n\u003Ch2>Le fix : 301, canonical dur, et patience\u003C/h2>\n\u003Cp>Le correctif a nécessité trois interventions simultanées, déployées le jeudi 8 juin à 9h.\u003C/p>\n\u003Ch3>Intervention 1 — Redirection 301 sur Nginx\u003C/h3>\n\u003Cp>Le bloc serveur \u003Ccode>www\u003C/code> a été remplacé par une redirection stricte :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\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 http2;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">www.example.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate \u003C/span>\u003Cspan style=\"color:#E1E4E8\">    /etc/ssl/certs/example.fr.pem;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    ssl_certificate_key \u003C/span>\u003Cspan style=\"color:#E1E4E8\">/etc/ssl/private/example.fr.key;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 301\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> https://example.fr$request_uri;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">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\">80\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">www.example.fr example.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 301\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> https://example.fr$request_uri;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>La règle couvre HTTPS et HTTP. Tout appel à \u003Ccode>www\u003C/code> renvoie un 301 permanent vers le domaine nu, en conservant le chemin et les query strings.\u003C/p>\n\u003Ch3>Intervention 2 — Canonical en dur\u003C/h3>\n\u003Cp>Le middleware a été modifié pour forcer le domaine canonique via une variable d'environnement, indépendamment de la requête entrante :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// middleware/seo.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> CANONICAL_ORIGIN\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> process.env.\u003C/span>\u003Cspan style=\"color:#79B8FF\">CANONICAL_ORIGIN\u003C/span>\u003Cspan style=\"color:#F97583\"> ||\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'https://example.fr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">app.\u003C/span>\u003Cspan style=\"color:#B392F0\">use\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#FFAB70\">req\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">res\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> path\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> req.originalUrl.\u003C/span>\u003Cspan style=\"color:#B392F0\">split\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'?'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)[\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]; \u003C/span>\u003Cspan style=\"color:#6A737D\">// strip query params\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  res.locals.canonicalUrl \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> `${\u003C/span>\u003Cspan style=\"color:#79B8FF\">CANONICAL_ORIGIN\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">path\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  next\u003C/span>\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>Plus de dépendance à \u003Ccode>req.get('host')\u003C/code>. Même si une requête arrive par un chemin inattendu — proxy interne, preview Cloudflare, outil de monitoring — la canonical pointe toujours vers \u003Ccode>https://example.fr\u003C/code>.\u003C/p>\n\u003Ch3>Intervention 3 — Search Console et sitemap\u003C/h3>\n\u003Cp>Le sitemap \u003Ccode>sitemap.xml\u003C/code> pointait déjà vers \u003Ccode>example.fr\u003C/code>, mais le lead SEO a soumis une demande de changement d'adresse dans Search Console (outil \"Changement d'adresse\" dans les paramètres de la propriété \u003Ccode>www\u003C/code>). Il a également supprimé le sitemap de la propriété \u003Ccode>www\u003C/code> et vérifié que la propriété \u003Ccode>example.fr\u003C/code> était définie comme propriété principale du domaine.\u003C/p>\n\u003Cp>Enfin, un crawl Screaming Frog complet a été lancé sur \u003Ccode>https://www.example.fr\u003C/code> pour valider que chaque URL renvoyait bien un 301 vers son équivalent sans \u003Ccode>www\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérification rapide sur un échantillon de 50 URLs extraites du sitemap\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">cat\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> sitemap-urls.txt\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -50\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sed\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 's|https://example.fr|https://www.example.fr|'\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  xargs\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -I\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> {}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -sI\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> {}\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -E\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"^(HTTP|location)\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> \u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Résultat attendu pour chaque ligne :\u003C/p>\n\u003Cpre>\u003Ccode>HTTP/2 301\nlocation: https://example.fr/produit/lampe-articulee-noir\n\u003C/code>\u003C/pre>\n\u003Ch3>Temps de récupération\u003C/h3>\n\u003Cp>La récupération n'a pas été immédiate. \u003Ca href=\"/blog/migration-wordpress-vers-headless-strapi-4000-redirections-htaccess-oubliees\">Comme lors d'autres migrations où les signaux d'autorité sont dispersés\u003C/a>, Google a mis du temps à consolider.\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Semaine 1 (8-15 juin)\u003C/strong> : Googlebot a commencé à suivre les 301. Les logs montrent une chute brutale des fetches sur \u003Ccode>www\u003C/code> — de 4 200/jour à 600/jour.\u003C/li>\n\u003Cli>\u003Cstrong>Semaine 3 (22-29 juin)\u003C/strong> : Le nombre d'URLs indexées en \u003Ccode>www\u003C/code> dans Search Console est passé de 8 400 à 3 100. La propriété \u003Ccode>example.fr\u003C/code> affiche 11 200 pages indexées.\u003C/li>\n\u003Cli>\u003Cstrong>Semaine 6 (13-20 juillet)\u003C/strong> : Plus que 180 URLs \u003Ccode>www\u003C/code> dans l'index. Le trafic organique remonte à 340 000 sessions mensuelles.\u003C/li>\n\u003Cli>\u003Cstrong>Semaine 10 (10-17 août)\u003C/strong> : Retour à 375 000 sessions. Les positions moyennes sur les requêtes top 100 ont récupéré 92 % de leur niveau pré-migration. L'index \u003Ccode>www\u003C/code> est vide.\u003C/li>\n\u003C/ul>\n\u003Cp>Le total de la perte estimée : environ 200 000 sessions organiques sur la période mars-juillet. Pour un site avec un panier moyen à 85 € et un taux de conversion organique de 2,1 %, le manque à gagner approche les 350 000 € de chiffre d'affaires.\u003C/p>\n\u003Ch3>Leçons opérationnelles\u003C/h3>\n\u003Cp>L'équipe a ajouté trois gardes-fous dans son pipeline CI/CD :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Test de redirection post-deploy\u003C/strong> : un script curl qui vérifie le status code de \u003Ccode>www\u003C/code> + \u003Ccode>http://\u003C/code> + domaine nu HTTP, sur 5 URLs échantillon. Bloque le déploiement si un seul retourne autre chose qu'un 301.\u003C/li>\n\u003Cli>\u003Cstrong>Crawl Screaming Frog hebdomadaire\u003C/strong> sur les deux variantes (\u003Ccode>www\u003C/code> et non-\u003Ccode>www\u003C/code>), avec alerte si le nombre de 200 dépasse 0 sur la variante morte.\u003C/li>\n\u003Cli>\u003Cstrong>Canonical audit\u003C/strong> : vérification automatisée que 100 % des balises canonical en production pointent vers le \u003Ccode>CANONICAL_ORIGIN\u003C/code> défini en variable d'environnement.\u003C/li>\n\u003C/ol>\n\u003Cp>Pour ceux qui veulent aller plus loin sur les tests pré-migration, \u003Ca href=\"/blog/how-to-stress-test-a-staging-environment-to-surface-risks-pre-launch-ask-an-seo-via-sejournal-helenpollitt1\">le guide sur le stress-test d'environnements staging\u003C/a> détaille une méthodologie applicable.\u003C/p>\n\u003Ch2>Ce qu'on en retient\u003C/h2>\n\u003Cp>Une migration \u003Ccode>www\u003C/code> vers no-\u003Ccode>www\u003C/code> ressemble à une opération triviale. Deux lignes DNS. Un bloc Nginx. Et pourtant, sans 301 explicite et sans canonical en dur, Google traite les deux variantes comme deux sites distincts. Le crawl budget se divise. L'autorité se dilue. Et le trafic fond en silence pendant des semaines avant que quiconque ne regarde le bon graphique.\u003C/p>\n\u003Cp>Le signal d'alerte était là dès le jour 1 : un \u003Ccode>curl -I\u003C/code> sur la variante \u003Ccode>www\u003C/code> qui retourne 200 au lieu de 301. Un monitoring continu type Seogard détecte cette divergence en quelques minutes — pas 84 jours plus tard devant un dashboard GA4 en chute libre.\u003C/p>\n\u003Cp>Les 301 ne sont pas un détail d'implémentation. Ce sont le mécanisme de transfert d'autorité. Les oublier, c'est couper le fil entre l'ancien monde et le nouveau — et laisser Google décider seul lequel garder.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21],"www","canonical","crawl budget","duplicate content","Migration www vers no-www : 3 mois d'index dupliqué","Fri May 29 2026 16:01:33 GMT+0000 (Coordinated Universal Time)",[25,39,52],{"_id":26,"slug":27,"__v":6,"author":7,"canonical":28,"category":10,"createdAt":29,"date":30,"description":31,"image":15,"imageAlt":15,"readingTime":16,"tags":32,"title":37,"updatedAt":38},"6a1b09e9aa6b273b0c40f580","passage-http-vers-https-imparfait-mixed-content-sur-les-images-cdn","https://seogard.io/blog/passage-http-vers-https-imparfait-mixed-content-sur-les-images-cdn","2026-05-30T16:01:45.919Z","2026-05-30","Migration HTTPS réussie, mais les images CDN restent en HTTP. Récit d'un mixed content invisible qui a coûté 34% de clics en 5 semaines.",[33,34,35,36],"https","mixed content","cdn","security","Mixed content CDN : HTTPS cassé par des images HTTP","Sat May 30 2026 16:01:45 GMT+0000 (Coordinated Universal Time)",{"_id":40,"slug":41,"__v":6,"author":7,"canonical":42,"category":10,"createdAt":43,"date":12,"description":44,"image":15,"imageAlt":15,"readingTime":16,"tags":45,"title":50,"updatedAt":51},"6a192bc6aa6b273b0cb63757","migration-webflow-vers-framer-301-hub-and-spoke-perdues-pagerank-dilue","https://seogard.io/blog/migration-webflow-vers-framer-301-hub-and-spoke-perdues-pagerank-dilue","2026-05-29T06:01:42.654Z","Récit d'une migration Webflow vers Framer où 301 redirects hub-and-spoke disparaissent. Diagnostic, impact sur 90 jours, et fix complet.",[46,47,48,49],"webflow","framer","migration","redirects","Migration Webflow → Framer : 301 perdues, PageRank dilué","Fri May 29 2026 06:01:42 GMT+0000 (Coordinated Universal Time)",{"_id":53,"slug":54,"__v":6,"author":7,"canonical":55,"category":10,"createdAt":56,"date":57,"description":58,"image":15,"imageAlt":15,"readingTime":59,"tags":60,"title":64,"updatedAt":65},"6a17da49aa6b273b0c9fa147","migration-prestashop-vers-bigcommerce-canonicals-pointent-encore-vers-le-staging","https://seogard.io/blog/migration-prestashop-vers-bigcommerce-canonicals-pointent-encore-vers-le-staging","2026-05-28T06:01:45.067Z","2026-05-28","Migration PrestaShop vers BigCommerce : les canonical pointaient vers le staging. Récit du bug, diagnostic technique et fix complet.",11,[61,62,19,63],"prestashop","bigcommerce","staging","BigCommerce : canonicals staging en prod, −38% trafic","Thu May 28 2026 06:01:45 GMT+0000 (Coordinated Universal Time)"]