optimisation core web vitals 7 min

Load balancing et AMP : l'erreur de cache qui tue ton indexation mobile

Un load balancer mal configuré peut invalider le cache AMP de Google et dégrader ton indexation mobile. Voici comment aligner signature de cache et Core Web Vitals.

Par Julien Morel
Partager

On a débarqué sur un projet où les URLs AMP disparaissaient de la Search Console en boucle. Une semaine, 15 000 pages indexées. La suivante, 8 000. L’équipe infra nous disait que le site était stable, la page AMP servie, le cache CDN ok. On a ouvert les logs du load balancer, et le problème sautait aux yeux : deux réponses HTML différentes pour la même URL selon le backend qui traitait la requête. La cause racine n’était ni le framework, ni le validateur AMP. C’était l’absence de signature de cache cohérente entre les serveurs.

Quand tu sers de l’AMP derrière plusieurs serveurs et un load balancer, tu actives un mécanisme que Google ne décrit pas assez dans sa documentation : le Google AMP Cache compare les réponses signées, et toute divergence invalide la version stockée. L’intention derrière AMP est de garantir un temps de réponse sous la seconde sur mobile, avec un pré-rendu agressif. Pour cela, le cache stocke une version unique de ta page, signée cryptographiquement. Si deux serveurs back-end produisent un document légèrement différent — un hash d’intégrité qui change, un ordre d’attributs modifié, un timestamp dynamique dans le markup — le cache invalide. Ta page AMP n’est plus dans le cache Google. Elle retourne à un fetch direct, souvent plus lent. Et la Search Console la rétrograde.

Le Google AMP Cache ne tolère aucune variation

Le fonctionnement du cache AMP repose sur une signature de contenu. Googlebot-AMP télécharge la version AMP d’une URL, la nettoie, la valide, et la stocke sur un serveur de cache avec un hash complet. Quand un utilisateur mobile clique sur un résultat AMP, Google sert cette version pré-calculée. Le visiteur ne touche jamais ton serveur d’origine. C’est cette promesse de zéro latence qui justifie encore AMP dans des secteurs éditoriaux où le LCP doit rester sous 1,2 seconde.

Mais cette mécanique a un corollaire brutal : si le cache reçoit une réponse qui diffère du hash stocké lors d’une recrawl, il considère la page invalide. Pas de version “partiellement valide”. Pas de fallback. La page sort du cache. Les logs de ton serveur ne montreront ni 404 ni 500, car ce n’est pas une erreur côté serveur. C’est une erreur de signature qui se lit uniquement dans les rapports AMP de la Search Console ou en inspectant les en-têtes AMP-Cache-Transform.

On a vu cette situation sur un site éditorial avec des milliers d’articles AMP. Trois backends Node.js, un load balancer en round-robin sans persistance de session, chacun générant un hash d’intégrité de ressource avec son propre identifiant de build. Résultat : le Google AMP Cache voyait trois signatures pour la même URL, et invalidait la page en continu. La Search Console affichait une oscillation entre “Page AMP valide” et “Page avec redirection”. Le site perdait du crawl budget mobile chaque semaine, car Googlebot-AMP revisitait des URLs qu’il jugeait incohérentes.

Quand ton round-robin distribue la dette technique

Le round-robin est le mode par défaut de nombreux load balancers : il distribue chaque nouvelle requête au serveur suivant de la liste. Couplé à des backends qui ne partagent pas un état de cache commun, il devient un distributeur de dette technique. Chaque serveur va recalculer ses propres métadonnées, sa propre version du template, ses propres horodatages internes. Si tu génères un amp-custom avec des règles CSS compilées à la volée, le moindre écart d’ordre des classes modifie le hash final.

Sur un projet Next.js, nous avions une version AMP servie via useAmp(). Le build produisait un CSS critique optimisé, mais chaque instance de l’app conteneurisée insérait son propre identifiant de déploiement dans une balise meta. Deux instances, deux signatures. Le load balancer répartissait les requêtes de Googlebot comme les autres, et le moteur voyait deux versions de la même page. Ce comportement n’était pas documenté dans la configuration Next, et Lighthouse ne détecte pas ce type d’incohérence. Seule une inspection systématique des en-têtes de cache, couplée à un diff HTML, a permis de le révéler.

Ce type de bug est d’autant plus pernicieux qu’il ne touche pas tous les utilisateurs. Environ une requête sur deux recevait la bonne signature, l’autre non. Les tests manuels dans un navigateur étaient verts. Le monitoring synthétique, lui aussi, ne reproduisait pas la condition de charge. C’est l’archétype du problème d’infrastructure invisible qui ne se manifeste que dans les signaux de classement mobile.

⚠️ Attention : Si tu actives le cache de page entière sur chaque backend sans synchroniser la clé de purge, chaque serveur peut garder une version obsolète de la page AMP, ce qui aggrave encore la divergence de signature.

Le header Vary, clé de voute de l’indexation AMP

Beaucoup de développeurs traitent le header Vary comme un détail de configuration CDN. Avec AMP, c’est un couteau suisse à double tranchant. Un Vary: Accept mal positionné peut indiquer au Google AMP Cache de stocker une version différente selon l’en-tête de requête du bot, même si ta page AMP doit être unique. La documentation de Google précise que le cache AMP ignore certains en-têtes, mais l’interface de ton load balancer peut les relayer autrement.

Mesurer l’impact sur les Core Web Vitals avant de paniquer

Quand l’AMP est dans un état dégradé, tu ne perds pas seulement une ligne dans le carrousel mobile. Tu perds sur le terrain plus large des Core Web Vitals, notamment le LCP mobile. Si ta page AMP sort du cache Google, chaque visiteur mobile doit refaire le round-trip vers ton infrastructure, souvent derrière un load balancer non taillé pour le trafic AMP. Le TTFB grimpe, le LCP franchit le seuil de 2,5 secondes, et le INP peut déraper si le fallback JavaScript est plus lourd.

Prenons un cas concret. Une page AMP bien mise en cache chez Google est servie en moins de 40 ms de TTFB. La même page, renvoyée par ton serveur d’origine après un cache miss, affiche un TTFB de 300 à 800 ms selon la localisation du visiteur et la charge du load balancer. Cette différence dégrade directement le LCP, que Google mesure sur le terrain via les utilisateurs de Chrome. Ton rapport Core Web Vitals dans la Search Console va donc afficher des valeurs dégradées pour les URLs AMP, même si ton infrastructure tourne sans alerte de performance. Si tu agrèges ces métriques pour l’ensemble de ton site, tu peux passer d’un statut “Bien” à “Amélioration nécessaire” sans avoir changé une seule ligne de code. C’est ce qui est arrivé au site éditorial mentionné plus tôt : le LCP médian mobile a bondi de 1,3 s à 2,9 s en trois semaines, simplement à cause de l’invalide cache AMP.

Le lien avec l’optimisation des Core Web Vitals est direct : une signature de cache incohérente n’est pas qu’un problème AMP, c’est un problème de performance globale. Un load balancer qui introduit de la variabilité sur les pages accélérées augmente le bruit dans tes métriques terrain. Tu passes du temps à optimiser des images, à réduire ton bundle, alors que le levier le plus immédiat est au niveau de l’infrastructure de cache partagé.

Implémenter une signature de cache déterministe en 3 étapes

La solution passe par une signature invariante, quel que soit le backend qui sert la réponse. Trois actions suffisent pour cadrer le problème.

  1. Déconnecter l’identifiant de build du markup. Toute variable d’environnement, tout numéro de version, tout timestamp qui n’est pas strictement nécessaire au rendu AMP doit être retiré du HTML final. Si ton pipeline CI insère un meta build-id, associe-le à une variable de config qui ne s’écrit pas dans le DOM.

  2. Centraliser la clé de cache au niveau du load balancer ou du CDN. Le load balancer doit pouvoir signer la réponse avec une clé déterministe basée sur la combinaison URL + contenu hashé. Si tu déploies sur un CDN comme Fastly ou Cloudflare Workers, le calcul du hash peut être déporté dans une edge function. Aucun backend ne doit décider seul de la signature.

  3. Synchroniser les purges de cache entre backends. Lorsqu’un article est mis à jour, la purge doit être envoyée à tous les niveaux de cache : cache applicatif de chaque backend, cache du load balancer, cache CDN, et finalement purge via l’API Google AMP Cache. La moindre purge manquée crée une fenêtre de divergence.

Sur le projet Node.js évoqué plus tôt, la correction a tenu en un patch de 40 lignes : suppression des métadonnées non déterministes, basculement du calcul de signature dans une fonction lambda commune à tous les pods, et ajout d’un header Surrogate-Key cohérent. Le temps de rétablissement de l’indexation AMP a pris dix jours, le temps que Googlebot-AMP recrawl l’ensemble des URLs avec une signature stable.

Ce type de correction n’a rien de spécifique à AMP. Si vous gérez un état applicatif réparti entre plusieurs processus Node.js, la tentation d’utiliser un store local par instance peut réintroduire une divergence similaire sur d’autres types de pages. Quand nous avons étudié les pièges du state management avec Zustand, nous avions rappelé que l’état doit être un contrat, pas un détail d’instance. La même rigueur s’applique au markup AMP.

💡 Conseil : Valide l’identité du cache avec curl -sD - https://example.com/article.amp.html -H 'Accept: text/html' | grep -i 'x-cache\|surrogate-key' depuis plusieurs origines réseau pour vérifier que la signature ne bouge pas.

Pourquoi ton SSR headless n’est pas le coupable

L’erreur classique est d’accuser le framework front : React, Next.js, Nuxt. Mais le SSR headless n’est pas responsable de l’incohérence de cache. Il produit un markup qu’il transmet au load balancer. S’il ne contient pas de données d’instance, le document est stable. Le problème émerge quand chaque instance ajoute des informations volatiles après le rendu, comme un identifiant de connexion, un token CSRF, un nonce de sécurité. Ces données changent à chaque requête, et le load balancer les laisse passer sans les normaliser.

La parade consiste à placer ces données volatiles dans des headers HTTP ou dans un cookie sécurisé, jamais dans le balisage AMP. Le Google AMP Cache ignore les cookies et ne stocke pas les headers. Tu gardes la dynamique pour les requêtes non-AMP, et tu stabilises la version accélérée. Cette séparation est plus saine pour ton architecture, car elle évite de devoir maintenir des sessions sur chaque backend.

Quand on débug ce genre de problème, un IDE qui permet d’inspecter rapidement les headers, de lancer des requêtes curl et de comparer les diffs change la donne. Nous avons comparé l’utilisation de Claude Code et Cursor IDE pour ce type de sessions de diagnostic, et la capacité à itérer rapidement sur des scripts de vérification s’est révélée plus précieuse qu’un outillage plus lourd.

Questions fréquentes

Est-ce que le problème existe aussi avec un CDN comme Cloudflare en mode AMP Real URL ?

Oui, car le CDN agit comme un proxy de cache supplémentaire. Si votre origine derrière Cloudflare produit des réponses différentes, le CDN peut stocker et servir plusieurs versions. La certification AMP Real URL ne garantit pas la cohérence. Il faut s’assurer que la signature de cache est déjà stabilisée au niveau de l’origine.

Faut-il abandonner AMP si on a plusieurs backends et un load balancer ?

Non, mais il faut traiter l’infrastructure comme un seul hôte logique vis-à-vis du Google AMP Cache. Tant que tous les backends répondent à l’identique pour la même URL, le nombre de serveurs est transparent. L’abandon d’AMP doit se décider sur des critères stratégiques, pas à cause d’une incohérence de cache qui se corrige.

Comment vérifier que mes URLs AMP sont bien dans le cache Google ?

Utilisez l’outil AMP de la Search Console pour inspecter l’URL, et vérifiez via l’API AMP Cache en accédant à https://example-com.cdn.ampproject.org/c/s/example.com/page.amp.html. L’en-tête AMP-Cache-Transform et le code 200 confirment que la version servie est celle du cache. Si l’URL redirige ou retourne un 404, la signature est invalide.

Articles similaires

Julien Morel

Julien Morel

Ancien dev front React passé SEO technique après une migration e-commerce qui a fait perdre 60% du trafic organique à son employeur en une nuit (fichier robots.txt oublié en staging). Depuis, il écrit pour que ça n'arrive à personne d'autre et teste sur ses propres side-projects avant de publier quoi que ce soit.

Cet article est publie a titre informatif. Faites vos propres recherches avant toute decision.