Wie ich meinen LCP um 70 % gesenkt habe
Lerne fortgeschrittene Methoden zur Verbesserung der Core Web Vitals
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.

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.

So bringst du dieses Element schnell auf den Bildschirm:
- 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. - Nutze responsive Bilder. Liefere keine Bilder in Desktop-Größe an mobile Geräte aus.
- Komprimiere deine Bilder. Bildkomprimierung kann die Bildgröße drastisch reduzieren.
- Nutze moderne Bildformate. Moderne Bildformate wie WebP übertreffen ältere Formate wie JPEG und PNG in fast allen Fällen.
- 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
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.
Search Console meckert an deiner Site rum?
Du bekommst eine priorisierte Fix-Liste auf Basis echter Daten. Kein 50-Seiten-PDF.
Audit anfragen