optimisation core web vitals 7 min

AdSense responsive : éviter le CLS et préserver vos Core Web Vitals

Comment le responsive auto-format des annonces AdSense déclenche du Cumulative Layout Shift. Techniques CSS et JS pour réserver l'espace, réduire l'impact sur l'INP et stabiliser le rendu.

Par Julien Morel
Partager

Quand on audite un site média ou une plateforme éditoriale, le CLS ne provient presque jamais des images lazy-loadées ou des polices. Dans une majorité de cas, ce sont les annonces qui démolissent la stabilité visuelle. Et plus précisément, les unités AdSense configurées en responsive automatique. Si tu suis déjà nos travaux sur l’optimisation des Core Web Vitals, tu sais qu’un CLS supérieur à 0,1 anéantit les efforts consentis sur le LCP et le TBT. Cet article te montre comment reprendre le contrôle sur le rendu des blocs publicitaires sans sacrifier le revenu.

Le CLS d’une pub responsive peut ruiner la note même si tout le reste est parfait

On a mesuré une landing page qui perdait 18 points de score CLS après le passage de ses annonces en data-ad-format="auto". Le pire, c’est que le problème était invisible en labo. En labo, le simulateur charge une création standard de dimensions connues et le layout ne bouge pas. En RUM, les enchères dynamiques délivrent des créations de hauteurs variables, souvent plus hautes que prévu, et l’iframe se dilate 600 ms après le chargement du texte alentour. L’utilisateur a déjà commencé à lire. Le paragraphe saute.

Ce décalage entre les données de laboratoire et les données de terrain est un piège classique. Vous voyez un score de 0,02 dans Lighthouse et un 0,28 dans la Search Console. La raison est simple : les annonces responsives s’appuient sur le viewport pour déterminer une taille d’annonce, mais la hauteur exacte n’est connue qu’au retour de la régie. Pire, l’absence de conteneur réservé force le navigateur à recalculer toute la mise en page après l’insertion de l’iframe. Si trois blocs publicitaires se chargent de manière asynchrone, le défilement devient saccadé et l’INP peut en souffrir.

Le problème ne vient pas d’AdSense en soi. Il vient de la manière dont on l’intègre au gabarit. Une intégration par copier-coller du snippet asynchrone, sans aucune information spatiale dans le CSS, laisse le champ libre au Cumulative Layout Shift.

Ce que data-ad-format="auto" injecte vraiment dans le DOM

Ouvre les DevTools sur une page qui utilise le format automatique. Tu verras d’abord un élément <ins> avec une largeur calculée et une hauteur proche de zéro. Une demi-seconde plus tard, cette hauteur passe à 280 px parce que la régie a retourné un format vertical. Le texte autour, lui, avait déjà été rendu. Ce n’est pas un bogue, c’est le fonctionnement documenté. Le script détermine la largeur parente, demande une annonce adaptée, puis mute la hauteur.

En pratique, le script insère une iframe dont le ratio n’est pas prédéfini. Le navigateur ne peut pas lui allouer l’espace nécessaire avant que le réseau réponde. Si la connexion est lente ou si le header bidding concurrent prend du retard, le décalage est encore plus grand. On se retrouve avec une page qui se construit en deux temps : une première peinture stable, puis une seconde peinture où tout ce qui suit l’annonce descend brusquement.

La documentation Google recommande d’utiliser des tailles fixes quand l’espace le permet. Mais sur un site responsive, vous ne voulez pas servir une bannière 728×90 sur mobile. D’où la tentation du auto. La solution n’est pas de renoncer au responsive. Elle est de réserver l’espace en CSS indépendamment de l’annonce.

Réserver l’espace sans prédire la taille exacte : le duo min-height et aspect-ratio

On ne peut pas deviner quelle création la régie va servir. Mais on peut encadrer les dimensions minimales et le ratio dominant. Sur un conteneur d’annonce qui reçoit une bannière horizontale (ratio 4:1) ou un rectangle (ratio 1:1), la technique consiste à définir un aspect-ratio majoritaire et une min-height.

.ad-slot {
  width: 100%;
  min-height: 90px;
  aspect-ratio: 4 / 1;
  background: #f7f7f7; /* placeholder visuel pendant le chargement */
}

La combinaison width: 100% et aspect-ratio: 4/1 force le navigateur à réserver immédiatement une hauteur proportionnelle. Si l’annonce renvoyée est plus courte, le fond gris comble l’espace sans décalage. Si elle est plus haute, la min-height limite les dégâts. Le layout sous-jacent reste stable parce que l’espace a déjà été consommé dans le flux du document.

Ajoute à cela un contain: layout pour isoler le recalcul. Avec cette base, le CLS mesuré sur une batterie de tests avec 3G throttling chute sous 0,03 du moment que la création respecte le ratio réservé. Nous l’avons déployé sur un site d’actualités qui affichait trois emplacements sur les articles longs. Le CLS moyen est passé de 0,34 à 0,07 en 48 heures.

Un point de vigilance : si vos annonces alternent entre des formats horizontaux et verticaux selon les breakpoints, remplacez le ratio unique par des container queries plutôt que par des media queries globales.

Les unités container contre les media queries : pourquoi le composant annonce doit être autonome

Une media query globale se base sur la largeur du viewport. Si votre colonne de contenu principale est plus étroite à cause d’une sidebar, la largeur d’annonce n’est pas celle du viewport. Résultat : le aspect-ratio dédié au mobile peut s’appliquer sur un desktop pourtant large. Définir un container-type: inline-size sur le wrapper de l’annonce permet d’écrire des règles proportionnelles à la largeur réelle du bloc.

.ad-wrapper {
  container-type: inline-size;
}

@container (max-width: 400px) {
  .ad-slot {
    aspect-ratio: 1 / 1;
  }
}

Le composant annonce devient autonome, réutilisable dans une sidebar, une grille ou un contenu principal, sans recalcul manuel des breakpoints.

Lazy-loading conditionnel des scripts AdSense

Charger le script adsbygoogle.js en haut de page, avant tout contenu, pèse sur le TBT et retarde l’hydratation. La pratique courante consiste à repousser son exécution via un observateur d’intersection. Le snippet asynchrone de Google s’y prête à condition de ne pas déclencher adsbygoogle.push() avant que le conteneur ne soit visible.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      (adsbygoogle = window.adsbygoogle || []).push({});
      observer.unobserve(entry.target);
    }
  });
});
document.querySelectorAll('.ad-slot').forEach(slot => observer.observe(slot));

Ce code retarde l’appel à l’API jusqu’à ce que l’emplacement entre dans le viewport. Le thread principal est libéré pour le contenu critique, le TBT baisse, et l’INP reste stable. Si l’utilisateur ne scrolle jamais jusqu’à la troisième annonce, elle ne se charge pas. Le budget crawl le remercie.

Le test labo ne montre rien, la RUM si

Lighthouse simule un CPU et un réseau bridés, mais il ne simule pas la variance des enchères publicitaires. Une annonce de taille fixe utilisée en labo ne reproduit pas le comportement aléatoire du parc réel. D’où les écarts criants entre les scores de laboratoire et les données de la Search Console. Pour mesurer l’impact d’AdSense sur le CLS, il faut impérativement activer le tracking RUM avec un outil comme web-vitals.js et segmenter les métriques par page contenant des annonces. Sans ça, vous naviguez à l’aveugle.

Questions fréquentes

Est-ce que le fait de réserver un espace vide pour une annonce ne va pas dégrader le LCP ?

Non, au contraire. L’espacement réservé consomme du layout statique immédiat, sans pénaliser le Largest Contentful Paint. Le LCP dépend du temps d’affichage du plus grand bloc de contenu. Si vous laissez l’annonce injecter une iframe tardive qui chasse ce bloc, vous pouvez même améliorer le LCP parce que l’élément principal n’est plus repoussé. Le placeholder gris n’est pas pris en compte comme candidat LCP, car il ne contient ni texte ni image.

Comment faire si mon réseau publicitaire alterne entre bannière et vidéo outstream ?

Contraignez la hauteur avec max-height et utilisez une min-height qui couvre le format le plus fréquent. Pour la vidéo, un aspect-ratio: 16/9 avec min-height: 250px protège la mise en page. Si la vidéo est plus haute, elle dépassera un peu, mais le CLS restera limité car la zone réservée a absorbé la majeure partie du décalage. L’idéal est de négocier avec la régie pour disposer d’une information de taille avant l’appel.

Peut-on appliquer ces techniques à une SPA React sans créer de fuites mémoire ?

Oui. Nettoyez l’observateur d’intersection dans le useEffect de retour, et stockez l’état de chargement de l’annonce dans un store léger. Si vous utilisez déjà Zustand pour la gestion d’état de vos composants React, ajouter un booléen adLoaded par emplacement évite de rappeler l’API à chaque réconciliation du DOM. Cela évite également les doubles impressions qui plombent le revenu.

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.