Migration www vers no-www : Google indexe les deux versions pendant 3 mois
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 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.
Mercredi 7 juin — T+84 jours : le tableau de bord vire au rouge
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.
Sauf que cette fois, le ralentissement ne correspond pas à la courbe des années précédentes. Il est deux fois plus marqué.
Le 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.
Requête site:www.example.fr dans Google : 8 400 résultats.
Requête site:example.fr : 9 100 résultats.
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 www, une fois sans.
L'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.
À 15h45, un développeur backend lance un curl -I sur une URL produit en version www :
curl -sI "https://www.example.fr/produit/lampe-articulee-noir" | grep -iE "^(HTTP|location|x-redirect)"
Résultat :
HTTP/2 200
Pas 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.
Le lead SEO vérifie alors la balise canonical dans le HTML rendu. Il inspecte le source de la page www :
<link rel="canonical" href="https://www.example.fr/produit/lampe-articulee-noir" />
La 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.
À 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.
Ce n'est pas un bug mineur. C'est une duplication massive de l'index, active depuis 84 jours.
Le bug : un canonical dynamique et zéro 301
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.
Couche 1 — Le DNS
Avant la migration, la configuration DNS ressemblait à ça :
www.example.fr. A 203.0.113.42
example.fr. CNAME www.example.fr.
Le domaine nu (example.fr) renvoyait vers www. Après la migration, l'équipe infra a inversé :
example.fr. A 203.0.113.42
www.example.fr. CNAME example.fr.
Le 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.
Couche 2 — Le serveur HTTP (Nginx derrière le CDN)
Le 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.
Voici ce que le bloc www aurait dû contenir :
server {
listen 443 ssl http2;
server_name www.example.fr;
ssl_certificate /etc/ssl/certs/example.fr.pem;
ssl_certificate_key /etc/ssl/private/example.fr.key;
return 301 https://example.fr$request_uri;
}
Voici ce qu'il contenait réellement :
server {
listen 443 ssl http2;
server_name www.example.fr;
ssl_certificate /etc/ssl/certs/example.fr.pem;
ssl_certificate_key /etc/ssl/private/example.fr.key;
root /var/www/html;
index index.html;
location / {
proxy_pass http://backend:3000;
}
}
Le serveur www servait le site normalement. Même backend, même contenu, même rendu. Deux portes d'entrée, un seul magasin.
Couche 3 — Le canonical dynamique
Le moteur de templates (un setup Node.js / Express avec un rendu SSR maison) générait la balise canonical à partir de l'objet req :
// middleware/seo.js
app.use((req, res, next) => {
res.locals.canonicalUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
next();
});
Et dans le template EJS :
<link rel="canonical" href="<%= canonicalUrl %>" />
Quand 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.
Pourquoi les tests n'ont rien vu
L'é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.
Screaming 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.
L'impact sur le crawl budget
Les 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.
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 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.
Le fix : 301, canonical dur, et patience
Le correctif a nécessité trois interventions simultanées, déployées le jeudi 8 juin à 9h.
Intervention 1 — Redirection 301 sur Nginx
Le bloc serveur www a été remplacé par une redirection stricte :
server {
listen 443 ssl http2;
server_name www.example.fr;
ssl_certificate /etc/ssl/certs/example.fr.pem;
ssl_certificate_key /etc/ssl/private/example.fr.key;
return 301 https://example.fr$request_uri;
}
server {
listen 80;
server_name www.example.fr example.fr;
return 301 https://example.fr$request_uri;
}
La 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.
Intervention 2 — Canonical en dur
Le middleware a été modifié pour forcer le domaine canonique via une variable d'environnement, indépendamment de la requête entrante :
// middleware/seo.js
const CANONICAL_ORIGIN = process.env.CANONICAL_ORIGIN || 'https://example.fr';
app.use((req, res, next) => {
const path = req.originalUrl.split('?')[0]; // strip query params
res.locals.canonicalUrl = `${CANONICAL_ORIGIN}${path}`;
next();
});
Plus 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.
Intervention 3 — Search Console et sitemap
Le 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.
Enfin, 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 :
# Vérification rapide sur un échantillon de 50 URLs extraites du sitemap
cat sitemap-urls.txt | head -50 | sed 's|https://example.fr|https://www.example.fr|' | \
xargs -I {} curl -sI {} | grep -E "^(HTTP|location)"
Résultat attendu pour chaque ligne :
HTTP/2 301
location: https://example.fr/produit/lampe-articulee-noir
Temps de récupération
La récupération n'a pas été immédiate. Comme lors d'autres migrations où les signaux d'autorité sont dispersés, Google a mis du temps à consolider.
- 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. - Semaine 3 (22-29 juin) : Le nombre d'URLs indexées en
wwwdans Search Console est passé de 8 400 à 3 100. La propriétéexample.fraffiche 11 200 pages indexées. - Semaine 6 (13-20 juillet) : Plus que 180 URLs
wwwdans l'index. Le trafic organique remonte à 340 000 sessions mensuelles. - 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
wwwest vide.
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.
Leçons opérationnelles
L'équipe a ajouté trois gardes-fous dans son pipeline CI/CD :
- 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. - Crawl Screaming Frog hebdomadaire sur les deux variantes (
wwwet non-www), avec alerte si le nombre de 200 dépasse 0 sur la variante morte. - Canonical audit : vérification automatisée que 100 % des balises canonical en production pointent vers le
CANONICAL_ORIGINdéfini en variable d'environnement.
Pour ceux qui veulent aller plus loin sur les tests pré-migration, le guide sur le stress-test d'environnements staging détaille une méthodologie applicable.
Ce qu'on en retient
Une 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.
Le 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.
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.