← Retour au blog
AngularPerformanceImagesLazy LoadingWeb Vitals

Optimiser les images : ng-srcset et lazy loading natif sans ralentir Angular

Les images sont le principal coupable des pages lentes. Elles représentent facilement 50 à 70 % du poids total d'une application web, et chaque octets non optimisé se traduit par une dégradation du Core Web Vitals, notamment LCP (Largest Contentful Paint) et CLS (Cumulative Layout Shift). Dans une application Angular, cette charge devient encore plus critique : le bundle initial doit être petit, le rendu doit être rapide, et chaque image doit être servie au bon format, à la bonne résolution, au bon moment. C'est précisément là qu'interviennent ng-srcset et le lazy loading natif du navigateur. Ensemble, ils forment une stratégie redoutable pour délivrer des images optimales sans surcharge JavaScript.

La problématique : pourquoi les images ralentissent Angular

Une image non optimisée crée plusieurs problèmes en cascade. D'abord, elle retarde le téléchargement initial, impactant directement le LCP, l'une des trois métriques Core Web Vitals. Ensuite, si la résolution n'est pas adaptée à l'écran de l'utilisateur, il télécharge une image bien trop grande pour son appareil : un utilisateur mobile reçoit la version desktop de 2 Mo, alors qu'une version mobile de 400 ko suffirait. Enfin, sans lazy loading, toutes les images sont chargées, même celles loin en bas de la page que l'utilisateur ne verra jamais. Dans Angular, ajouter une image via un simple <img [src]="monImage"> ne bénéficie d'aucune optimisation native : pas de format moderne (WebP), pas d'adaptation à l'écran, pas de chargement différé. C'est pourquoi la directive ng-srcset et l'attribut loading="lazy" natif du navigateur sont essentiels.

ng-srcset : adapter l'image à chaque écran

La directive ng-srcset d'Angular (disponible depuis Angular 14) encapsule la logique complexe de l'attribut HTML srcset et la rend déclarative et réactive. Au lieu de gérer manuellement des chaînes comme image-small.webp 480w, image-large.webp 1024w , vous déclarez les variantes d'images dans votre template et laissez Angular négocier avec le navigateur. Le navigateur choisit alors l'image la plus appropriée en fonction de la densité de pixels de l'écran (devicePixelRatio) et de la largeur disponible. Concrètement, si un utilisateur dispose d'une connexion 4G et d'un écran Retina (2x), le navigateur peut sélectionner une version haute résolution sans que vous ayez à écrire une ligne de JavaScript pour détecter l'appareil.

<img
  ngSrcset="
    images/hero-480.webp 480w,
    images/hero-1024.webp 1024w,
    images/hero-2048.webp 2048w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
  ngSrc="images/hero-2048.webp"
  alt="Hero banner"
  width="1920"
  height="1080"
/>

Dans cet exemple, l'attribut sizes indique au navigateur que sur mobile (≤600px), l'image occupe 100 % de la largeur du viewport, sur tablette 50 %, et sur desktop 33 %. Le navigateur utilise cette information pour calculer la largeur réelle de l'image et choisir la variante appropriée dans ngSrcset . La directive ngSrc agit comme fallback et doit toujours pointer vers la version haute résolution. Les attributs width et height sont obligatoires pour éviter le CLS : ils informent le navigateur de l'aspect ratio, permettant au moteur de rendu de réserver l'espace avant même que l'image soit chargée.

Lazy loading natif : charger uniquement ce qui est visible

Le lazy loading natif du navigateur, activé par l'attribut loading="lazy" , diffère fondamentalement du lazy loading JavaScript (Intersection Observer). Au lieu de charger une image quand elle entre dans la fenêtre d'affichage, le navigateur la charge un peu avant, selon sa stratégie interne. Cette approche réduit drastiquement le JavaScript côté application et délègue l'optimisation au moteur navigateur, qui dispose de bien plus d'informations (connexion réseau estimée, charge CPU, etc.). Pour les images éloignées du fold, le gain est spectaculaire : une page avec 50 images au-dessous de la ligne de flottaison peut économiser 10+ Mo de téléchargement initial.

<img
  ngSrcset="
    images/card-300.webp 300w,
    images/card-600.webp 600w
  "
  sizes="(max-width: 768px) 100vw, 50vw"
  ngSrc="images/card-600.webp"
  alt="Product card"
  width="600"
  height="400"
  loading="lazy"
/>

En production, combiner ngSrcset et loading="lazy" crée une synergie puissante. Le navigateur attend que l'image approche du viewport, puis charge uniquement la variante adaptée à l'appareil. Aucun JavaScript n'est nécessaire pour détecter la résolution, calculer la largeur, ou gérer les événements. La charge cognitive et computationnelle disparaît.

Formats modernes et optimisation d'ordre d'apparition

Servir du WebP ou AVIF (formats 25-35 % plus compacts que JPEG/PNG) est crucial, mais seulement si le navigateur les supporte. L'approche la plus robuste est d'utiliser l'élément <picture> avec plusieurs sources, mais cela explose rapidement en verbosité. Une alternative élégante : créer un pipe Angular qui génère automatiquement les variantes WebP et fallback JPEG à partir d'une URL de base. Certains services CDN (Cloudinary, Imgix) offrent une API pour cela : une URL unique génère toutes les variantes et formats selon les paramètres de requête.

<picture>
  <source
    ngSrcset="
      images/hero.webp?w=480 480w,
      images/hero.webp?w=1024 1024w
    "
    type="image/webp"
    sizes="(max-width: 600px) 100vw, 50vw"
  />
  <img
    ngSrcset="
      images/hero.jpg?w=480 480w,
      images/hero.jpg?w=1024 1024w"
    sizes="(max-width: 600px) 100vw, 50vw"
    ngSrc="images/hero.jpg?w=2048"
    alt="Hero"
    width="1920"
    height="1080"
    loading="lazy"
  />
</picture>

Pour les images au-dessus du fold (Hero, Header), ne jamais utiliser loading="lazy" . Utilisez fetchpriority="high" pour signaler au navigateur que cette image est critique. Inversement, pour les images sous le fold ou les icônes, loading="lazy" est la norme.

Le piège courant : oublier les dimensions et créer du CLS

Beaucoup de développeurs ajoutent ngSrcset et loading="lazy" mais omettent les attributs width et height . Résultat : le navigateur n'a aucune idée de l'espace à réserver pour l'image. Quand l'image arrive, le layout se réajuste brutalement, générant un CLS élevé et une expérience utilisateur médiocre. Le CLS impacte le classement Google et l'expérience perçue de l'application. De plus, sans dimensions, le navigateur ne peut pas calculer correctement le ratio pour choisir la variante dans srcset . C'est un piège insidieux parce que l'application fonctionne visuellement, mais les Core Web Vitals en pâtissent.

Autre piège : mélanger ngSrc avec srcset natif sans ngSrcset . Angular optimise ngSrc (ajout du timestamp cache-busting, validation), mais si vous écrivez srcset en dur, ces optimisations sont contournées. Toujours préférer ngSrcset quand c'est possible.

Intégration en production : CDN et cache headers

En production, les images doivent être servies via un CDN avec des headers de cache agressifs (Cache-Control: public, max-age=31536000). Les URLs des images doivent inclure un hash de contenu (content-hash) ou une version, garantissant que chaque modification génère une nouvelle URL et invalide le cache immédiatement. Si vous utilisez ng-srcset avec des URLs statiques, assurez-vous que votre pipeline de build injecte ce hash. Cloudinary, Imgix, ou même Nginx + ImageMagick peuvent gérer cela automatiquement.

Pour Angular, considérez la directive NgOptimizedImage (évolution de ngSrcset en Angular 15+), qui offre une validation stricte des attributs et des avertissements en développement si les bonnes pratiques ne sont pas respectées. Cette directive force l'inclusion de width , height , et alt , réduisant les erreurs.

Conclusion opérationnelle

Optimiser les images en Angular se résume à trois actions concrètes. Premièrement, remplacer tous les <img src> par <img ngSrc> ou <img ngSrcset> avec des dimensions explicites. Deuxièmement, déclarer sizes pour adapter l'image à chaque breakpoint, et utiliser des formats modernes (WebP) avec fallback. Troisièmement, ajouter loading="lazy" partout sauf pour les images critiques (au-dessus du fold), et utiliser fetchpriority="high" pour ces dernières. Le résultat : LCP réduit de 30-50 %, CLS proche de zéro, et économies de bande passante spectaculaires. C'est un investissement de quelques heures pour des gains durables sur les performances et le SEO.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️