Un catalogue e-commerce de 22 000 fiches produit passe au lazy loading sur toutes ses images. Trois semaines plus tard, 40 % des images produit disparaissent de Google Images et le trafic organique chute de 18 %. Le coupable : un data-src sans fallback src, combiné à un IntersectionObserver que Googlebot ne déclenche jamais. Le lazy loading mal implémenté est l'un des saboteurs silencieux les plus fréquents en SEO technique.
L'attribut natif loading="lazy" : ce que Googlebot comprend réellement
Depuis 2019, l'attribut HTML natif loading="lazy" est supporté par tous les navigateurs majeurs. Google a explicitement confirmé dans sa documentation sur le lazy loading que Googlebot gère correctement cet attribut — à condition que l'URL de l'image soit présente dans l'attribut src.
C'est le point critique. L'attribut natif fonctionne avec le moteur de rendu de Chromium embarqué dans Googlebot. Le navigateur décide quand charger l'image en fonction de sa distance au viewport. Googlebot, lorsqu'il effectue son rendu, simule un viewport et charge les images dont le src est déclaré.
<!-- ✅ Compatible Googlebot : l'URL est dans src -->
<img
src="/images/produit-chaussure-running-42.webp"
alt="Chaussure running homme taille 42 - Modèle TrailX Pro"
width="800"
height="600"
loading="lazy"
/>
<!-- ❌ Invisible pour Googlebot : pas de src valide -->
<img
data-src="/images/produit-chaussure-running-42.webp"
alt="Chaussure running homme taille 42 - Modèle TrailX Pro"
width="800"
height="600"
class="lazyload"
/>
La différence est binaire. Dans le premier cas, Googlebot voit l'URL dans le DOM initial et la soumet à son pipeline d'indexation d'images. Dans le second, l'attribut data-src n'a aucune sémantique pour le crawler — c'est un attribut data-* arbitraire qu'aucun moteur de recherche n'interprète comme une source d'image.
Le piège du viewport simulé
Googlebot utilise un viewport mobile de 411×731 pixels (Nexus 5X). Les images situées très bas dans le DOM pourraient théoriquement ne pas être rendues si le navigateur headless ne scrolle pas suffisamment. En pratique, le Web Rendering Service (WRS) de Google effectue une forme de scroll, mais la documentation ne garantit pas qu'il atteigne le bas d'une page de 15 000 pixels de haut.
Pour les pages longues (catégories avec 100+ produits, articles à scroll infini), la recommandation est simple : les images critiques pour le SEO — celles que vous voulez indexer dans Google Images — ne doivent pas dépendre du lazy loading. Utilisez loading="eager" (valeur par défaut) pour les images above the fold et les premières images produit.
Impact sur le LCP
L'attribut loading="lazy" sur l'image LCP (Largest Contentful Paint) est une erreur classique qui dégrade les Core Web Vitals. Chrome 114+ ignore d'ailleurs loading="lazy" sur les images détectées comme LCP candidates dans certaines conditions, mais ce comportement n'est pas garanti côté Googlebot.
Règle : identifiez votre élément LCP avec Chrome DevTools (onglet Performance > cliquez sur le marqueur LCP) et assurez-vous qu'il porte loading="eager" ou aucun attribut loading. Pour un diagnostic approfondi du LCP, consultez notre guide sur le diagnostic et la correction d'un LCP lent.
Bibliothèques JavaScript de lazy loading : l'audit indispensable
Les bibliothèques comme lazysizes, lozad.js ou vanilla-lazyload suivent toutes le même pattern : elles remplacent src par data-src, insèrent un placeholder (pixel transparent, blur, couleur unie) et swappent l'attribut au scroll via IntersectionObserver.
Ce pattern pose deux problèmes SEO majeurs :
-
Le DOM initial ne contient pas l'URL de l'image. Googlebot indexe d'abord le HTML brut (first wave), puis effectue un rendu JavaScript (second wave). Entre ces deux étapes, il peut se passer des heures, voire des jours. Les images en
data-srcne seront découvertes qu'à la second wave — si le JS s'exécute correctement. -
L'IntersectionObserver dépend du scroll. Si le WRS de Google ne scrolle pas jusqu'à l'image, le swap
data-src→srcne se produit jamais. L'image reste un placeholder.
Migration d'une lib JS vers le natif
Voici un script de migration pour remplacer lazysizes par l'attribut natif, applicable sur un site existant sans refonte du template :
// migration-lazy-native.js
// Exécuter en build ou comme transformation post-rendu côté serveur
document.querySelectorAll('img.lazyload').forEach((img) => {
const realSrc = img.getAttribute('data-src');
const realSrcset = img.getAttribute('data-srcset');
if (realSrc) {
img.setAttribute('src', realSrc);
img.removeAttribute('data-src');
}
if (realSrcset) {
img.setAttribute('srcset', realSrcset);
img.removeAttribute('data-srcset');
}
// Ajouter le lazy loading natif
img.setAttribute('loading', 'lazy');
// Retirer la classe lazysizes
img.classList.remove('lazyload');
});
Ce script est une solution transitoire. L'approche pérenne consiste à modifier les templates (Twig, JSX, Blade, Handlebars) pour qu'ils génèrent directement le markup natif. Mais sur un site legacy avec des centaines de templates partiels, ce script en post-processing — intégré dans un pipeline SSR ou un plugin de build — peut sauver l'indexation en 48h.
Le cas des frameworks SPA
Les sites en React, Vue ou Angular qui utilisent un composant <LazyImage /> custom doivent vérifier ce que le SSR produit. Si vous utilisez Next.js, le composant next/image gère nativement loading="lazy" avec un src correct dans le HTML rendu côté serveur. Vérifiez avec curl :
# Vérifier le HTML brut reçu par Googlebot (avant exécution JS)
curl -s -A "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.175 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://votre-site.fr/produit/chaussure-running-42 \
| grep -i '<img'
Si la sortie contient data-src sans src (ou un src pointant vers un pixel transparent en base64), le SSR ne fait pas son travail. Il faut soit corriger le composant, soit implémenter un prerendering dédié aux bots.
Lazy loading des éléments non-image : iframes, vidéos et le scroll infini
Le lazy loading ne concerne pas que les images. Les iframes YouTube, les widgets tiers et surtout le contenu textuel chargé au scroll posent des problèmes d'indexation bien plus graves.
Iframes et vidéos
L'attribut loading="lazy" fonctionne sur les <iframe> dans les navigateurs modernes. Pour les embeds YouTube, c'est une optimisation de performance légitime qui n'a pas d'impact SEO négatif — Google indexe les vidéos YouTube via YouTube, pas via votre iframe.
<!-- Lazy loading natif sur une iframe YouTube -->
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
width="560"
height="315"
loading="lazy"
title="Démonstration produit TrailX Pro"
allow="accelerometer; autoplay; clipboard-write; encrypted-media"
allowfullscreen
></iframe>
Le piège du scroll infini et de la pagination AJAX
C'est le scénario le plus destructeur. Un site média charge ses articles de catégorie par blocs de 20 au scroll. Le HTML initial contient les 20 premiers articles. Les 180 suivants n'existent dans le DOM qu'après interaction utilisateur.
Googlebot ne clique pas sur "Charger plus". Googlebot ne scrolle pas indéfiniment. Résultat : 180 articles sur 200 n'ont aucun lien interne crawlable depuis cette page de catégorie. Leur PageRank interne s'effondre, leur fréquence de crawl baisse, leur indexation devient erratique.
La solution canonique est documentée par Google : implémenter un scroll infini avec pagination par URL. Chaque état du scroll correspond à une URL distincte (/categorie/tech?page=2, /categorie/tech?page=3) que Googlebot peut crawler via des liens <a href> présents dans le HTML initial.
<!-- Pagination crawlable en bas de page, même avec scroll infini côté JS -->
<nav aria-label="Pagination" class="pagination">
<a href="/categorie/tech?page=1">1</a>
<a href="/categorie/tech?page=2">2</a>
<a href="/categorie/tech?page=3">3</a>
<!-- ... -->
<a href="/categorie/tech?page=10">10</a>
</nav>
Le JavaScript intercepte les clics sur ces liens pour charger le contenu en AJAX et mettre à jour l'URL via history.pushState. Les utilisateurs bénéficient d'une expérience fluide. Googlebot suit les liens <a href> classiques et crawle chaque page de pagination.
Scénario concret : migration lazy loading sur un catalogue de 18 000 pages
Un e-commerce spécialisé en équipement outdoor (18 000 fiches produit, 450 pages catégorie, 1 200 pages de contenu éditorial) utilise lazysizes depuis 2021. Le site tourne sur un monolithe PHP avec Twig. Chaque fiche produit contient entre 4 et 12 images.
Diagnostic initial
L'équipe SEO détecte un problème via Google Search Console : le rapport "Pages" montre que le nombre de pages avec des images indexées dans Google Images a chuté de 14 200 à 8 600 sur six mois. Pas de migration, pas de refonte — la dégradation est progressive.
Un crawl Screaming Frog en mode "JavaScript rendering" (Configuration > Spider > Rendering > JavaScript) révèle que :
- 100 % des images utilisent le pattern
data-srcsanssrcvalide - Le placeholder est un SVG inline en base64 de 1×1 pixel
- L'IntersectionObserver de lazysizes fonctionne dans Screaming Frog (qui scrolle), mais le rendu est lent : 3,2 secondes en moyenne par page pour que toutes les images soient swappées
Le problème : le WRS de Google a un budget de rendu limité. Sur un catalogue de cette taille, les pages sont rendues avec un délai variable (parfois plusieurs jours) et un timeout qui ne permet pas toujours au JS de compléter le swap de toutes les images — surtout les 8e, 9e, 10e images d'une fiche produit.
Plan de correction
Semaine 1 : Modification du template Twig des fiches produit.
{# Avant : lazysizes #}
<img
data-src="{{ image.url }}"
data-srcset="{{ image.srcset }}"
class="lazyload"
alt="{{ image.alt }}"
width="{{ image.width }}"
height="{{ image.height }}"
/>
{# Après : lazy loading natif #}
<img
src="{{ image.url }}"
srcset="{{ image.srcset }}"
sizes="(max-width: 768px) 100vw, 50vw"
alt="{{ image.alt }}"
width="{{ image.width }}"
height="{{ image.height }}"
loading="{{ loop.index <= 2 ? 'eager' : 'lazy' }}"
decoding="async"
fetchpriority="{{ loop.index == 1 ? 'high' : 'auto' }}"
/>
Points clés de cette modification :
- Les deux premières images de chaque fiche chargent en
eageravecfetchpriority="high"pour la première (l'image héro, candidate LCP) decoding="async"évite de bloquer le thread principal pendant le décodage des images suivantessrcsetetsizessont dans le HTML initial, permettant au navigateur de choisir la bonne résolution
Semaine 2 : Suppression de lazysizes du bundle JS. L'équipe vérifie qu'aucun autre composant ne dépend de la bibliothèque (certains sliders l'utilisent parfois comme dépendance implicite). Le bundle passe de 187 KB à 164 KB après minification — un gain modeste mais réel qui améliore le Time to Interactive.
Semaine 3 : Monitoring. L'équipe suit trois métriques :
- Google Search Console > Performances > Type de recherche : Image : le nombre d'impressions dans Google Images
- Rapport de couverture : vérifier qu'aucune page ne bascule en erreur
- Core Web Vitals dans CrUX : le LCP des fiches produit, qui devrait s'améliorer puisque l'image héro n'est plus retardée par lazysizes
Résultats à 6 semaines
Les images indexées dans Google Images remontent de 8 600 à 15 800 — au-delà du niveau d'avant la dégradation, car certaines images n'avaient probablement jamais été correctement indexées. Le LCP P75 des fiches produit passe de 3,1s à 2,4s (données CrUX). Le trafic organique sur les fiches produit augmente de 12 %, en partie attribuable à la récupération du trafic Google Images.
Vérifier ce que Googlebot voit : les outils de diagnostic
Le debugging du lazy loading nécessite de voir la page comme Googlebot la voit — pas comme Chrome sur votre poste de dev.
Outil d'inspection d'URL (Search Console)
L'outil d'inspection d'URL de Google Search Console est la référence. Il effectue un rendu réel avec le WRS et montre le screenshot + le HTML rendu. Cherchez vos balises <img> dans l'HTML rendu : si le src contient encore un placeholder, le lazy loading bloque l'indexation.
Pour aller plus loin sur ce diagnostic, notre article sur comment tester ce que Google voit réellement sur votre site détaille la méthodologie complète.
Chrome DevTools : simuler Googlebot
Dans Chrome DevTools, l'onglet Network permet de throttler la connexion et de désactiver JavaScript pour voir le HTML brut. Pour simuler plus précisément le comportement de Googlebot :
- Ouvrez DevTools > Network Conditions
- Changez le User-Agent pour celui de Googlebot mobile
- Désactivez le cache
- Rechargez la page
- Inspectez le DOM dans l'onglet Elements : les
<img>ont-ils unsrcvalide ?
Pour une vérification à l'échelle, Screaming Frog en mode JavaScript rendering avec extraction custom est plus efficace :
Configuration > Custom > Extraction
- Regex : <img[^>]*data-src="([^"]+)"
- Scope : HTML brut (avant rendu JS)
Si cette extraction retourne des résultats sur vos pages, vous avez des images invisibles pour le premier pass de Googlebot.
Audit automatisé avec Lighthouse CI
Pour intégrer la vérification dans votre CI/CD :
# Installer Lighthouse CI
npm install -g @lhci/cli
# Lancer un audit avec focus sur les images
lhci autorun --collect.url="https://votre-site.fr/produit/chaussure-running-42" \
--assert.assertions.uses-lazy-loading=warn \
--assert.assertions.largest-contentful-paint=error
L'assertion uses-lazy-loading de Lighthouse vérifie que les images below the fold utilisent le lazy loading. Mais attention : Lighthouse ne vérifie pas si votre lazy loading est compatible Googlebot. C'est un audit de performance, pas de SEO. Combinez-le avec le crawl Screaming Frog pour une couverture complète.
Cas limites et trade-offs à connaître
Images CSS (background-image)
Le loading="lazy" ne s'applique qu'aux éléments <img> et <iframe>. Les images en background-image CSS ne sont pas lazy-loadées nativement et ne sont pas indexables par Google Images (sauf cas très rares via le rendu).
Si une image a une valeur SEO (photo produit, infographie, illustration d'article), elle doit être dans un <img> avec un alt descriptif. Pour une référence complète sur l'optimisation des images, consultez notre guide sur l'optimisation des images pour le SEO et la performance.
Contenu textuel en lazy loading
Certains sites lazy-loadent des blocs de texte entiers (FAQs, descriptions longues, avis clients) pour améliorer le temps de chargement perçu. C'est une mauvaise idée pour le SEO : le contenu textuel est léger (quelques KB) et représente le signal principal pour le ranking. Le gain de performance est négligeable, le risque de perte d'indexation est réel.
Le seul cas acceptable : un accordéon HTML natif avec <details> / <summary>. Google indexe le contenu à l'intérieur des <details> même s'il est visuellement masqué, car il est dans le DOM initial. C'est fondamentalement différent d'un contenu chargé en AJAX au click.
Le loading="lazy" et les navigateurs anciens
Sur les navigateurs qui ne supportent pas loading="lazy" (IE 11 est mort, mais certains WebViews Android anciens subsistent), l'attribut est simplement ignoré et l'image charge normalement — en eager. C'est un progressive enhancement parfait : aucun risque de régression.
C'est d'ailleurs l'argument principal contre les bibliothèques JS de lazy loading en 2026. Le support natif est à 96 %+ selon Can I Use. Les 4 % restants chargent les images normalement. Le trade-off performance vs complexité ne justifie plus une dépendance JavaScript.
Le CLS induit par le lazy loading
Une image lazy-loadée sans dimensions explicites provoque un décalage de layout (CLS) lorsqu'elle apparaît. Les attributs width et height dans le HTML permettent au navigateur de réserver l'espace avant le chargement. C'est non négociable.
<!-- Réservation d'espace via aspect-ratio implicite (width/height) -->
<img
src="/images/produit-sac-a-dos-alpine.webp"
alt="Sac à dos Alpine 40L - Coloris kaki"
width="800"
height="1067"
loading="lazy"
decoding="async"
/>
Si vos images sont responsive et que les dimensions HTML ne correspondent pas à la taille d'affichage, ajoutez un aspect-ratio en CSS :
img[loading="lazy"] {
aspect-ratio: attr(width) / attr(height);
width: 100%;
height: auto;
}
Cette règle CSS utilise les attributs HTML pour calculer le ratio et évite le CLS sans contraindre la taille d'affichage.
Monitoring continu : détecter les régressions avant Google
Le lazy loading est un domaine où les régressions sont silencieuses. Un développeur met à jour une dépendance npm, un composant Image change de comportement, et soudain 3 000 pages servent des images en data-src sans src. Vous ne le voyez pas dans vos tests manuels parce que le JS fonctionne dans votre navigateur. Googlebot, lui, ne voit rien.
La détection manuelle est impossible à l'échelle d'un catalogue de milliers de pages. Un monitoring automatisé qui crawle régulièrement le site en mode headless, compare le DOM rendu avec le DOM initial, et alerte quand des balises <img> perdent leur src est le seul filet de sécurité viable. Un outil comme SEOGard détecte ce type de régression automatiquement — la disparition d'un src sur une image produit déclenche une alerte avant que l'indexation ne soit impactée.
Complétez avec un check régulier dans Google Search Console : le rapport "Pages indexées" filtré par type d'image et le rapport Performances segmenté sur la recherche Image sont vos indicateurs avancés. Une baisse progressive des impressions Image sur des pages dont le contenu n'a pas changé est le signal d'alarme classique d'un lazy loading défaillant.
Le lazy loading natif avec loading="lazy" est la seule approche recommandable en 2026. Supprimez les bibliothèques JS, mettez l'URL dans src, réservez l'espace avec width/height, et gardez les images critiques en eager. Le reste, c'est du monitoring.