Effektive Image-Komponenten erstellen

Eine Bildkomponente verkörpert 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 wichtiger Schwerpunkt für die Optimierung. Nicht optimierte Bilder tragen zum Seitenaufblähen bei und machen über 70% des gesamten Seitengewichts in Byte im 90. Perzentil aus. Da es mehrere Möglichkeiten zur Optimierung von Bildern gibt, ist eine intelligente „Bildkomponente“ mit standardmäßig integrierten Leistungslösungen erforderlich.

Das Aurora-Team hat mit Next.js eine solche Komponente entwickelt. 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 gearbeitet und arbeiten mit Angular an der Bildoptimierung in zukünftigen Versionen. In diesem Beitrag geht es darum, wie wir die Next.js-Bildkomponente 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 zweitstärkste Vorhersagefaktor für Conversions von Nutzern, die Websites besuchen. Sitzungen, in denen Nutzer eine Conversion ausgeführt haben, enthielten 38% weniger Bilder als Sitzungen, in denen keine Conversion ausgeführt wurde. Lighthouse listet im Rahmen der Best Practices-Analyse mehrere Möglichkeiten zur Optimierung von Bildern und zur Verbesserung der Web Vitals auf. Im Folgenden finden Sie einige häufige Bereiche, in denen Bilder sich auf die Core Web Vitals und die Nutzerfreundlichkeit auswirken können.

Bilder ohne Größenangabe beeinträchtigen den CLS

Bilder, deren Größe nicht angegeben ist, können zu einer Instabilität des Layouts und zu einem hohen Cumulative Layout Shift (CLS) beitragen. Wenn Sie die Attribute width und height für img-Elemente festlegen, können Sie Layoutverschiebungen verhindern. Beispiel:

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

Breite und Höhe sollten so festgelegt werden, dass das Seitenverhältnis des gerenderten Bildes dem natürlichen Seitenverhältnis nahekommt. Ein deutlicher Unterschied im Seitenverhältnis kann dazu führen, dass das Bild verzerrt erscheint. Mit einer relativ neuen Eigenschaft, mit der Sie das Seitenverhältnis in CSS angeben können, können Sie Bilder responsiv skalieren und gleichzeitig CLS verhindern.

Große Bilder können sich negativ auf das LCP auswirken

Je größer die Dateigröße eines Bildes ist, desto länger dauert der Download. Ein großes Bild kann das „Hero“-Bild für die Seite oder das wichtigste Element im Darstellungsbereich sein, das für das Auslösen des Largest Contentful Paint (LCP) verantwortlich ist. Wenn ein Bild zu den wichtigen Inhalten gehört und der Download lange dauert, wird der LCP verzögert.

In vielen Fällen können Entwickler die Bildgröße durch eine bessere Komprimierung und die Verwendung von responsiven Bildern reduzieren. Mit den Attributen srcset und sizes des Elements <img> können Sie Bilddateien in verschiedenen Größen bereitstellen. Der Browser kann dann je nach Bildschirmgröße und Auflösung das richtige auswählen.

Eine schlechte Bildkomprimierung kann sich negativ auf den LCP auswirken

Moderne Bildformate wie AVIF oder WebP können eine bessere Komprimierung als die gängigen JPEG- und PNG-Formate bieten. Eine bessere Komprimierung reduziert die Dateigröße in einigen Fällen um 25 bis 50 % bei gleicher Bildqualität. Dies führt zu schnelleren Downloads mit weniger Datenverbrauch. Die App sollte moderne Bildformate für Browser bereitstellen, die diese Formate unterstützen.

Das Laden unnötiger Bilder wirkt sich negativ auf den LCP aus

Bilder, die sich unterhalb des Folds oder nicht im Darstellungsbereich befinden, werden Nutzern beim Laden der Seite nicht angezeigt. Sie können verschoben werden, damit sie nicht zum LCP beitragen und es verzögern. Mit Lazy Loading können solche Bilder später geladen werden, wenn der Nutzer zu ihnen scrollt.

Herausforderungen bei der Optimierung

Teams können die Leistungskosten aufgrund der oben aufgeführten Probleme bewerten und Best Practices implementieren, um sie zu beheben. In der Praxis ist das jedoch oft nicht der Fall und ineffiziente Bilder verlangsamen das Web weiterhin. Mögliche Gründe sind z. B. folgende:

  • Prioritäten: Webentwickler konzentrieren sich in der Regel auf Code, JavaScript und Datenoptimierung. Daher sind sie möglicherweise nicht mit Problemen mit Bildern oder deren Optimierung vertraut. Von Designern erstellte oder von Nutzern hochgeladene Bilder haben möglicherweise nicht die höchste Priorität.
  • Plug-and-play-Lösung: Selbst wenn Entwickler mit den Feinheiten der Bildoptimierung vertraut sind, kann das Fehlen einer All-in-One-Plug-and-play-Lösung für ihr Framework oder ihren Tech-Stack ein Hindernis darstellen.
  • Dynamische Bilder: Zusätzlich zu den statischen Bildern, die Teil der Anwendung sind, werden dynamische Bilder von Nutzern hochgeladen oder aus externen Datenbanken oder CMS stammen. Es kann schwierig sein, die Größe solcher Bilder zu definieren, deren Quelle dynamisch ist.
  • Markup-Überlastung: Lösungen zum Einfügen der Bildgröße oder 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, wird aber heute nur von 26,5%der Websites verwendet. Bei der Verwendung von srcset müssen Entwickler Bilder in verschiedenen Größen erstellen. Tools wie just-gimme-an-img können hilfreich sein, müssen aber für jedes Bild manuell verwendet werden.
  • Browserunterstützung: Moderne Bildformate wie AVIF und WebP erzeugen kleinere Bilddateien, erfordern aber eine spezielle Verarbeitung in Browsern, die sie nicht unterstützen. Entwickler müssen Strategien wie die Inhaltsverhandlung oder das Element <picture> verwenden, damit Bilder für alle Browser ausgeliefert werden.
  • Komplikationen beim Lazy Loading: Es gibt mehrere Techniken und Bibliotheken, mit denen Lazy Loading für Bilder implementiert werden kann, die erst bei Scrollen sichtbar werden. Die Auswahl der besten Option kann eine Herausforderung sein. Entwickler wissen möglicherweise auch nicht, welcher Abstand zum „Fold“ am besten geeignet ist, um verzögerte Bilder zu laden. Unterschiedliche Darstellungsbereiche auf Geräten können dies noch weiter erschweren.
  • Änderung der Landschaft: Da Browser neue HTML- oder CSS-Funktionen zur Leistungssteigerung unterstützen, kann es für Entwickler schwierig sein, jede einzelne zu bewerten. In Chrome wird beispielsweise die Funktion Abrufpriorität als Origin Trial eingeführt. Damit lässt sich die Priorität bestimmter Bilder auf der Seite erhöhen. Insgesamt wäre es für Entwickler einfacher, wenn solche Verbesserungen auf Komponentenebene bewertet und implementiert würden.

Bildkomponente als Lösung

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

Im letzten Jahr haben wir mit dem Next.js-Framework die Bildkomponente entworfen und implementiert. Es kann 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 allgemein durch eine Vielzahl von Funktionen und Prinzipien anzugehen. Außerdem bietet sie Optionen, mit denen Entwickler sie für verschiedene Bildanforderungen anpassen können.

Schutz vor Layoutverschiebungen

Wie bereits erwähnt, verursachen nicht skalierte Bilder Layoutverschiebungen und tragen zum CLS bei. Wenn Entwickler die Next.js-Bildkomponente verwenden, müssen sie eine Bildgröße mit den Attributen width und height angeben, um Layoutverschiebungen zu vermeiden. Wenn die Größe unbekannt ist, müssen Entwickler layout=fill angeben, um ein nicht skaliertes Bild in einem skalierten Container bereitzustellen. Alternativ können Sie statische Bildimporte verwenden, um die Größe des tatsächlichen Bildes auf der Festplatte zum Zeitpunkt des Builds abzurufen und in das Bild einzufügen.

// 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 ohne Größe verwenden können, sorgt das Design dafür, dass sie sich die Zeit nehmen, die Bildgröße zu berücksichtigen und Layoutverschiebungen zu verhindern.

Reaktionsfähigkeit fördern

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. Die Next.js-Bildkomponente wurde so konzipiert, dass die Attributwerte nur einmal pro Anwendung festgelegt werden. Sie werden je nach Layoutmodus auf alle Instanzen der Bildkomponente angewendet. Wir haben eine dreiteilige Lösung entwickelt:

  1. Property deviceSizes: Mit dieser Property können Sie einmal Bruchstellen basierend auf den Geräten konfigurieren, die für die Nutzerbasis der Anwendung üblich sind. Die Standardwerte für Haltestellen sind in der Konfigurationsdatei enthalten.
  2. imageSizes-Attribut: Dies ist auch ein konfigurierbares Attribut, mit dem die Bildgrößen abgerufen werden, die den Gerätegrößen entsprechen.
  3. layout-Attribut in jedem Bild: Hiermit wird angegeben, wie die Properties 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 anhand der Größe des Geräts, das die Seite anfordert, das Bild, das bereitgestellt werden soll, und legt srcset und sizes im Bild entsprechend fest.

Im folgenden Vergleich wird gezeigt, wie Sie mit dem Layoutmodus die Größe des Bildes auf verschiedenen Bildschirmen steuern können. Wir haben ein Demobild verwendet, das in den Next.js-Dokumenten geteilt wurde und auf einem Smartphone und einem Standard-Laptop angezeigt wird.

Laptop-Bildschirm Smartphone-Display
Layout = „Intrinsic“ (Intrinsisch): In kleineren Darstellungsbereichen wird das Layout so skaliert, dass es in die Breite des Containers passt. Wird in einem größeren Darstellungsbereich nicht über die ursprüngliche Größe des Bilds hinaus skaliert. Containerbreite ist auf 100%
Bild von Bergen, das unverändert angezeigt wird Bild mit Bergen verkleinert
Layout = Fixed: Das Bild ist nicht responsiv. Breite und Höhe sind ähnlich wie beim Element „“ festgelegt, unabhängig davon, auf welchem Gerät es gerendert wird.
Bild von Bergen, das unverändert angezeigt wird Das Bild mit Bergen passt nicht auf den Bildschirm
Layout = Responsives Layout: Das Layout wird je nach Breite des Containers in verschiedenen Darstellungsbereichen verkleinert oder vergrößert, wobei das Seitenverhältnis beibehalten wird.
Bild der Berge, das auf die Bildschirmgröße skaliert wurde Bild der Berge, das auf die Bildschirmgröße skaliert wurde
Layout = Fill (Ausfüllen): Breite und Höhe werden so gedehnt, dass sie den übergeordneten Container ausfüllen. (Die Breite der übergeordneten <div> ist in diesem Beispiel auf 300 × 500 festgelegt.)
Bild von Bergen, gerendert auf 300 × 500 Bild von Bergen, gerendert auf 300 × 500
Für verschiedene Layouts gerenderte Bilder

Integriertes Lazy Loading

Die Bildkomponente bietet standardmäßig eine integrierte, leistungsstarke Lösung für das Lazy Loading. Wenn Sie das Element <img> verwenden, gibt es einige Optionen für das Lazy Loading. Diese haben jedoch alle Nachteile, die die Verwendung erschweren. Entwickler können einen der folgenden Lazy-Loading-Ansätze verwenden:

  • Geben Sie das Attribut loading an: Dieses Attribut wird von allen modernen Browsern unterstützt.
  • Intersection Observer API verwenden: Das Erstellen einer benutzerdefinierten Lösung für Lazy Loading erfordert Aufwand und eine durchdachte Design- und Implementierung. Entwickler haben dafür aber nicht immer Zeit.
  • Bibliothek eines Drittanbieters importieren, um Bilder per Lazy Load zu laden: Es kann zusätzlicher Aufwand erforderlich sein, um eine geeignete Bibliothek eines Drittanbieters für das Lazy Loading zu bewerten und zu integrieren.

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

Wichtige Bilder vorab laden

LCP-Elemente sind häufig Bilder. Große Bilder können die LCP-Zeit verlängern. Es ist empfehlenswert, wichtige Bilder vorab zu laden, damit der Browser sie früher erkennt. Wenn du ein <img>-Element verwendest, kann im HTML-Header ein Hinweis zum Vorladen eingefügt werden.

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

Eine gut durchdachte Bildkomponente sollte unabhängig vom verwendeten Framework eine Möglichkeit bieten, die Ladereihenfolge von Bildern anzupassen. Bei der Next.js-Bildkomponente können Entwickler mit dem Attribut priority der Bildkomponente ein Bild angeben, das sich gut zum Vorladen eignet.

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

Das Hinzufügen eines priority-Attributs vereinfacht das Markup und ist nutzerfreundlicher. Entwickler von Bildkomponenten können auch Heuristiken anwenden, um das Vorladen von Bildern, die sich im sichtbaren Bereich der Seite befinden und bestimmte Kriterien erfüllen, zu automatisieren.

Hochleistungsfähiges Bildhosting verwenden

Bild-CDNs werden für die Automatisierung der Bildoptimierung empfohlen. Außerdem unterstützen sie moderne Bildformate wie WebP und AVIF. Die Next.js-Bildkomponente verwendet standardmäßig ein Bild-CDN mit einer Ladearchitektur. Das folgende Beispiel zeigt, dass der Loader die Konfiguration des CDN in der Next.js-Konfigurationsdatei ermöglicht.

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

Bei dieser Konfiguration können Entwickler relative URLs in der Bildquelle verwenden. Das Framework verknüpft dann die relative URL mit dem CDN-Pfad, um die absolute URL zu generieren. Gängige Bild-CDNs wie Imgix, Cloudinary und Akamai werden unterstützt. Die Architektur unterstützt die Verwendung eines benutzerdefinierten Cloud-Anbieters durch Implementieren einer benutzerdefinierten loader-Funktion für die App.

Unterstützung für selbst gehostete Images

Es kann vorkommen, dass Websites keine Bild-CDNs verwenden können. In solchen Fällen muss eine Bildkomponente selbst gehostete Bilder unterstützen. Die Next.js-Bildkomponente verwendet einen Bildoptimierer als integrierten Bildserver, der eine CDN-ähnliche API bereitstellt. Der Optimierer verwendet Sharp für die Transformation von Produktionsbildern, wenn es auf dem Server installiert ist. Diese Bibliothek ist eine gute Wahl für alle, die ihre eigene Pipeline zur Bildoptimierung erstellen möchten.

Progressives Laden unterstützen

Beim progressiven Laden wird ein Platzhalterbild, in der Regel mit deutlich geringerer Qualität, angezeigt, während das eigentliche Bild geladen wird. So soll das Interesse der Nutzer aufrechterhalten werden. Sie verbessert die wahrgenommene Leistung und die Nutzerfreundlichkeit. Es kann in Kombination mit Lazy Loading für Bilder unterhalb oder oberhalb des sichtbaren Bereichs verwendet werden.

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

Auswirkungen

Durch all diese Optimierungen konnten wir die Next.js-Bildkomponente in der Produktion erfolgreich einsetzen. Außerdem arbeiten wir mit anderen Technologie-Stacks an ähnlichen Bildkomponenten.

Als Leboncoin sein altes JavaScript-Frontend zu Next.js migrierte, wurde auch die Bildpipeline auf die Next.js-Bildkomponente umgestellt. Auf einer Seite, die von <img> zu „Nächste Seite“/„Bild“ migriert wurde, sank der LCP von 2,4 Sekunden auf 1,7 Sekunden. Die Gesamtzahl der für die Seite heruntergeladenen Bild-Byte sank von 663 KB auf 326 KB (mit etwa 100 KB Lazy-Load-Bild-Byte).

Gewonnene Erkenntnisse

Alle, die eine Next.js-Anwendung erstellen, können die Next.js-Bildkomponente zur Optimierung nutzen. Wenn Sie jedoch ähnliche Leistungsabstraktionen für ein anderes Framework oder CMS erstellen möchten, können Ihnen die folgenden Erkenntnisse, die wir im Laufe der Zeit gewonnen haben, weiterhelfen.

Sicherheitsventile können mehr schaden als nützen

In einer frühen Version der Next.js-Bildkomponente haben wir das Attribut unsized bereitgestellt, mit dem Entwickler die Größenanforderung umgehen und Bilder mit nicht angegebenen Abmessungen verwenden konnten. Wir haben diese Funktion für Fälle eingeführt, in denen die Höhe oder Breite des Bildes nicht im Voraus bekannt ist. Wir haben jedoch festgestellt, dass Nutzer das Attribut unsized in GitHub-Problemen als Allroundlösung für Probleme mit der Größenanforderung empfehlen, auch in Fällen, in denen das Problem auf eine Weise gelöst werden konnte, die den CLS nicht verschlechtert. Das unsized-Attribut wurde daher eingestellt und entfernt.

Nützliche Hürden von unnötigen Ärgernissen unterscheiden

Die Anforderung, die Größe eines Bildes festzulegen, ist ein Beispiel für „nützliche Reibung“. Dies schränkt die Verwendung der Komponente ein, bietet aber im Gegenzug enorme Leistungsvorteile. Nutzer akzeptieren die Einschränkung bereitwillig, wenn sie ein klares Bild von den potenziellen Leistungsvorteilen haben. Daher lohnt es sich, diesen Kompromiss in der Dokumentation und anderen veröffentlichten Materialien zur Komponente zu erläutern.

Es gibt jedoch Möglichkeiten, solche Probleme zu umgehen, ohne die Leistung zu beeinträchtigen. Während der Entwicklung der Next.js-Bildkomponente haben wir beispielsweise Beschwerden erhalten, dass es mühsam war, die Größe lokal gespeicherter Bilder zu ermitteln. Wir haben statische Bildimporte hinzugefügt, die diesen Prozess optimieren, indem Abmessungen für lokale Bilder bei der Build-Zeit automatisch mit einem Babel-Plug-in abgerufen werden.

Ausgewogenheit zwischen praktischen Funktionen und Leistungsoptimierungen

Wenn Ihre Bildkomponente den Nutzern nur „nützliche Reibung“ auferlegt, werden Entwickler sie in der Regel nicht verwenden wollen. Wir haben festgestellt, dass Leistungsfunktionen wie die Bildgröße und die automatische Generierung von srcset-Werten am wichtigsten sind. Auch praktische Funktionen für Entwickler wie automatisches Lazy Loading und vordefinierte unscharfe Platzhalter haben das Interesse an der Next.js-Bildkomponente geweckt.

Eine Roadmap für Funktionen festlegen, um die Akzeptanz zu steigern

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 dann den anderen 25% zu sagen: „In diesen Fällen ist diese Komponente nicht für Sie geeignet.“

In der Praxis steht diese Strategie jedoch im Widerspruch zu Ihren Zielen als Komponentendesigner. Sie möchten, dass Entwickler Ihre Komponente verwenden, um von den Leistungsvorteilen zu profitieren. Das ist schwierig, wenn es eine Gruppe von Nutzern gibt, die nicht migrieren können und sich ausgeschlossen fühlen. Sie werden wahrscheinlich enttäuscht sein, was zu negativen Wahrnehmungen führt, die sich auf die Akzeptanz auswirken.

Es empfiehlt sich, eine Roadmap für Ihre Komponente zu haben, die alle vernünftigen Anwendungsfälle langfristig abdeckt. Es ist auch hilfreich, in der Dokumentation klar zu formulieren, was nicht unterstützt wird und warum, um Erwartungen an die Probleme zu formulieren, die die Komponente lösen soll.

Fazit

Die Verwendung und Optimierung von Bildern ist kompliziert. Entwickler müssen die richtige Balance zwischen Leistung und Qualität der Bilder finden und gleichzeitig für eine gute Nutzererfahrung sorgen. Das macht die Bildoptimierung zu einem kostspieligen und wirkungsvollen Unterfangen.

Anstatt bei jeder App das Rad neu zu erfinden, haben wir eine Best Practices-Vorlage entwickelt, die Entwickler, Frameworks und andere Tech-Stacks als Referenz für ihre eigenen Implementierungen verwenden können. Diese Erfahrung wird sich als wertvoll erweisen, wenn wir andere Frameworks bei ihren Bildkomponenten unterstützen.

Mit der Next.js-Bildkomponente konnte die Leistung in Next.js-Anwendungen verbessert und so die Nutzerfreundlichkeit optimiert werden. Wir sind der Meinung, dass es ein hervorragendes Modell ist, das sich gut im gesamten Ökosystem eignen würde. Wir würden uns sehr über Rückmeldungen von Entwicklern freuen, die dieses Modell in ihren Projekten verwenden möchten.