Effektive Image-Komponenten erstellen

Eine Bildkomponente verkapselt Best Practices für die Leistung und bietet eine sofort einsatzbereite Lösung zur Optimierung von Bildern.

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

Bilder sind eine häufige Ursache für Leistungsengpässe bei Webanwendungen und ein Hauptbereich für die Optimierung. Nicht optimierte Bilder tragen zum Bloat bei und machen derzeit über 70% der gesamten Seitengröße in Byte beim 90. Perzentil aus. Mehrere Möglichkeiten zur Bildoptimierung erfordern eine intelligente „Bildkomponente“ mit standardmäßig integrierten Leistungslösungen.

Das Aurora-Team arbeitete mit Next.js an der Entwicklung einer solchen Komponente. Ziel war es, eine optimierte Bildvorlage zu erstellen, die Webentwickler weiter anpassen können. Die Komponente dient als gutes Modell und setzt einen Standard für die Erstellung von Bildkomponenten in anderen Frameworks, Content-Management-Systemen (CMS) und Tech-Stacks. Wir haben an einer ähnlichen Komponente für Nuxt.js zusammengearbeitet und arbeiten mit Angular an der Bildoptimierung in zukünftigen Versionen. In diesem Beitrag erfahren Sie, wie wir die Next.js-Image-Komponente entworfen haben und welche Erkenntnisse wir dabei gewonnen haben.

Bildkomponente als Erweiterung von Bildern

Probleme und Optimierungsmöglichkeiten bei der Bildoptimierung

Bilder wirken sich nicht nur auf die Leistung, sondern auch auf das Geschäft aus. Die Anzahl der Bilder auf einer Seite war der zweitgrößte zuverlässigste Indikator für die Conversion-Rate von Nutzern, die Websites besuchen. Sitzungen, in denen Nutzer eine Conversion ausgeführt haben, enthielten 38% weniger Bilder als Sitzungen ohne Conversion. Im Rahmen der Best-Practices-Prüfung werden in Lighthouse verschiedene Möglichkeiten aufgeführt, Bilder zu optimieren und Web Vitals zu verbessern. Im Folgenden sind einige häufig genutzte Bereiche aufgeführt, in denen Bilder die Core Web Vitals und die Nutzererfahrung beeinflussen können.

Bilder ohne Größe beeinträchtigen die CLS

Bilder, die ohne angegebene Größe ausgeliefert werden, können zu Layoutinstabilität und einem hohen Cumulative Layout Shift (CLS) führen. Wenn Sie die Attribute width und height für img-Elemente festlegen, können Layoutverschiebungen vermieden werden. Beispiel:

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

Breite und Höhe sollten so eingestellt werden, dass das Seitenverhältnis des gerenderten Bilds annähernd seinem natürlichen Seitenverhältnis entspricht. Ein signifikanter Unterschied im Seitenverhältnis kann dazu führen, dass das Bild verzerrt wirkt. Mit einer relativ neuen Eigenschaft, mit der Sie das Seitenverhältnis in CSS angeben können, können Sie die Größe von Bildern anpassen und gleichzeitig CLS verhindern.

Große Bilder können den LCP beeinträchtigen

Je größer die Dateigröße eines Bildes, desto länger dauert der Download. Ein großes Bild kann das Hero-Image der Seite oder das wichtigste Element im Darstellungsbereich sein, das für die Auslösung des Largest Contentful Paint (LCP) verantwortlich ist. Wenn ein Bild Teil des kritischen Inhalts ist und der Download lange dauert, verzögert sich der LCP-Wert.

In vielen Fällen können Entwickler Bildgrößen durch eine bessere Komprimierung und die Verwendung responsiver Bilder verringern. Die Attribute srcset und sizes des Elements <img> helfen bei der Bereitstellung von Bilddateien unterschiedlicher Größe. Der Browser wählt dann je nach Bildschirmgröße und Auflösung den richtigen aus.

Schlechte Bildkomprimierung kann den LCP beeinträchtigen

Moderne Bildformate wie AVIF oder WebP bieten eine bessere Komprimierung als die gängigen JPEG- und PNG-Formate. Durch eine bessere Komprimierung wird die Dateigröße in einigen Fällen um 25% bis 50% verringert, um dieselbe Bildqualität zu erhalten. Dies führt zu schnelleren Downloads bei geringerem Datenverbrauch. Die App sollte in Browsern, die diese Formate unterstützen, moderne Bildformate bereitstellen.

Laden unnötiger Bilder schadet LCP

Bilder, die „below the fold“ (mit Scrollen sichtbar) sind oder nicht im Darstellungsbereich sind, werden beim Laden der Seite nicht angezeigt. Sie können aufgeschoben werden, damit sie nicht zum LCP beitragen und ihn verzögern. Mit Lazy Loading können Bilder erst dann geladen werden, wenn der Nutzer darauf scrollt.

Herausforderungen bei der Optimierung

Die Teams können die Leistungskosten aufgrund der oben aufgeführten Probleme bewerten und Best-Practice-Lösungen implementieren, um diese zu beheben. Dies geschieht jedoch häufig nicht und ineffiziente Bilder verlangsamen das Web weiterhin. Mögliche Gründe sind z. B. folgende:

  • Prioritäten: Webentwickler konzentrieren sich meist auf Code, JavaScript und Datenoptimierung. Daher wissen sie möglicherweise nicht, ob es Probleme mit Bildern gibt oder wie sie optimiert werden können. Bilder, die von Designern erstellt oder von Nutzern hochgeladen wurden, stehen möglicherweise nicht ganz oben auf der Prioritätenliste.
  • Sofort einsatzbereite Lösung: Selbst wenn Entwickler die Feinheiten der Bildoptimierung kennen, kann es abschreckend wirken, wenn es keine Universallösung für ihr Framework oder ihren Technologie-Stack gibt.
  • Dynamische Bilder: Zusätzlich zu statischen Bildern, die Teil der Anwendung sind, werden dynamische Bilder von Nutzern hochgeladen oder aus externen Datenbanken oder CMS bezogen. Es kann schwierig sein, die Größe solcher Bilder zu definieren, wenn die Quelle des Bildes dynamisch ist.
  • Zu viele Markups: Lösungen zum Einbeziehen der Bildgröße oder von srcset für verschiedene Größen erfordern zusätzliches Markup für jedes Bild, was mühsam sein kann. Das Attribut srcset wurde 2014 eingeführt und wird heute nur von 26,5%der Websites verwendet. Wenn Entwickler srcset verwenden, müssen sie Bilder in verschiedenen Größen erstellen. Tools wie just-gimme-an-img können helfen, müssen aber für jedes Bild manuell verwendet werden.
  • Unterstützung von Browsern: Moderne Bildformate wie AVIF und WebP erstellen kleinere Bilddateien, müssen aber in Browsern, die diese nicht unterstützen, besonders gehandhabt werden. Entwickler müssen Strategien wie Aushandlung von Inhalten oder das Element <picture> verwenden, damit Bilder in allen Browsern ausgeliefert werden.
  • Zusatzfunktionen von Lazy Loading: Es gibt verschiedene Techniken und Bibliotheken, um Lazy Loading für Bilder vom Typ „below the fold“ (mit Scrollen sichtbar) zu implementieren. Es kann eine Herausforderung sein, die beste auszuwählen. Entwickler kennen möglicherweise auch nicht den besten Abstand vom „Fold“ zum Laden verzögerter Bilder. Unterschiedliche Größen des Darstellungsbereichs auf Geräten können dies zusätzlich erschweren.
  • Änderung des Umfelds: Da Browser neue HTML- oder CSS-Funktionen zur Verbesserung der Leistung unterstützen, kann es für Entwickler schwierig werden, sie einzeln zu bewerten. Beispielsweise führt Chrome die Funktion Priorität abrufen als Ursprungstest ein. Damit lässt sich die Priorität bestimmter Bilder auf der Seite erhöhen. Insgesamt wäre es für Entwickler einfacher, solche Verbesserungen auf Komponentenebene zu evaluieren und zu implementieren.

Bildkomponente als Lösung

Die Möglichkeiten zur Optimierung von Bildern und die Herausforderungen bei ihrer individuellen Implementierung für jede Anwendung brachten uns zur Idee einer Bildkomponente. Eine Bildkomponente kann Best Practices verkapseln und durchsetzen. Wenn Entwickler das <img>-Element durch eine Bildkomponente ersetzen, können Entwickler Probleme mit der Bildleistung besser beheben.

Im letzten Jahr haben wir mit dem Framework Next.js gearbeitet, um die Bildkomponente zu entwerfen und implement. Es kann wie unten beschrieben als Drop-in-Ersatz für die vorhandenen <img>-Elemente in Next.js-Apps verwendet werden.

// 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" />
}

Die Komponente versucht, bildbezogene Probleme mithilfe einer Vielzahl von Funktionen und Prinzipien allgemein zu lösen. Sie enthält auch Optionen, mit denen Entwickler sie an verschiedene Bildanforderungen anpassen können.

Schutz vor Layout Shifts

Wie bereits erwähnt, führen nicht markierte Bilder zu Layoutverschiebungen und zum CLS. Wenn Entwickler die Next.js-Bildkomponente verwenden, müssen sie mithilfe der Attribute width und height eine Bildgröße angeben, um Layoutverschiebungen zu verhindern. Wenn die Größe unbekannt ist, müssen Entwickler layout=fill angeben, um ein nicht skaliertes Image bereitzustellen, das sich in einem Container mit benutzerdefinierter Größe befindet. Alternativ können Sie statische Image-Importe verwenden, um die Größe des tatsächlichen Images auf der Festplatte bei der Erstellung abzurufen und in das Image aufzunehmen.

// 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" />
}

Da Entwickler die Bildkomponente nicht unverändert verwenden können, sorgt das Design dafür, dass sie sich die Zeit nehmen, die Bildgröße zu berücksichtigen und Layoutverschiebungen zu verhindern.

Besser reagieren

Damit Bilder geräteübergreifend responsiv sind, müssen Entwickler die Attribute srcset und sizes im Element <img> festlegen. Mit der Bildkomponente wollten wir diesen Aufwand reduzieren. Wir haben die Next.js-Bildkomponente so konzipiert, dass die Attributwerte nur einmal pro Anwendung festgelegt werden. Sie werden basierend auf dem Layoutmodus auf alle Instanzen der Bildkomponente angewendet. Wir haben eine dreiteilige Lösung entwickelt:

  1. deviceSizes-Attribut: Mit diesem Attribut können einmalig Haltepunkte auf Basis der Geräte konfiguriert werden, die die Nutzer der Anwendung gemeinsam nutzen. Die Standardwerte für Haltepunkte sind in der Konfigurationsdatei enthalten.
  2. imageSizes-Eigenschaft: Dies ist auch eine konfigurierbare Eigenschaft, mit der die Bildgrößen entsprechend den Haltepunkten der Gerätegröße abgerufen werden können.
  3. Attribut layout in jedem Bild: Damit wird angegeben, wie die Eigenschaften deviceSizes und imageSizes für jedes Bild verwendet werden sollen. Die unterstützten Werte für den Layoutmodus sind fixed, fill, intrinsic und responsive

Wenn ein Bild mit den Layoutmodi responsive oder fill angefordert wird, ermittelt Next.js das bereitzustellende Bild anhand der Größe des Geräts, das die Seite anfordert, und legt srcset und sizes im Bild entsprechend fest.

Der folgende Vergleich zeigt, wie der Layoutmodus verwendet werden kann, um die Größe des Bildes auf verschiedenen Bildschirmen zu steuern. Wir haben ein Demobild verwendet, das in der Next.js-Dokumentation enthalten ist und auf einem Smartphone und einem Standardlaptop angezeigt wird.

Laptopbildschirm Smartphone-Bildschirm
Layout = Intrinsisch: Skaliert verkleinert, um die Breite des Containers an kleinere Darstellungsbereiche anzupassen. Skaliert in einem größeren Darstellungsbereich nicht über die ursprüngliche Größe des Bildes hinaus. Die Containerbreite liegt bei 100%
Bild zu Bergen, wie es ist Bild der Berge verkleinert
Layout = Korrigiert: Das Bild ist nicht responsiv. Breite und Höhe werden ähnlich wie das Element „“ unabhängig davon festgelegt, auf welchem Gerät es gerendert wird.
Bild zu Bergen, wie es ist Das abgebildete Bergbild passt nicht auf den Bildschirm
Layout = Responsiv: Je nach Breite des Containers in verschiedenen Darstellungsbereichen skalieren und das Seitenverhältnis beibehalten.
Bild der Berge, das an die Bildschirmgröße angepasst wurde Bild mit Bergen, das an den Bildschirm angepasst wurde
Layout = Füllung: Breite und Höhe werden so gestreckt, dass sie den übergeordneten Container ausfüllen. (Übergeordnetes
` width ist in diesem Beispiel auf 300*500 festgelegt)
Bild mit Bergen, gerendert für die Größe 300 × 500 Bild mit Bergen, gerendert für die Größe 300 × 500
Für verschiedene Layouts gerenderte Bilder

Integriertes Lazy Loading

Die Komponente „Bild“ bietet standardmäßig eine integrierte, leistungsstarke Lazy Loading-Lösung. Bei der Verwendung des <img>-Elements gibt es einige native Optionen für Lazy Loading, die jedoch alle ihre Nachteile mit sich bringen. Entwickler können einen der folgenden Ansätze für Lazy Loading nutzen:

  • Geben Sie das Attribut loading an: Das ist einfach zu implementieren, wird aber derzeit von einigen Browsern nicht unterstützt.
  • Intersection Observer API verwenden: Das Erstellen einer benutzerdefinierten Lazy-Loading-Lösung erfordert Aufwand sowie eine durchdachte Entwicklung und Implementierung. Entwickler haben dafür möglicherweise nicht immer Zeit.
  • Drittanbieterbibliothek importieren, um Bilder mit Lazy Loading zu laden: Die Evaluierung und Integration einer geeigneten Drittanbieterbibliothek für Lazy Loading kann mit zusätzlichem Aufwand verbunden sein.

In der Next.js-Bildkomponente ist das Laden standardmäßig auf "lazy" festgelegt. Lazy Loading wird mit Intersection Observer implementiert, die in den meisten modernen Browsern verfügbar ist. Entwickler müssen für die Aktivierung nichts weiter tun, können sie jedoch bei Bedarf deaktivieren.

Wichtige Bilder vorab laden

LCP-Elemente sind häufig Bilder und große Bilder können den LCP verzögern. Es empfiehlt sich, kritische Images vorab zu laden, damit der Browser sie schneller erkennen kann. Bei Verwendung eines <img>-Elements kann wie folgt ein Vorladehinweis in den HTML-Kopf eingebunden werden.

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

Eine gut durchdachte Bildkomponente sollte eine Möglichkeit bieten, die Ladereihenfolge von Bildern unabhängig vom verwendeten Framework zu optimieren. Bei der Next.js-Image-Komponente können Entwickler mithilfe des Attributs priority der Bildkomponente ein Bild angeben, das sich gut für das Vorabladen eignet.

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

Das Hinzufügen eines priority-Attributs vereinfacht das Markup und ist praktischer. Entwickler von Bildkomponenten können auch Optionen zum Anwenden von Heuristiken nutzen, um das Vorabladen von Bildern auf der Seite, die ohne Scrollen sichtbar sind, zu automatisieren und bestimmte Kriterien zu erfüllen.

Leistungsstarkes Bild-Hosting fördern

Bild-CDNs werden für die automatische Bildoptimierung empfohlen und unterstützen auch moderne Bildformate wie WebP und AVIF. Die Next.js-Image-Komponente verwendet standardmäßig ein Image-CDN mit einer Loader-Architektur. Das folgende Beispiel zeigt, dass das Ladeprogramm die Konfiguration des CDN in der Next.js-Konfigurationsdatei ermöglicht.

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

Mit dieser Konfiguration können Entwickler relative URLs in der Bildquelle verwenden und das Framework verkettet die relative URL mit dem CDN-Pfad, um die absolute URL zu generieren. Beliebte Bild-CDNs wie Imgix, Cloudinary und Akamai werden unterstützt. Die Architektur unterstützt die Verwendung eines benutzerdefinierten Cloud-Anbieters durch Implementierung einer benutzerdefinierten loader-Funktion für die Anwendung.

Selbstgehostete Bilder unterstützen

Es kann Situationen geben, in denen Websites keine Bild-CDNs verwenden können. In solchen Fällen muss eine Bildkomponente selbst gehostete Bilder unterstützen. Die Next.js-Image-Komponente nutzt eine Bildoptimierung als integrierten Bildserver, der eine CDN-ähnliche API bereitstellt. Das Optimierer verwendet Sharp für Transformationen von Produktionsbildern, wenn es auf dem Server installiert ist. Diese Bibliothek ist eine gute Wahl für alle, die ihre eigene Bildoptimierungs-Pipeline erstellen möchten.

Fortlaufendes Laden unterstützen

Das progressive Laden ist eine Technik, die das Interesse der Nutzer aufrechterhält, indem ein Platzhalterbild in der Regel von deutlich schlechterer Qualität angezeigt wird, während das eigentliche Bild geladen wird. Sie verbessert die wahrgenommene Leistung und verbessert die User Experience. Sie kann in Kombination mit Lazy Loading für „below the fold“-Bilder oder für „above the fold“-Bilder verwendet werden.

Die Next.js-Bildkomponente unterstützt das progressive Laden des Bildes über die placeholder-Eigenschaft. Diese Funktion kann als LQIP (Platzhalter für Bilder mit niedriger Qualität) verwendet werden, um ein Bild mit niedriger Qualität oder verschwommene Bilder anzuzeigen, während das eigentliche Bild geladen wird.

Auswirkungen

Nachdem alle oben genannten Optimierungen berücksichtigt wurden, konnten wir mit der Next.js Image-Komponente in der Produktion Erfolge erzielen und auch mit anderen Technologie-Stacks an ähnlichen Bildkomponenten arbeiten.

Als Leboncoin das alte JavaScript-Front-End zu Next.js migrierte, wurde auch die Image-Pipeline auf die Next.js-Image-Komponente aktualisiert. Auf einer Seite, die von <img> zu „next/image“ migriert wurde, ist der LCP von 2,4 s auf 1,7 s gesunken. Die Gesamtzahl der für die Seite heruntergeladenen Bildbyte stieg von 663 KB auf 326 KB (mit etwa 100 KB an Lazy-Loading-Bildbyte).

Gewonnene Erkenntnisse

Wenn Sie eine Next.js-App erstellen, können Sie die Next.js-Bildkomponente zur Optimierung einsetzen. Wenn Sie jedoch ähnliche Abstraktionen für ein anderes Framework oder CMS erstellen möchten, könnten die folgenden Erkenntnisse hilfreich sein.

Sicherheitsventile können mehr Schaden als Nutzen verursachen

In einer frühen Version der Next.js-Bildkomponente haben wir ein unsized-Attribut bereitgestellt, mit dem Entwickler die Größenanforderung umgehen und Bilder mit nicht angegebenen Abmessungen verwenden konnten. Wir dachten, dies wäre notwendig, wenn es nicht möglich war, die Höhe oder Breite eines Bildes im Voraus zu ermitteln. Wir haben jedoch festgestellt, dass Nutzer bei GitHub-Problemen das Attribut unsized als universelle Lösung für Probleme mit den Größenanforderungen empfehlen, selbst wenn sie das Problem auf eine Weise lösen konnten, die die CLS nicht verschlimmert. Infolgedessen haben wir das Attribut unsized eingestellt und entfernt.

Trenne nützliche Reibungspunkte von sinnlosen Ärgernissen

Die Anforderung an die Größe eines Bildes ist ein Beispiel für „nützliche Reibung“. Sie schränkt die Verwendung der Komponente ein, bietet aber erhebliche Leistungsvorteile. Die Nutzer werden die Einschränkung akzeptieren, wenn sie eine klare Vorstellung von den potenziellen Leistungsvorteilen haben. Daher ist es sinnvoll, diese Kompromisse in der Dokumentation und in anderen veröffentlichten Materialien über die Komponente zu erläutern.

Es gibt jedoch Lösungen, mit denen sich solche Probleme beheben lassen, ohne dass die Leistung beeinträchtigt wird. So haben wir beispielsweise während der Entwicklung der Next.js-Image-Komponente Beschwerden erhalten, dass es lästig war, Größen für lokal gespeicherte Bilder zu ermitteln. Wir haben Importe statischer Bilder hinzugefügt, die diesen Prozess optimieren, indem die Abmessungen für lokale Bilder zum Build-Zeitpunkt automatisch mit einem Babel-Plug-in abgerufen werden.

Die richtige Balance zwischen Komfortfunktionen und Leistungsoptimierung finden

Wenn Ihre Bildkomponente nichts anderes als einen „nützlichen Reibungsverlust“ für die Nutzer bewirkt, werden Entwickler sie tendenziell nicht verwenden. Dabei haben sich die Leistungsmerkmale wie die Bildgröße und die automatische Generierung von srcset-Werten am wichtigsten. Komfortfunktionen für Entwickler wie automatisches Lazy Loading und eingebaute verschwommene Platzhalter sorgten auch für das Interesse an der Next.js-Bildkomponente.

Roadmap für Funktionen aufstellen, die die Akzeptanz fördern

Es ist sehr schwierig, eine Lösung zu entwickeln, die in allen Situationen perfekt funktioniert. Es kann verlockend sein, etwas zu entwerfen, das für 75% der Nutzer gut funktioniert, und den anderen 25% dann zu sagen, dass diese Komponente in diesen Fällen nicht für Sie geeignet ist.

In der Praxis stellt sich heraus, dass diese Strategie im Widerspruch zu Ihren Zielen als Komponentendesigner steht. Sie möchten, dass die Entwickler Ihre Komponente verwenden, um von den Leistungsvorteilen zu profitieren. Das ist schwer zu erreichen, wenn einige Nutzer nicht migrieren können und sich vom Gespräch ausgeschlossen fühlen. Es ist wahrscheinlich, dass sie Enttäuschung zum Ausdruck bringen, was zu negativen Wahrnehmungen führt, die sich auf die Akzeptanz auswirken.

Es empfiehlt sich, für Ihre Komponente eine Roadmap zu erstellen, die langfristig alle angemessenen Anwendungsfälle abdeckt. Es ist auch hilfreich, in der Dokumentation deutlich zu machen, was nicht unterstützt wird und warum, um Erwartungen in Bezug auf die Probleme zu wecken, die mit der Komponente gelöst werden sollen.

Fazit

Die Verwendung und Optimierung von Bildern ist kompliziert. Entwickelnde müssen das Gleichgewicht zwischen Leistung und Qualität der Bilder finden und gleichzeitig eine großartige Nutzererfahrung gewährleisten. Dies macht die Bildoptimierung zu einem teuren und wirkungsvollen Unterfangen.

Anstatt jedes Mal das Rad neu erfinden zu müssen, haben wir eine Vorlage für Best Practices entwickelt, die Entwickler, Frameworks und andere Technologie-Stacks als Referenz für ihre eigenen Implementierungen verwenden können. Diese Erfahrung wird sich in der Tat als wertvoll erweisen, wenn wir andere Frameworks für ihre Bildkomponenten unterstützen.

Mit der Next.js-Bildkomponente konnten die Leistung in Next.js-Anwendungen verbessert und so die Nutzererfahrung verbessert werden. Wir sind davon überzeugt, dass es sich um ein großartiges Modell handelt, das gut in der allgemeinen Umgebung funktionieren würde, und würden uns über Feedback von Entwicklern freuen, die dieses Modell in ihren Projekten verwenden möchten.