Créer un composant d'image efficace

Un composant d'image encapsule les bonnes pratiques liées aux performances et fournit une solution prête à l'emploi pour optimiser les images.

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

Les images sont une source courante de goulots d'étranglement qui affectent les performances des applications Web et constituent un point essentiel de l'optimisation. Les images non optimisées contribuent à leur surcharge et représentent actuellement plus de 70% de leur taille totale (en octets) au 90e centile. Plusieurs méthodes d'optimisation des images nécessitent un "composant image" intelligent avec des solutions de performances intégrées par défaut.

L'équipe Aurora a travaillé avec Next.js pour créer un de ces composants. L'objectif était de créer un modèle d'image optimisé que les développeurs Web pourraient personnaliser. Le composant constitue un bon modèle et définit une norme pour la création de composants d'image dans d'autres frameworks, systèmes de gestion de contenu (CMS) et piles technologiques. Nous avons collaboré sur un composant similaire pour Nuxt.js et nous travaillons avec Angular sur l'optimisation des images dans les prochaines versions. Cet article explique comment nous avons conçu le composant Image Next.js et les enseignements que nous en avons tirés.

Composant Image en tant qu'extension d'images

Opportunités et problèmes d'optimisation des images

Les images ont un impact non seulement sur les performances, mais aussi sur l'activité. Le nombre d'images par page constituait le deuxième meilleur indicateur de conversions des utilisateurs visitant des sites Web. Les sessions au cours desquelles les utilisateurs ont effectué une conversion comportaient 38% d'images en moins que les sessions au cours desquelles ils n'ont pas effectué de conversion. Lighthouse répertorie plusieurs opportunités d'optimisation des images et d'amélioration de Web Vitals dans le cadre de son audit des bonnes pratiques. Voici quelques-uns des domaines courants dans lesquels les images peuvent avoir une incidence sur les métriques Core Web Vitals et l'expérience utilisateur :

Les images non dimensionnées nuisent au CLS

Les images diffusées dont la taille n'est pas spécifiée peuvent provoquer une instabilité de la mise en page et contribuer à une hausse du Cumulative Layout Shift (CLS). Définir les attributs width et height sur les éléments img permet d'éviter les décalages de mise en page. Exemple :

<img src="flower.jpg" width="360" height="240">

La largeur et la hauteur doivent être définies de sorte que les proportions de l'image rendue soient proches de celles d'origine. Une différence de format importante peut entraîner la déformation de l'image. Une propriété relativement récente qui vous permet de spécifier aspect-ratio dans CSS peut aider à dimensionner les images de manière réactive tout en empêchant le CLS.

Les images volumineuses peuvent nuire au LCP

Plus la taille du fichier d'une image est grande, plus le téléchargement est long. Une grande image peut être l'image "héros" de la page ou l'élément le plus important de la fenêtre d'affichage qui est à l'origine du déclenchement du LCP (Largest Contentful Paint). Une image qui fait partie du contenu critique et dont le téléchargement prend beaucoup de temps retardera le LCP.

Dans de nombreux cas, les développeurs peuvent réduire la taille des images grâce à une meilleure compression et à l'utilisation d'images responsives. Les attributs srcset et sizes de l'élément <img> permettent de fournir des fichiers image de différentes tailles. Le navigateur peut alors choisir la bonne option en fonction de la taille et de la résolution de l'écran.

Une mauvaise compression d'image peut nuire au LCP.

Les formats d'image modernes tels que AVIF ou WebP peuvent offrir une meilleure compression que les formats JPEG et PNG couramment utilisés. Une meilleure compression réduit la taille du fichier de 25 à 50 % dans certains cas pour une qualité d'image identique. Cette réduction permet d'accélérer les téléchargements tout en consommant moins de données. L'appli doit diffuser des formats d'image modernes dans les navigateurs compatibles.

Le chargement d'images inutiles nuit au LCP

Les images en dessous de la ligne de flottaison dans la fenêtre d'affichage ne sont pas visibles par l'utilisateur lors du chargement de la page. Elles peuvent être différées pour ne pas contribuer au LCP et le retarder. Vous pouvez utiliser le chargement différé pour charger ces images ultérieurement, lorsque l'utilisateur fait défiler la page vers elles.

Problématiques liées à l'optimisation

Les équipes peuvent évaluer les coûts de performance en raison des problèmes listés précédemment et mettre en œuvre des solutions de bonnes pratiques pour les surmonter. Toutefois, cela n'arrive souvent pas dans la pratique et des images inefficaces continuent de ralentir le Web. Les raisons suivantes peuvent expliquer ces différences :

  • Priorités: les développeurs Web ont tendance à se concentrer sur le code, JavaScript et l'optimisation des données. Par conséquent, ils peuvent ne pas être au courant des problèmes liés aux images ni de la manière de les optimiser. Les images créées par des concepteurs ou téléchargées par des utilisateurs peuvent ne pas figurer en haut de la liste des priorités.
  • Solution prête à l'emploi: même si les développeurs sont conscients des nuances de l'optimisation des images, l'absence de solution tout-en-un prête à l'emploi pour leur framework ou leur pile technologique peut constituer un moyen de dissuasion.
  • Images dynamiques: en plus des images statiques intégrées à l'application, les images dynamiques sont importées par les utilisateurs ou issues de bases de données externes ou de CMS. Il peut être difficile de définir la taille de ces images lorsque leur source est dynamique.
  • Surcharge de balisage: les solutions permettant d'inclure la taille d'image ou srcset pour différentes tailles nécessitent un balisage supplémentaire pour chaque image, ce qui peut s'avérer fastidieux. L'attribut srcset a été lancé en 2014, mais n'est actuellement utilisé que par 26,5% des sites Web. Lorsqu'ils utilisent srcset, les développeurs doivent créer des images de différentes tailles. Des outils comme just-gimme-an-img peuvent être utiles, mais ils doivent être utilisés manuellement pour chaque image.
  • Navigateurs compatibles: les formats d'image modernes tels que AVIF et WebP créent des fichiers image plus petits, mais nécessitent un traitement spécial dans les navigateurs qui ne les acceptent pas. Les développeurs doivent appliquer des stratégies telles que la négociation de contenu ou l'élément <picture> pour que les images soient diffusées sur tous les navigateurs.
  • Complications du chargement différé: il existe plusieurs techniques et bibliothèques permettant d'implémenter le chargement différé pour les images en dessous de la ligne de flottaison. Choisir le meilleur n'est pas toujours facile. Les développeurs ne connaissent peut-être pas la meilleure distance entre le "pli" et le chargement des images différées. Les différentes tailles de fenêtre d'affichage sur les appareils peuvent compliquer ce processus.
  • Changer l'environnement: les navigateurs commencent à accepter de nouvelles fonctionnalités HTML ou CSS pour améliorer les performances. Il peut donc être difficile pour les développeurs d'évaluer chacune d'elles. Par exemple, Chrome lance la fonctionnalité Fetch Priority (Priorité de récupération) lors d'une phase d'évaluation. Vous pouvez l'utiliser pour améliorer la priorité de certaines images sur la page. Dans l'ensemble, les développeurs trouveraient plus simple si de telles améliorations étaient évaluées et mises en œuvre au niveau des composants.

Composant image en tant que solution

Les possibilités d'optimisation des images et les difficultés liées à leur implémentation individuelle pour chaque application nous ont poussés à concevoir un composant image. Un composant Image peut encapsuler et appliquer les bonnes pratiques. En remplaçant l'élément <img> par un composant d'image, les développeurs peuvent mieux résoudre les problèmes de performances des images.

Au cours de l'année écoulée, nous avons collaboré avec le framework Next.js pour concevoir et implement son composant Image. Il peut être utilisé pour remplacer les éléments <img> existants dans les applications Next.js, comme suit.

// Before with <img> element:
function Logo() {
  return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}

// After with image component:
import Image from 'next/image'

function Logo() {
  return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}

Ce composant tente de résoudre les problèmes liés aux images de manière générique grâce à un vaste ensemble de fonctionnalités et de principes. Il comprend également des options qui permettent aux développeurs de le personnaliser pour différentes exigences d'images.

Protection contre les décalages de mise en page

Comme indiqué précédemment, les images non dimensionnées entraînent des décalages de mise en page et contribuent au CLS. Lorsqu'ils utilisent le composant Image Next.js, les développeurs doivent fournir une taille d'image à l'aide des attributs width et height pour éviter tout décalage de mise en page. Si la taille est inconnue, les développeurs doivent spécifier layout=fill pour diffuser une image non dimensionnée qui se trouve dans un conteneur de taille. Vous pouvez également utiliser des importations d'images statiques pour récupérer la taille de l'image réelle sur le disque dur au moment de la compilation et l'inclure dans l'image.

// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />

// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />

// Image component with image import
import Image from 'next/image'
import logo from './logo.png'

function Logo() {
  return <Image src={logo} alt="logo" />
}

Étant donné que les développeurs ne peuvent pas utiliser le composant Image sans taille, la conception garantit qu'ils prendront le temps de prendre en compte le dimensionnement de l'image et d'éviter les décalages de mise en page.

Faciliter la réactivité

Pour que les images soient responsives sur tous les appareils, les développeurs doivent définir les attributs srcset et sizes dans l'élément <img>. Nous voulions réduire cet effort grâce au composant Image. Nous avons conçu le composant Image Next.js de sorte qu'il ne définisse les valeurs d'attribut qu'une seule fois par application. Nous les appliquons à toutes les instances du composant Image en fonction du mode de mise en page. Nous avons trouvé une solution en trois parties:

  1. Propriété deviceSizes: cette propriété peut être utilisée pour configurer des points d'arrêt ponctuellement en fonction des appareils communs à la base d'utilisateurs de l'application. Les valeurs par défaut des points d'arrêt sont incluses dans le fichier de configuration.
  2. Propriété imageSizes: il s'agit également d'une propriété configurable permettant d'obtenir les tailles d'image correspondant aux points d'arrêt associés à la taille de l'appareil.
  3. Attribut layout dans chaque image: indique comment utiliser les propriétés deviceSizes et imageSizes pour chaque image. Les valeurs acceptées pour le mode de mise en page sont fixed, fill, intrinsic et responsive.

Lorsqu'une image est demandée avec les modes de mise en page responsive ou fill, Next.js identifie l'image à diffuser en fonction de la taille de l'appareil qui demande la page, et définit les valeurs srcset et sizes de l'image de manière appropriée.

La comparaison suivante montre comment utiliser le mode de mise en page pour contrôler la taille de l'image sur différents écrans. Nous avons utilisé une image de démonstration partagée dans la documentation Next.js, affichée sur un téléphone et un ordinateur portable standard.

Écran d'ordinateur portable Écran de téléphone
Mise en page = intrinsèque: s'adapte à la largeur du conteneur dans les fenêtres d'affichage plus petites. N'augmente pas au-delà de la taille intrinsèque de l'image dans une fenêtre d'affichage plus grande. La largeur du conteneur est de 100 %.
Image des montagnes telle quelle Image de montagnes réduite
Mise en page = corrigée: l'image ne répond pas. La largeur et la hauteur sont fixes, tout comme l'élément , quel que soit l'appareil sur lequel il est affiché.
Image des montagnes telle quelle Image de montagnes affichée comme ne correspond pas à l&#39;écran
Mise en page = responsive: la mise en page s'adapte à la largeur du conteneur pour différentes fenêtres d'affichage, tout en conservant les proportions.
Image de montagnes agrandie pour s&#39;adapter à l&#39;écran Image de montagnes réduite pour s&#39;adapter à l&#39;écran
Mise en page = Remplissage: largeur et hauteur étirées pour remplir le conteneur parent. (Parent `
` est définie sur 300*500 dans cet exemple)
Image de montagnes rendue pour correspondre à la taille 300 x 500 Image de montagnes rendue pour correspondre à la taille 300 x 500
Images affichées pour différentes mises en page

Fournir un chargement différé intégré

Par défaut, le composant Image fournit une solution de chargement différé intégrée et performante. Lorsque vous utilisez l'élément <img>, il existe quelques options natives pour le chargement différé, mais elles présentent toutes des inconvénients qui les rendent difficiles à utiliser. Un développeur peut adopter l'une des approches de chargement différé suivantes:

  • Spécifiez l'attribut loading: facile à implémenter, mais non compatible avec certains navigateurs pour le moment.
  • Utilisez l'API Intersection Observer: la création d'une solution personnalisée de chargement différé demande des efforts, ainsi qu'une conception et une implémentation bien pensées. Les développeurs n'ont pas toujours le temps pour cela.
  • Importez une bibliothèque tierce pour charger des images en différé: des efforts supplémentaires peuvent être nécessaires pour évaluer et intégrer une bibliothèque tierce adaptée au chargement différé.

Dans le composant Image Next.js, le chargement est défini sur "lazy" par défaut. Pour mettre en œuvre le chargement différé, utilisez Intersection Observer, disponible dans la plupart des navigateurs récents. Les développeurs ne sont pas tenus d'effectuer quoi que ce soit de plus pour l'activer, mais ils peuvent la désactiver si nécessaire.

Précharger les images importantes

Très souvent, les éléments LCP sont des images, et les grandes images peuvent retarder le LCP. Il est conseillé de précharger les images critiques pour que le navigateur puisse détecter ces images plus tôt. Lorsque vous utilisez un élément <img>, vous pouvez inclure un indice de préchargement dans l'en-tête HTML comme suit.

<link rel="preload" as="image" href="important.png">

Un composant image bien conçu doit permettre d'ajuster la séquence de chargement des images, quel que soit le framework utilisé. Dans le cas du composant Next.js Image, les développeurs peuvent indiquer une image adaptée au préchargement à l'aide de l'attribut priority du composant "images".

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

L'ajout d'un attribut priority simplifie le balisage et est plus pratique à utiliser. Les développeurs de composants d'image peuvent également explorer les options permettant d'appliquer des méthodes heuristiques pour automatiser le préchargement des images au-dessus de la ligne de flottaison de la page qui répondent à des critères spécifiques.

Encourager l'hébergement d'images hautes performances

Les CDN d'images sont recommandés pour automatiser l'optimisation des images. Ils sont également compatibles avec les formats d'image modernes tels que WebP et AVIF. Le composant Image Next.js utilise par défaut une image CDN à l'aide d'une architecture de chargement. L'exemple suivant montre que le chargeur permet de configurer le CDN dans le fichier de configuration Next.js.

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

Avec cette configuration, les développeurs peuvent utiliser des URL relatives dans la source de l'image, et le framework concatène l'URL relative avec le chemin CDN pour générer l'URL absolue. Les CDN d'images populaires tels que Imgix, Cloudinary et Akamai sont compatibles. L'architecture prend en charge l'utilisation d'un fournisseur de services cloud personnalisé en implémentant une fonction loader personnalisée pour l'application.

Prendre en charge les images auto-hébergées

Dans certains cas, les sites Web ne peuvent pas utiliser de CDN d'images. Dans ce cas, un composant image doit être compatible avec les images auto-hébergées. Le composant Image Next.js utilise un optimiseur d'image en tant que serveur d'images intégré qui fournit une API de type CDN. L'optimiseur utilise Sharp pour transformer l'image de production si celui-ci est installé sur le serveur. Cette bibliothèque est idéale pour tous ceux qui souhaitent créer leur propre pipeline d'optimisation d'images.

Prise en charge du chargement progressif

La technique de chargement progressif vise à maintenir l'intérêt des utilisateurs en affichant une image fictive dont la qualité est généralement nettement inférieure lors du chargement de l'image réelle. Elle améliore les performances perçues ainsi que l'expérience utilisateur. Elle peut être utilisée en combinaison avec le chargement différé pour les images en dessous ou au-dessus de la ligne de flottaison.

Le composant Next.js Image permet le chargement progressif de l'image via la propriété placeholder. Cela peut être utilisé comme LQIP (espace réservé d'image de basse qualité) pour afficher une image de mauvaise qualité ou floutée pendant le chargement de l'image réelle.

Impact

En intégrant toutes les optimisations ci-dessus, nous avons obtenu de bons résultats avec le composant Image Next.js en production et nous travaillons également avec d'autres piles technologiques sur des composants d'image similaires.

Lors de la migration de son ancienne interface JavaScript vers Next.js, Leboncoin a également mis à niveau son pipeline d'images afin d'utiliser le composant Image Next.js. Sur une page qui est passée de <img> à l'image suivante, le LCP est passé de 2,4 s à 1,7 s. Le nombre total d'octets d'images téléchargés pour la page est passé de 663 Ko à 326 Ko (avec environ 100 Ko d'octets d'image à chargement différé).

Enseignements à tirer

Toute personne créant une application Next.js peut bénéficier de l'utilisation du composant Image Next.js pour l'optimiser. Toutefois, si vous souhaitez créer des abstractions de performances similaires pour un autre framework ou CMS, voici quelques enseignements que nous avons tirés en cours de route qui pourraient vous être utiles.

Les vannes de sécurité peuvent causer plus de mal que de bien

Dans une première version du composant Next.js Image, nous avons fourni un attribut unsized qui permettait aux développeurs de contourner les exigences de dimensionnement et d'utiliser des images dont les dimensions n'étaient pas spécifiées. Nous pensions que cela serait nécessaire dans les cas où il était impossible de connaître la hauteur ou la largeur de l'image à l'avance. Toutefois, nous avons remarqué que des utilisateurs recommandaient l'attribut unsized dans les problèmes GitHub comme solution générique aux problèmes de dimensionnement, même dans les cas où ils pouvaient résoudre le problème sans nuire au CLS. Nous avons par la suite abandonné et supprimé l'attribut unsized.

Séparer les frictions utiles des ennuis inutiles

L'exigence de dimensionnement d'une image est un exemple de "frottement utile". Elle limite l'utilisation du composant, mais offre en échange des avantages considérables en termes de performances. Les utilisateurs accepteront facilement la contrainte s'ils ont une idée précise des avantages potentiels en termes de performances. Par conséquent, il est utile d’expliquer ce compromis dans la documentation et d’autres documents publiés concernant le composant.

Il est toutefois possible de contourner ce problème sans compromettre les performances. Par exemple, lors du développement du composant Next.js Image, nous avons reçu des plaintes indiquant qu'il était ennuyeux de rechercher les tailles d'images stockées localement. Nous avons ajouté des importations d'images statiques, qui simplifient ce processus en récupérant automatiquement les dimensions des images locales au moment de la création à l'aide d'un plug-in Babel.

Trouver un équilibre entre fonctionnalités pratiques et optimisation des performances

Si votre composant "image" ne sert qu'à engendrer des frictions utiles pour les utilisateurs, les développeurs auront tendance à ne pas vouloir l'utiliser. Nous avons constaté que même si les caractéristiques de performances telles que le dimensionnement des images et la génération automatique de valeurs srcset étaient les plus importantes. Les fonctionnalités pratiques destinées aux développeurs, comme le chargement différé automatique et les espaces réservés flous intégrés, ont également suscité l'intérêt du composant Image de Next.js.

Définir une feuille de route des fonctionnalités pour favoriser l'adoption

Il est très difficile de trouver une solution qui fonctionne parfaitement dans toutes les situations. Il peut être tentant de concevoir quelque chose qui fonctionne bien pour 75% des personnes, puis de dire aux 25% restants que "dans ces cas, ce composant n'est pas pour vous".

En pratique, cette stratégie s’avère être en désaccord avec vos objectifs en tant que concepteur de composants. Vous souhaitez que les développeurs adoptent votre composant pour bénéficier de ses avantages en termes de performances. Cela est difficile à faire si certains utilisateurs ne peuvent pas migrer et se sentent exclus de la conversation. Ils sont susceptibles d'exprimer leur déception, ce qui entraîne des perceptions négatives qui affectent l'adoption.

Il est conseillé d'avoir une feuille de route pour votre composant qui couvre tous les cas d'utilisation raisonnables à long terme. Il est également utile d'indiquer explicitement dans la documentation ce qui n'est pas pris en charge et pourquoi afin de définir les attentes concernant les problèmes que le composant est destiné à résoudre.

Conclusion

L'utilisation et l'optimisation des images sont complexes. Les développeurs doivent trouver un juste équilibre entre performances et qualité des images, tout en assurant une expérience utilisateur de qualité. L'optimisation des images est donc à la fois coûteuse et à fort impact.

Au lieu que chaque application réinvente la roue à chaque fois, nous avons créé un modèle de bonnes pratiques que les développeurs, les frameworks et d'autres piles technologiques peuvent utiliser comme référence pour leurs propres implémentations. Cette expérience s'avère particulièrement utile, car nous prenons en charge d'autres frameworks pour leurs composants d'image.

Le composant Image Next.js a permis d'améliorer les performances des applications Next.js, améliorant ainsi l'expérience utilisateur. Nous pensons qu'il s'agit d'un excellent modèle qui fonctionnerait bien dans l'écosystème global, et nous aimerions connaître l'avis de développeurs qui aimeraient l'adopter dans leurs projets.