Performance: Largest Contentful Paint für Web Vitals optimieren

Im vergangenen Jahr hatten wir bereits über unsere Bemühungen zur Optimierung des Cumulative Layout Shift (CLS) berichtet. Heute setzen wir die Core-Web-Vitals-Optimierungen mit dem LCP fort.

In Pocket speichern vorlesen Druckansicht 9 Kommentare lesen
Lesezeit: 6 Min.
Inhaltsverzeichnis

Im vergangenen Jahr hatten wir bereits über unsere Bemühungen zur Optimierung des Cumulative Layout Shift (CLS) berichtet. Heute setzen wir die Core-Web-Vitals-Optimierungen mit dem LCP fort.

Der Largest Contentful Paint (LCP) ist die Zeit vom Beginn des Pageload bis zum Anzeigen des größten Inhaltselements im Viewport. Das ist häufig ein Bild, kann aber auch durchaus Text sein. Bei Bildern werden <img>, <svg> mit <image> und bei <video> das Poster-Image (<video poster="url">) berücksichtigt. Via CSS muss ein Bild mit url() geladen werden um für den LCP in Frage zu kommen. Bei skalierten Bildern greift der kleinere Wert: Ein hochskaliertes Bild zählt nur mit seiner ursprünglichen Größe, ein herunterskaliertes Bild zählt mit seiner skalierten Größe.

Ein LCP-Wert von der heise online Startseite am Desktop aus dem Juli 2021. Diese Felddaten stammen aus Googles CrUX-Report.

Während des Ladens einer Seite kann es mehrere LCPs geben, beispielsweise weil ein Bild erst nach dem Text gerendert wird. Für den Wert der Metrik zählt am Ende der letzte gemeldete LCP. Das kann auch ein Element sein, das im Verlauf des Seitenaufbaus wieder aus der Seite entfernt wird. Wenn nicht noch ein größeres Element auftaucht, bleibt das zuletzt gemeldete Element als LCP bestehen.

Google nimmt für die Bewertung der Core Web Vitals ein Ampelsystem. Grün ("Good") zählt ein LCP bis 2,5 Sekunden, rot ("Poor") wird es ab vier Sekunden und alles dazwischen fällt ins Gelbe ("Needs Improvement").

Bei Felddaten gibt Google eine prozentuale Verteilung über die drei Kategorien an, sofern diese für die getestete URL vorliegen. Felddaten sind Daten aus Googles CrUX-Report, welche von Messungen aus Chromium-Browsern echter Nutzer:innen stammen, die dieser Datensammlung zugestimmt haben.

Top-Teaser-Bereich auf der heise online Startseite.

Auf der Startseite von heise online haben wir den sogenannten Top-Teaser-Bereich. Das sind mehrere Zeilen zwischen einem und drei Teasern, die die Redaktion dort positioniert. Eines dieser Bilder wird bei uns immer das LCP-Element – welches genau hängt von der Kuratierung der Redaktion ab.

Seit 2018 wurden die Bilder von unserem Custom Element <a-img> ausgeliefert. Es ermittelt die zur Verfügung stehende Größe, die Pixeldichte des Anzeigegeräts und kümmert sich auch gleich um Lazy Loading. Der Nachteil für die Webperformance ist, dass der Browser erst davon erfährt, dass an dieser Stelle ein Bild gerendert werden muss, wenn JavaScript im Hintergrund bereits das Bild fertig geladen hat. Der Preload-Scanner des Browsers kann sich nicht nützlich einbringen und einen Preload-Hint im <head> konnten wir nicht setzen, weil es zig verschiedene Varianten des Bildes geben kann.

Im Februar entschlossen wir uns dann, die Bilder im ersten sichtbaren Bereich nach dem Laden der Seite wieder klassisch per <img> auszuliefern. Nun waren wir auch in der Lage einen Preload-Hint im <head> zu hinterlassen (<link rel="preload" href="/path/to/image.jpg" as="image">).
Eine Verbesserung zu diesem Ansatz schickten wir dann im März hinterher, als wir das Bild-Tag zu einem <img srcset="…"> erweiterten und ebenso den Preload-Hint anpassten <link rel="preload" imagesrcset="…">.

Nun waren wir zwar aus Webperformancesicht ganz gut aufgestellt, aber das srcset war nicht perfekt. Es deckte lediglich die gängigen Responsive-Ausprägungen unseres Layouts ab und damit in Kombination noch gängige Pixeldichten, was auch bereits zu einer recht umfangreichen srcset-Definition führte.

Wir schauten uns nochmal die Nachteile unseres Custom Elements <a-img> an. Für unsere Nutzerinnen und Nutzer bot es nämlich das individuell beste Bild und diesen Vorteil wollten wir nicht einfach aufgeben. <a-img> baute das finale <img> erst in die Seite ein, nachdem im Hintergrund das Bild vollständig geladen war. Bis dahin residierte ein <div> an der Stelle, das als Platzhalter diente, um Layout-Sprünge zu vermeiden. Aus Performancesicht war dieses Vorgehen für sich genommen nicht ideal, da mehrere DOM-Writes vollzogen wurden:

  • <a-img> schrieb ein <div class="spacer"> ins DOM,
  • <a-img> lud im Hintergrund das echte Bild,
  • <a-img> löschte das <div class="spacer"> aus dem DOM und
  • <a-img> schrieb das finale <img> ins DOM.

Wir passten <a-img> also an. Es wird nun direkt mit einem <img> ausgeliefert, das im src="…" inline ein minimales graues Platzhalter-SVG hat. Dieses <img> dient nun gleichzeitig als "Spacer", sodass das JavaScript zwei DOM-Writes weniger benötigt. Der Browser weiß sofort, dass an der Stelle ein Bild gerendert werden muss und kann das dank des Inline-SVGs sofort übernehmen. Hat das JavaScript im Hintergrund das tatsächliche Bild fertig geladen, wird nur noch das src-Attribut ausgetauscht und aus dem grauen Platzhalter wird das echte Bild.

Der Preload-Hint ist nun natürlich nicht mehr sinnvoll möglich, weshalb wir ihn wieder entfernt haben, da wir auf keinen Fall ein nicht benötigtes Bild darüber laden wollen. Auch würde es nichts bringen, wenn das JavaScript einen passenden Preload-Hint ergänzt, da es zu diesem Zeitpunkt bereits selbst anfängt das Bild zu laden.

Ein Screenshot aus dem CrUX-Report zum Largest Contentful Paint auf Mobilgeräten über ganz heise online. Entwicklung von 78,13% im August zu 87,05% im Mai 2021 im Bereich unter 2,5 Sekunden (Kategorie "Good")

Für den Moment haben wir eine gute Mischung aus Bildqualität für unsere Nutzerinnen und Nutzer und ordentlicher Webperformance gefunden. Die Entwicklung der LCP-Metrik stimmt uns ebenfalls zu. Seit letztem Jahr August hat sich unser Largest Contentful Paint verbessert, und seit ein paar Monaten bestehen wir auf dieser Seite auch den Core-Web-Vitals-Test. (hih)