A/B test header : la variante B sert un noindex à 50% du trafic pendant 9 jours
Mercredi 14h. L'équipe Growth d'une marketplace mode française — 12 000 pages actives, 1,2 million de sessions organiques par mois — lance un A/B test sur le header global. Objectif : mesurer l'impact d'un nouveau sticky nav sur le taux de scroll. Le test est routé côté client via un snippet maison. Dans le navigateur, les deux variantes s'affichent. Personne ne regarde le HTML brut. Neuf jours plus tard, 387 pages catégories ont disparu de l'index Google.
Jeudi J+1 — "C'est saisonnier"
Le lendemain du lancement, personne ne surveille rien côté SEO. L'A/B test est une initiative Produit. L'équipe SEO n'a pas été prévenue — le test touche le header, pas le contenu, donc "pas de risque SEO". Le mot exact dans le ticket Jira : "header-only, no SEO impact".
Vendredi, un analyste Data remarque une légère baisse de clics dans le rapport Search Console. −8% sur les pages catégories. Le Lead SEO regarde, hausse les épaules. On est vendredi, les données GSC ont 48h de latence. Probablement du bruit.
Lundi J+5. Le rapport hebdomadaire tombe. Les clics organiques sur les pages catégories sont à −23%. Le Lead SEO ouvre Search Console, filtre par type de page. Les impressions chutent plus vite que les clics — signe classique d'une perte de positions, pas d'un changement de comportement utilisateur.
Premier réflexe : vérifier si une core update est en cours. Rien sur le Search Status Dashboard. Second réflexe : ouvrir Screaming Frog sur un crawl de 500 pages catégories. Tout remonte en 200, les canonicals sont propres, les titles sont là.
Le Lead SEO poste dans Slack : "Quelqu'un a touché aux catégories ce week-end ?". Silence. Personne n'a fait de deploy. Le seul changement en prod est le snippet d'A/B test, lancé mercredi dernier.
Mardi J+6, 9h12. Le Lead SEO relance un crawl Screaming Frog, cette fois en simulant un user-agent Googlebot. Les résultats sont identiques. Pas de noindex visible. Il commence à douter de ses propres données.
Mardi J+6, 14h30. Un dev frontend, en debug sur un tout autre sujet, ouvre Chrome DevTools sur une page catégorie. Il rafraîchit trois fois. À la troisième, il voit un <meta name="robots" content="noindex"> dans le <head>. Il rafraîchit encore. Disparu. Encore. Présent. Il envoie un screenshot dans le canal #seo.
Le Lead SEO répond en 90 secondes : "C'est quoi ce meta ?"
C'est le début de la vraie investigation.
Le bug : un meta robots conditionnel injecté par le snippet d'expérimentation
L'A/B test fonctionne ainsi. Un snippet JavaScript injecté dans le <head> global attribue chaque visiteur à une variante — A ou B — via un cookie. Le snippet a été écrit par un dev Growth junior, en s'inspirant d'un template interne utilisé pour des tests de landing pages.
Le template interne contenait une directive meta robots. Pourquoi ? Parce que les landing pages de test sont souvent en noindex pour éviter la cannibalisation. Le dev a copié le template, adapté le code du header, mais n'a pas retiré le bloc conditionnel qui injectait le meta robots.
Voici le snippet tel qu'il était en production :
<script>
(function() {
var variant = document.cookie.match(/ab_header=([AB])/);
if (!variant) {
variant = Math.random() < 0.5 ? 'A' : 'B';
document.cookie = 'ab_header=' + variant + '; path=/; max-age=2592000';
} else {
variant = variant[1];
}
if (variant === 'B') {
// Charge le nouveau header
document.documentElement.classList.add('header-v2');
// Ligne héritée du template landing page
var meta = document.createElement('meta');
meta.name = 'robots';
meta.content = 'noindex';
document.head.appendChild(meta);
}
})();
</script>
La ligne fatale : meta.content = 'noindex'. Injectée dynamiquement dans le DOM pour 50% des visiteurs.
Ce que voit le développeur vs. ce que voit Googlebot
C'est là que la situation devient perverse.
Le développeur ouvre la page dans Chrome. Il est en variante A (cookie déjà posé lors d'une session précédente). Il ne voit pas le meta noindex. Il vide son cache, rafraîchit — le Math.random() le remet en variante A. Tout semble normal. Il devrait tomber en B une fois sur deux en théorie, mais en pratique, les sessions de debug sont trop courtes pour que le biais soit visible.
Screaming Frog crawle sans exécuter le JavaScript par défaut. Le HTML statique ne contient pas le meta noindex — il est injecté dynamiquement. Même en activant le rendering JS dans Screaming Frog, le crawler ne porte pas le cookie ab_header. À chaque requête, le script fait un Math.random(). Sur un crawl de 500 pages, environ 250 pages remontent avec le noindex, 250 sans. Mais l'équipe n'a pas vérifié les directives meta dans les résultats du crawl — elle cherchait des erreurs de status code.
Googlebot utilise un renderer basé sur une version récente de Chrome. Il exécute le JavaScript. Il ne porte pas de cookies entre les requêtes. À chaque crawl d'une page, le script roule un Math.random(). Sur 9 jours, chaque page catégorie a été crawlée entre 3 et 12 fois. Statistiquement, la plupart des pages ont été vues au moins une fois en variante B. Et Google respecte le signal le plus restrictif : si une page est vue en noindex lors d'un crawl, et en index lors d'un autre, Google tend à retenir le noindex.
C'est le point crucial. Le signal noindex n'a pas besoin d'être permanent pour faire des dégâts. Il suffit qu'il soit vu une fois par Googlebot pour déclencher un processus de désindexation.
Preuve dans les logs serveur
Le Lead SEO demande au devops d'extraire les logs d'accès Nginx filtrés sur le user-agent Googlebot pour les pages catégories.
grep "Googlebot" /var/log/nginx/access.log \
| grep "/categorie/" \
| awk '{print $1, $4, $7, $9}' \
| tail -200
Les logs montrent que Googlebot crawle normalement — codes 200 partout. Rien d'anormal côté serveur. Le problème est invisible dans les logs HTTP parce que le noindex est injecté côté client, après le rendu.
L'équipe passe alors aux logs de rendu. Ils n'en ont pas. Pas de log du HTML post-JavaScript. C'est le point aveugle.
Le Lead SEO utilise alors l'outil d'inspection d'URL de Search Console. Il inspecte une page catégorie qui a perdu ses impressions. Résultat :
URL is not on Google: 'noindex' detected
Indexing allowed? No
User-declared canonical: https://example.com/categorie/robes-ete
Google-selected canonical: https://example.com/categorie/robes-ete
Page fetch: Successful
Le rendu montre le HTML tel que Googlebot le voit. Et dans le <head>, le meta noindex est présent. La preuve est là.
L'équipe inspecte 15 pages catégories dans Search Console. 9 d'entre elles affichent le noindex. Les 6 autres non — le Math.random() a joué en leur faveur lors du dernier crawl.
Pourquoi les tests n'ont rien détecté
Trois raisons.
1. Pas de review SEO sur les snippets d'expérimentation. Le workflow A/B test passe par le Product Manager, le dev Growth et le QA fonctionnel. Le QA vérifie que le header s'affiche correctement dans les deux variantes. Il ne regarde pas le <head>.
2. Screaming Frog sans filtre meta robots. L'équipe SEO lance des crawls réguliers, mais le dashboard ne remonte que les status codes, les titles dupliqués et les canonicals. Le filtre "Pages with noindex" n'est pas dans leur rapport standard.
3. Pas de monitoring du rendu JS. La stack de monitoring (Datadog, Pingdom) surveille les temps de réponse et les erreurs 5xx. Personne ne surveille ce que Googlebot voit réellement après exécution du JavaScript. Ce type de divergence entre le HTML statique et le DOM rendu est un angle mort classique — le même qui cause des régressions silencieuses lors de migrations de framework SSR ou de refontes de composants header.
Le fix : suppression, purge, et attente
Patch immédiat
Mardi J+6, 16h45. Le dev Growth supprime les trois lignes fautives du snippet :
// SUPPRIMÉ — injectait un noindex sur la variante B
// var meta = document.createElement('meta');
// meta.name = 'robots';
// meta.content = 'noindex';
// document.head.appendChild(meta);
Le snippet corrigé :
<script>
(function() {
var variant = document.cookie.match(/ab_header=([AB])/);
if (!variant) {
variant = Math.random() < 0.5 ? 'A' : 'B';
document.cookie = 'ab_header=' + variant + '; path=/; max-age=2592000';
} else {
variant = variant[1];
}
if (variant === 'B') {
document.documentElement.classList.add('header-v2');
}
})();
</script>
Deploy en production à 17h02.
Invalidation des caches
Le site utilise Cloudflare comme CDN. Le Lead SEO demande une purge complète du cache pour s'assurer qu'aucune version cachée du snippet ne continue à servir le noindex.
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
-H "Authorization: Bearer CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
Forcer le re-crawl
Le Lead SEO soumet manuellement les 50 pages catégories les plus impactées via l'outil d'inspection d'URL de Search Console, en cliquant "Demander une indexation" pour chacune.
Pour les 337 pages restantes, il soumet le sitemap à nouveau dans Search Console et attend que Googlebot repasse naturellement.
Chronologie de récupération
- J+7 (mercredi) : les premières pages réapparaissent dans l'index. Celles soumises manuellement en priorité.
- J+10 (samedi) : 60% des pages catégories sont de retour. Les impressions commencent à remonter.
- J+15 : 92% des pages sont réindexées. Le trafic organique sur les catégories est à −11% par rapport à la baseline d'avant le test.
- J+21 : retour à la normale. Le trafic organique retrouve son niveau d'avant l'incident.
Au total, l'incident a coûté environ 180 000 clics organiques sur 21 jours, concentrés sur les pages catégories qui génèrent 40% du revenu organique de la marketplace.
Mesures de prévention déployées
1. Checklist SEO obligatoire pour tout snippet d'expérimentation. Avant mise en production, le snippet doit passer par un scan automatisé qui vérifie l'absence de directives noindex, nofollow, canonical ou redirect dans le code.
2. Règle ESLint custom. L'équipe ajoute une règle qui flag toute création dynamique de <meta name="robots"> dans le codebase :
// .eslintrc.js — règle custom
module.exports = {
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Literal[value="robots"]',
message: 'Injection de meta robots détectée. Vérification SEO requise avant merge.'
}
]
}
};
3. Crawl hebdomadaire avec rendu JS. Screaming Frog est configuré pour crawler avec JavaScript activé, et le rapport inclut désormais un filtre sur les directives meta robots. Toute page en noindex non attendue déclenche une alerte Slack.
4. Séparation des templates. Les templates d'expérimentation pour les landing pages (qui peuvent légitimement contenir un noindex) sont séparés des templates pour les tests sur les pages indexées. Plus de copier-coller entre les deux.
Ce type d'incident rappelle d'autres régressions silencieuses documentées, comme celles causées par des migrations vers des frameworks headless ou des changements de configuration SSR — à chaque fois, le problème est invisible dans le navigateur mais dévastateur pour les crawlers.
Ce qu'on en retient
Un A/B test qui ne touche "que le header" peut désindexer la moitié d'un site. Le meta robots est une arme nucléaire du SEO technique : trois lignes de JavaScript suffisent à sortir 387 pages de l'index en neuf jours.
Les tests d'expérimentation sont un angle mort du SEO parce qu'ils vivent dans un workflow parallèle — Produit, Growth, Data — qui ne croise jamais la checklist technique SEO. La solution n'est pas de bloquer les tests. C'est de monitorer en continu ce que Googlebot voit réellement après exécution du JavaScript.
Un outil de monitoring continu comme Seogard détecte ce type de divergence — un meta noindex qui apparaît dans le DOM rendu mais pas dans le HTML statique — en quelques minutes après le deploy, pas neuf jours plus tard dans un rapport hebdomadaire.
Trois lignes de code héritées d'un template. Neuf jours d'exposition. Vingt-et-un jours de récupération. 180 000 clics perdus. Le coût d'un copier-coller.