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 bei der Optimierung. Nicht optimierte Bilder sorgen für eine überladene Seite und machen beim 90.th Perzentil über 70% der gesamten Seitengröße in Byte aus. Mehrere Möglichkeiten zur Bildoptimierung erfordern eine intelligente Bildkomponente mit standardmäßig integrierten Leistungslösungen.

Das Aurora arbeitete mit Next.js zusammen, um eine solche Komponente zu entwickeln. 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, hatten 38% weniger Bilder als Sitzungen, bei denen keine Conversion erfolgte. 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öße 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 signifikantes Unterschied des Seitenverhältnisses 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, lassen sich Bilder responsive skalieren und gleichzeitig CLS verhindern.

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

Je größer ein Bild 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 diesen 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 zuvor aufgeführten Probleme bewerten und Best-Practice-Lösungen implementieren, um sie zu überwinden. 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.
  • Zu viel Markup: Lösungen zum Einfügen 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, 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. Außerdem kennen Entwickler möglicherweise nicht den besten Abstand zum „Fold“, um verzögerte Bilder zu laden. Unterschiedliche Darstellungsbereiche auf Geräten können dies noch weiter erschweren.
  • Sich ändernde Werbelandschaft: Da Browser neue HTML- oder CSS-Funktionen zur Leistungssteigerung unterstützen, kann es für Entwickler schwierig sein, diese einzeln zu beurteilen. In Chrome wird beispielsweise die Funktion Abrufpriorität als Ursprungstest 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, Bilder zu optimieren, und die Herausforderungen bei der individuellen Implementierung für jede Anwendung brachten uns die Idee für eine Bildkomponente. 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 durch eine Vielzahl von Funktionen und Prinzipien allgemein 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 zu 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 Image-Importe verwenden, um die Größe des tatsächlichen Images auf der Festplatte zum Zeitpunkt 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 undefiniert verwenden können, wird durch das Design sichergestellt, dass sie sich die Zeit nehmen, die Bildgröße zu berücksichtigen und Layoutverschiebungen zu vermeiden.

Schnellere Reaktion

Damit Bilder geräteübergreifend responsiv werden, müssen Entwickler die Attribute srcset und sizes im Element <img> festlegen. Mit der Komponente „Bild“ 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 Haltepunkte sind in der Konfigurationsdatei enthalten.
  2. imageSizes-Eigenschaft: Dies ist ebenfalls eine konfigurierbare Eigenschaft, mit der die Bildgrößen abgerufen werden können, die den Haltepunkten für die Gerätegröße 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 die 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 werden ähnlich wie beim „“-Element festgelegt, und zwar unabhängig vom Gerät, auf dem es gerendert wird.
Bild von Bergen, das unverändert angezeigt wird Das Bild mit Bergen passt nicht auf den Bildschirm
Layout = responsiv: Je nach Breite des Containers in verschiedenen Darstellungsbereichen wird das Seitenverhältnis beibehalten.
Bild der Berge, das auf die Bildschirmgröße skaliert wurde Bild von Bergen, verkleinert für Bildschirmgröße
Layout = Fill (Ausfüllen): Breite und Höhe werden so gedehnt, dass sie den übergeordneten Container ausfüllen. (Die Breite des übergeordneten <div>-Elements ist in diesem Beispiel auf 300 × 500 festgelegt.)
Bild von Bergen, gerendert auf 300 × 500 Bild eines Berges für die Größe 300 × 500 gerendert
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 <img>-Element verwenden, gibt es einige Optionen für Lazy Loading. Diese haben jedoch alle Nachteile, die ihre 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 schneller findet. 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.

Hochleistungs-Bildhosting fördern

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 eine eigene Pipeline zur Bildoptimierung erstellen möchten.

Progressives Laden unterstützen

Beim progressiven Laden wird das Interesse der Nutzer aufrechterhalten, indem ein Platzhalterbild angezeigt wird, das in der Regel eine erheblich geringere Qualität aufweist, während das eigentliche Bild geladen wird. 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, finden Sie nachstehend einige Erkenntnisse, die Ihnen dabei helfen könnten.

Sicherheitsventile können mehr schaden als nützen

In einer Frühversion der Next.js-Bildkomponente haben wir das Attribut unsized bereitgestellt, mit dem Entwickler die Größenanforderung umgehen und Bilder mit nicht festgelegten 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 sie das Problem auf eine Weise lösen könnten, 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 problemlos, wenn sie ein klares Bild der potenziellen Leistungsvorteile 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 die Abmessungen für lokale Bilder bei der Build-Zeit automatisch mit einem Babel-Plug-in abgerufen werden.

Finde die richtige Balance 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 Funktionen für Entwickler wie automatisches Lazy Loading und integrierte unscharfe Platzhalter haben das Interesse an der Next.js-Bildkomponente gesteigert.

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 den anderen 25% dann 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. Die Bildoptimierung ist also ein kostspieliges Unterfangen mit großer Wirkung.

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 wurde die Leistung in Next.js-Anwendungen verbessert und damit die Nutzererfahrung verbessert. 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.