Wie ich meinen LCP um 70 % gesenkt habe

Lerne fortgeschrittene Methoden zur Verbesserung der Core Web Vitals

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-04

LCP-Metriken verbessern mit Web Workers und zweistufigem Bildladen

Meistens wird ein großes Bildelement im sichtbaren viewport zum Largest Contentful Paint-Element. Du kannst alle Lighthouse-Best-Practices anwenden: Bildgröße anpassen, Bilder komprimieren, in WebP konvertieren und das LCP-Element vorladen. Trotzdem besteht dein Largest Contentful Paint die Core Web Vitals vielleicht nicht.

Der einzige Ausweg sind fortgeschrittene Methoden wie zweistufiges Laden und das Aufteilen der Seite mit Web Workers, um Ressourcen auf dem main thread freizugeben.

Why should I preload the largest contentful paint image

Zuletzt geprüft von Arjen Karel im März 2026

Hintergrund

Ich bin Pagespeed-Spezialist und meine Website ist mein Showcase. Auf meiner Homepage behaupte ich stolz, dass meine Seite die schnellste der Welt ist. Deshalb muss meine Seite so schnell wie möglich laden. Ich will jede Millisekunde Ladezeit aus meiner Seite herausholen.

Die Techniken, die ich dir heute zeige, sind für eine durchschnittliche (WordPress-)Seite ohne ein engagiertes, talentiertes Entwicklerteam vielleicht nicht umsetzbar. Auch wenn du diese Technik auf deiner eigenen Seite nicht nachbauen kannst: Lies den Artikel trotzdem. Lerne, wie ich über Geschwindigkeit nachdenke und was meine Überlegungen sind.

Das Problem: Große Bilder im sichtbaren viewport

Ein großes Bild im sichtbaren viewport wird oft zum Largest Contentful Paint-Element. Häufig besteht dieses LCP-Bild die Core Web Vitals nicht. Solche Ergebnisse sehe ich täglich.

bad LCP with large image

So bringst du dieses Element schnell auf den Bildschirm:

  1. Lade das LCP-Element vor. Das Vorladen des LCP-Bildes stellt sicher, dass der Browser das Bild so früh wie möglich erhält. Kombiniere dies mit fetchpriority="high", damit der Browser dieses Bild gegenüber anderen Ressourcen bevorzugt.
  2. Nutze responsive Bilder. Liefere keine Bilder in Desktop-Größe an mobile Geräte aus.
  3. Komprimiere deine Bilder. Bildkomprimierung kann die Bildgröße drastisch reduzieren.
  4. Nutze moderne Bildformate. Moderne Bildformate wie WebP übertreffen ältere Formate wie JPEG und PNG in fast allen Fällen.
  5. Minimiere den kritischen Rendering-Pfad. Eliminiere alle render blocking Ressourcen wie JavaScript und Stylesheets, die den LCP verzögern könnten.

Leider bestehen die LCP-Metriken trotz all dieser Optimierungen manchmal die Core Web Vitals-Prüfung nicht. Warum? Allein die Bildgröße reicht aus, um die Phase resource load duration des LCP zu verzögern.

Die Lösung: zweistufiges Laden und Web Workers

Meine Lösung (nachdem alle anderen Probleme auf meiner Seite behoben waren): zweistufiges Bildladen.

Die Idee ist einfach: Zeige beim ersten Rendern ein Bild mit niedriger Qualität. Es hat exakt dieselben Abmessungen wie das finale, hochauflösende Bild. Sobald dieses Bild angezeigt wird, startet der Tausch gegen das hochauflösende Bild.

Eine einfache Implementierung sieht so aus: Du fügst dem Bild einen load-Event-Listener hinzu. Sobald das Bild lädt, entfernt sich der Listener selbst und die src des Bildes wird gegen das finale, hochauflösende Bild ausgetauscht.

<img
     width="100"
     height="100"
     alt="some alt text"
     src="lq.webp"
     onload="this.onload=null;this.src='hq.webp'"
>

Stufe 1: Low-Quality-WebP 3-5 KB

Stufe 2: High-Quality-WebP 20-40 KB

Das klingt einfach und ist es auch. Aber: Wenn du viele Bilder früh im Rendering-Prozess austauschst, erzeugt das zu viel Aktivität auf dem main thread. Das verschlechtert andere Core Web Vitals-Metriken.

Deshalb lagere ich einen Teil der Arbeit in einen Web Worker aus. Ein Web Worker läuft in einem eigenen Thread und hat keinen direkten Zugriff auf die Seite. Die Kommunikation zwischen Web Worker und Seite läuft über ein Messaging-System. Der Vorteil: Wir belasten den main thread der Seite nicht und halten dort Ressourcen frei. Der Nachteil: Die Nutzung eines Web Workers ist etwas umständlich.

Der Ablauf ist nicht kompliziert. Sobald das DOMContentLoaded-Event feuert, sammle ich alle Bilder auf der Seite. Ist ein Bild bereits geladen, tausche ich es sofort aus. Ist es noch nicht geladen (etwa wegen lazy loading), hänge ich einen Event-Listener an, der den Tausch nach dem Laden anstößt.

Ein wichtiger Haken: Der Browser wertet jeden Bildtausch als neuen LCP-Kandidaten. Wenn der Tausch gegen das hochauflösende Bild erst nach 2,5 Sekunden stattfindet, misst der Browser den LCP zum Tauschzeitpunkt, nicht zum Zeitpunkt des Platzhalters. Deshalb muss der Web Worker das Bild so schnell wie möglich abrufen und austauschen.

Das Ergebnis: spektakulär.

Der Code für zweistufiges LCP-Laden per Web Worker

Hier ist der Code, mit dem ich meinen LCP durch zweistufiges Laden und einen Web Worker beschleunige. Der Code auf der Hauptseite ruft einen Web Worker auf, der die Bilder lädt. Der Web Worker übergibt das Ergebnis als Blob an die Hauptseite. Sobald der Blob ankommt, wird das Bild ausgetauscht.

Worker.js

Der Worker hat eine Aufgabe: Er hört auf Nachrichten. Eine Nachricht enthält eine Bild-URL und eine eindeutige ID. Zuerst wandelt er die Bild-URL in die hochauflösende Version um. Bei mir ersetze ich dafür /lq durch /resize in der URL. Danach lädt der Worker das hochauflösende Bild, erstellt einen Blob und schickt diesen zusammen mit der ID zurück.
self.addEventListener('message', async event => {
    const newimageURL = event.data.src.replace("/lq-","/resize-");

    const response = await fetch(newimageURL)
    const blob = await response.blob()

    // Bilddaten an den UI-Thread senden!
    self.postMessage({
        uid: event.data.uid,
        blob: blob,
    })
})

Script.js

Die script.js läuft als normales Script auf der aktiven Webseite. Zuerst lädt das Script den Worker. Danach durchläuft es alle Bilder auf der Seite. Das passiert früh im Rendering-Prozess. Ein Bild kann bereits geladen sein oder nicht. Ist ein Bild mit niedriger Qualität schon geladen, wird der Tausch sofort gestartet. Falls nicht, registriert das Script einen Event-Listener für das load-Event. Der Tausch startet dann, sobald das Bild fertig geladen ist.

Sobald ein Bild lädt, generiert das Script eine eindeutige ID dafür. So finde ich das Bild auf der Seite leicht wieder (der Worker hat keinen DOM-Zugriff, wir können also kein DOM-Node senden). Die Bild-URL und die ID gehen an den Worker. Hat der Worker das Bild geladen, sendet er es als Blob zurück. Das Script tauscht schließlich die alte Bild-URL gegen die vom Web Worker erstellte Blob-URL aus.

var myWorker = new Worker('/path-to/worker.js');

// Nachricht an Worker senden
const sendMessage = (img) => {

        // UID macht das Finden des Bildes einfacher
        var uid = create_UID();

        // data-uid auf dem Bildelement setzen
        img.dataset.uid = uid;

        // Nachricht an Worker senden
        myWorker.postMessage({ src: img.src, uid: uid });
};

// UID generieren
const create_UID = () => {
    var dt = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (new Date().getTime() + Math.random() * 16) % 16 | 0;
        dt = Math.floor(dt / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
    return uid;
}

// Wenn wir ein Ergebnis vom Worker erhalten
myWorker.addEventListener('message', event => {
    // Nachrichtendaten aus dem Event auslesen
    const imageData = event.data

    // Das Original-Element für dieses Bild holen
    const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");

    // Wir können den Blob als Bildquelle nutzen! Wir müssen ihn nur zuerst
    // in eine Object-URL umwandeln
    const objectURL = URL.createObjectURL(imageData.blob)

    // Sobald das Bild geladen ist, räumen wir auf
    imageElement.onload = () => {
        URL.revokeObjectURL(objectURL)
    }
    imageElement[0].setAttribute('src', objectURL)
})

// Alle Bilder holen
document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll('img[loading="lazy"]').forEach(
        img => {

            // Bild bereits fertig geladen?
            img.complete ?

                // Sofort austauschen
                sendMessage(img) :

                // Nach dem Laden austauschen
                img.addEventListener(
                    "load", i => { sendMessage(img) }, { once: true }
                )
        })
})

Nutze Real User Monitoring, um die LCP-Verbesserung in der Praxis zu überprüfen. So erfasst du, wie echte Besucher deine Seite erleben. Lab-Tools wie Lighthouse zeigen die Verbesserung zwar an. Am Ende zählen aber die field data von echten Nutzern auf unterschiedlichen Verbindungen, um die Core Web Vitals zu bestehen.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Search Console meckert an deiner Site rum?

Du bekommst eine priorisierte Fix-Liste auf Basis echter Daten. Kein 50-Seiten-PDF.

Audit anfragen
Wie ich meinen LCP um 70 % gesenkt habe Core Web Vitals Wie ich meinen LCP um 70 % gesenkt habe