Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
14 avril 2026

Lazy-load partout ? La fausse bonne idée qui flingue ton LCP

Le lazy-loading est utile, mais « partout » c’est souvent un anti-pattern. Voilà les erreurs qui reviennent en audit et comment arrêter de plomber LCP, CLS et INP.

9 min de lecture
48 vues
réactions
Partager :
Lazy-load partout ? La fausse bonne idée qui flingue ton LCP

Le lazy-loading, c’est le genre de “bonne pratique” qui devient vite un réflexe bête. Tu coches une option, tu ajoutes loading="lazy" partout, tu installes un plugin… et tu te dis que tu viens de gagner des points de perf. Sauf que sur le terrain, je vois l’inverse : des sites qui ont dégradé leur LCP, déclenché du CLS, et déplacé du boulot côté scroll au point d’abîmer l’INP.

Le problème n’est pas le lazy-loading. Le problème, c’est de l’appliquer sans hiérarchie. Sur une page, il y a des ressources prioritaires (celles qui font le rendu initial), et d’autres qui peuvent attendre. Mélanger les deux, c’est s’inventer des problèmes.

Lazy-loading “partout” : pourquoi ça finit souvent en régression Core Web Vitals

Le lazy-loading est là pour éviter de télécharger trop tôt ce que l’utilisateur ne verra peut-être jamais. Sur le papier, c’est parfait pour des images “below the fold”, des sections de footer, des carrousels en bas de page, des embeds, des commentaires, des listes longues.

Mais si tu lazy-load ce qui est au-dessus de la ligne de flottaison, tu demandes au navigateur d’attendre un signal (intersection, scroll, heuristique) pour charger… ce qui devait précisément arriver le plus tôt possible. Et si tu ajoutes une couche JavaScript qui observe, remplace, anime, blur-up, tu crées de la contention sur le main thread. Résultat typique : LCP retardé, layout qui bouge, et interactions qui laggent.

Erreur n°1 : lazy-loader l’image “hero” (celle qui fait ton LCP)

Le cas le plus classique : l’image de hero est la plus grosse surface visible au chargement. Donc très souvent, c’est elle qui devient l’élément LCP. Si tu lui colles du loading="lazy", ou si ton framework la charge via une logique “au moment où elle est visible”, tu retards le départ du téléchargement. Et si en plus tu as une police web, un script, ou un CSS critique un peu lourd, ton hero se retrouve à attendre derrière tout le monde.

Ce qui rend le bug vicieux, c’est qu’il ne saute pas toujours aux yeux. Toi, sur ta fibre, tu vois l’image arriver “vite”. Sur un mobile milieu de gamme, avec un réseau moyen, elle arrive plus tard, et ton LCP s’écroule. C’est là que tu te retrouves avec un score Lighthouse correct en local, mais des Core Web Vitals terrain (CrUX, RUM) médiocres.

Le fix rentable : hero en eager + priorité réseau explicite

Pour l’image LCP, je veux un comportement simple : on charge tout de suite, et on dit au navigateur que c’est prioritaire. En HTML natif, ça ressemble souvent à loading="eager" (ou l’absence de lazy) et fetchpriority="high". L’idée n’est pas “d’optimiser au millimètre”, c’est d’éviter une mise en attente inutile.

Si tu utilises un système d’images responsives (et tu devrais), assure-toi aussi que le srcset et le sizes sont corrects. Beaucoup de “mauvais LCP” sont juste des images servies trop grosses parce que sizes ment, ou parce que le CDN d’images est mal paramétré.

<img
  src="/images/hero-1280.webp"
  srcset="/images/hero-640.webp 640w, /images/hero-1280.webp 1280w, /images/hero-1920.webp 1920w"
  sizes="(max-width: 768px) 100vw, 1200px"
  width="1280"
  height="720"
  loading="eager"
  fetchpriority="high"
  decoding="async"
  alt="">

Oui, tu peux aussi précharger l’image LCP quand c’est pertinent. Mais si tu dois sortir l’artillerie du <link rel="preload"> pour compenser un lazy-load mal placé, c’est souvent que le problème de base est ailleurs. Commence par ne pas la lazy-loader.

Erreur n°2 : ne pas réserver l’espace des images (bonjour CLS)

L’autre grand classique, c’est le lazy-loading qui déclenche du CLS parce que l’espace n’est pas réservé. L’image n’est pas là au départ, donc le navigateur rend le layout “comme si”. Puis quand l’image arrive, tout pousse en dessous. Et tu te retrouves avec un CLS moche, parfois déclenché plusieurs fois sur une page avec des listes d’articles, des cards, ou des produits.

Le correctif est rarement sexy, mais il est ultra efficace : tu donnes des dimensions. Soit avec width et height (recommandé, car ça donne un ratio), soit avec un conteneur qui impose un aspect-ratio. Le lazy-loading n’est pas le coupable ici. Le coupable, c’est l’absence de réservation.

Attention aux placeholders “blur-up” et aux squelettes mal calibrés. C’est très facile de créer un squelette qui ne correspond pas exactement à la taille finale, surtout quand le rendu dépend de styles chargés tard. Tu gagnes une illusion de vitesse, mais tu perds en stabilité.

Erreur n°3 : “on charge au scroll” et on flingue l’INP sans s’en rendre compte

Le lazy-loading ne concerne pas que les images. Beaucoup de sites déclenchent au scroll des scripts, des widgets, des “reco”, des trackers, des animations, des composants de page. Et là, tu peux te fabriquer un problème plus moderne et plus pénible : l’utilisateur scrolle, le main thread se met à bosser (parse JS, exécution, rendu), puis il clique… et l’INP prend cher.

Le piège, c’est de croire que « c’est plus tard, donc c’est gratuit ». Non. C’est juste déplacé. Si ton “plus tard” arrive au moment où l’utilisateur interagit, tu viens de mettre ton travail au pire endroit possible. Le scroll est un moment fragile : si tu déclenches trop de choses, tu perds en fluidité, et tu crées de la latence d’entrée au clic.

Si tu as besoin d’un IntersectionObserver, garde-le sobre. Observe peu d’éléments, évite les callbacks qui font du lourd, coupe l’observer dès que c’est chargé, et donne-toi une marge avec rootMargin pour ne pas déclencher exactement au dernier moment. Et surtout, ne mélange pas “déclenchement au scroll” et “grosse initialisation JS synchrone”.

const el = document.querySelector('[data-widget="reviews"]');
if (el) {
  const io = new IntersectionObserver(async ([entry]) => {
    if (!entry.isIntersecting) return;
    io.disconnect();

    // Charge le code quand on approche, puis laisse le navigateur respirer.
    const mod = await import('/assets/reviews-widget.js');
    requestAnimationFrame(() => mod.mount(el));
  }, { rootMargin: '600px 0px' });

  io.observe(el);
}

Autre point terrain : certains scripts tiers “au scroll” se réveillent en chaîne (recalc style, layout, paints). Tu ne le verras pas en regardant juste le Network. Tu le verras dans un enregistrement Performance, avec des tâches longues et des frames qui drop.

Comment valider que tu as vraiment amélioré LCP/CLS/INP (sans te raconter d’histoires)

Pour éviter le mode « j’ai changé un attribut donc j’ai optimisé », je fais une validation simple, en trois angles. D’abord Lighthouse, pas pour la vérité absolue, mais pour détecter vite une régression évidente et vérifier que l’élément LCP n’est pas devenu un truc absurde (un texte, un bloc, une image différente).

Ensuite WebPageTest. C’est là que tu vois le filmstrip et que tu arrêtes de débattre. Si ton hero arrive plus tard qu’avant, tu le verras image par image, et tu verras aussi si tu as introduit des à-coups de layout. Je garde un œil sur le waterfall pour vérifier que le navigateur lance bien tôt la ressource LCP, et qu’elle n’est pas bloquée derrière des CSS/JS non critiques.

Enfin, un check dans Chrome DevTools Performance sur un profil mobile réaliste (CPU throttling, réseau). Si ton idée “on charge au scroll” crée des tâches longues pile au moment d’un clic, tu le verras. Et ça t’évitera de célébrer une optimisation qui a juste déplacé la douleur.

WordPress, Next.js, SPA : les endroits où je vois ces bugs en boucle

Sur WordPress, le combo le plus fréquent, c’est un plugin qui force le lazy-load sur toutes les images et un autre qui injecte des placeholders, parfois avec un script qui remplace des img par des picture ou l’inverse. Ça marche “en apparence”, mais tu te retrouves vite avec une image de hero lazy-loadée, sans dimensions stables, plus un slider qui fait bouger le layout. Si tu ne dois faire qu’un truc, c’est vérifier la première image significative et lui rendre sa priorité.

Sur Next.js (et d’autres frameworks), l’optimisation d’image est une bénédiction… tant que tu comprends ce que tu demandes. Si tu marques une image comme “prioritaire”, fais-le parce que c’est vraiment la LCP candidate, pas parce que tu veux tout booster. À l’inverse, éviter de mettre “prioritaire” sur le hero sous prétexte que “le lazy-load c’est bien”, c’est exactement le mauvais arbitrage.

Sur des SPA, je vois souvent du lazy-loading de composants déclenché par scroll, qui se combine avec des transitions et des re-renders coûteux. Là, ce n’est pas juste un sujet d’attribut HTML, c’est une stratégie de rendu. Parfois, le meilleur “lazy-loading”, c’est juste de réduire le coût de tes composants, de couper des effets inutiles, et de limiter ce que tu montes au-dessus du fold.

Mon avis (assumé) : le lazy-loading est un outil, pas une religion

Je n’ai rien contre le lazy-loading. J’adore le lazy-loading quand il est utilisé pour ce qu’il est : retarder ce qui n’a pas besoin d’être là tout de suite. Mais si tu l’appliques “par défaut” au contenu visible au chargement, tu te tires une balle dans le pied.

La perf web, c’est de la priorité. Ce qui rend vite et stable la première vue doit passer devant. Le reste peut attendre. Si tu gardes ça en tête, tu n’auras pas besoin de recettes magiques. Tu feras juste des choix cohérents, et tes Core Web Vitals arrêteront de faire le yoyo.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !