Eine langsame Seite fühlt sich an wie ein Größenproblem, und deshalb wird zuerst komprimiert. Meistens ist es aber ein Reihenfolgeproblem: Der Browser hätte das wichtigste Bild längst laden können, er wusste nur noch nichts davon.
Kleinere Bilder, gleiche Note
Ein Muster, das mir in Projekten immer wieder begegnet: Die Search Console meldet für die mobilen Seiten eine schlechte LCP. Jemand wandelt daraufhin alle Bilder nach WebP um, halbiert ihre Dateigröße, wartet vier Wochen – und die Bewertung bewegt sich kaum.
Schaut man sich den Seitenaufbau dann tatsächlich an, ist der Grund fast immer
derselbe. Das große Bild oben auf der Startseite steht nicht im HTML. Es steckt
als background-image im Stylesheet oder wird von einem
Slider-Skript eingesetzt, und ein Performance-Plugin hat allen Bildern
vorsorglich loading="lazy" gegeben. Der Browser erfährt also erst
spät, dass er dieses Bild braucht, und behandelt es dann nicht als dringend. Der
eigentliche Download dauert danach nur noch einen Bruchteil der Zeit.
Die kleineren Dateien waren nicht falsch. Sie haben nur an dem Posten gespart, der ohnehin der kleinste war. Wer LCP verbessern will, muss deshalb erst wissen, wofür die Zeit draufgeht – und das lässt sich ziemlich genau zerlegen.
Was LCP misst
Largest Contentful Paint ist der Zeitpunkt, an dem das größte sichtbare Element
im Viewport gemalt ist, gemessen ab dem Beginn der Navigation. Als Kandidaten
zählen Bilder, Poster und erste Frames von Videos, Bilder aus
background-image, SVG-<image> und Blöcke mit
Text. Ein <canvas> gehört nicht dazu.
- Gut sind 2,5 Sekunden oder weniger, schlecht ist alles über 4 Sekunden. Bewertet wird das 75. Perzentil aller Seitenaufrufe, getrennt nach Mobilgeräten und Desktop.
- Der Browser meldet während des Ladens mehrere Kandidaten, jeden größer als den vorigen. Mit der ersten Eingabe – Tippen, Scrollen, Taste – hört er auf; der letzte Kandidat zählt.
- Unsichtbare Elemente mit opacity: 0 werden übergangen, ebenso Bilder, die den ganzen Viewport füllen und damit eher Hintergrund als Inhalt sind, und Platzhalter mit sehr wenig Bildinformation.
- Die Messschnittstelle gibt es in Chrome seit Version 77, in Firefox seit 122 und in Safari seit 26.2. Seit Dezember 2025 ist sie damit Baseline.
Der letzte Punkt hat eine Einschränkung, die man kennen sollte: Die Werte in der Search Console und in PageSpeed Insights stammen aus dem Chrome User Experience Report, also nur von Chrome-Nutzern. Was Safari-Besucher erleben, taucht dort nicht auf. Wer es wissen will, muss selbst messen – dazu unten mehr.
Vier Phasen, zweimal Warten
Die Zeit bis zur LCP lässt sich lückenlos in vier aufeinanderfolgende Abschnitte teilen. Das ist keine Theorie, Chrome zeigt genau diese Aufteilung im Performance-Panel der DevTools unter „LCP breakdown“ an:
- Time to First Byte: vom Klick bis zum ersten Byte des HTML-Dokuments.
- Resource Load Delay: vom ersten Byte bis zu dem Moment, in dem der Browser den Download des LCP-Bildes startet.
- Resource Load Duration: der Download des Bildes selbst.
- Element Render Delay: vom Ende des Downloads, bis das Element tatsächlich auf dem Bildschirm steht.
Die zweite und die vierte Phase sind reines Warten. In ihnen tut das Netz nichts für den Hauptinhalt, der Browser ist mit anderem beschäftigt oder weiß schlicht nicht Bescheid. Als grobe Zielmarke gilt deshalb: Die beiden Warte-Phasen zusammen sollten unter 20 Prozent bleiben, der Rest teilt sich etwa hälftig auf Server und Download. Bei einem Budget von 2,5 Sekunden sind das rund 1000 Millisekunden für das erste Byte, 1000 für den Download und jeweils höchstens 250 für die beiden Wartezeiten.
Ist das LCP-Element ein Text, fallen die beiden mittleren Phasen weg, weil es nichts herunterzuladen gibt. Dann bleiben nur das erste Byte und die Renderverzögerung. Genau das zeigt die Messung darunter: Sie zerlegt die LCP dieser Seite, so wie du sie gerade geladen hast.
-
LCP-Elementdas größte Element im sichtbaren Bereich - nicht gemessen
-
Time to First Bytebis das erste Byte des HTML ankommt - nicht gemessen
-
Resource Load DelayWarten, bis der Download des LCP-Bildes beginnt - nicht gemessen
-
Resource Load DurationDownload des LCP-Bildes - nicht gemessen
-
Element Render DelayWarten, bis das Element tatsächlich gemalt ist - nicht gemessen
-
Largest Contentful PaintSumme der vier Phasen, Grenze für „gut“: 2500 ms - nicht gemessen
Früh entdecken
Die Phase, die in der Praxis am meisten kostet, ist die Ladeverzögerung. In den Felddaten, die Chrome sammelt, wartet die mittlere Website mit schlechter LCP fast viermal so lange auf den Start des Bild-Downloads wie auf den Download selbst. Die Ursache ist fast immer, dass der Browser das Bild zu spät findet oder zu spät für wichtig hält.
Browser haben dafür einen eigenen Mechanismus, den Preload-Scanner. Er überfliegt
das HTML, noch während es ankommt, und stößt Downloads für alles an, was er in
src- und srcset-Attributen findet. Alles, was nicht im
Markup steht, sieht er nicht: Bilder in CSS-Dateien, Bilder, die JavaScript
einfügt, und Pfade in data-src, die ein Lazy-Loading-Skript erst
später umkopiert. So sieht die späte Variante aus:
/* Den Pfad kennt der Browser erst, wenn dieses Stylesheet
geladen ist UND eine Regel auf ein Element zutrifft.
Bis dahin ist das Bild für ihn nicht vorhanden. */
.zw-hero {
background: url('/images/hero-2400.jpg') center / cover;
}
Und so die frühe. Das Bild steht als echtes <img> im HTML, die
Gestaltung als Hintergrund übernimmt object-fit: cover im
Stylesheet:
<section class="zw-hero">
<!-- Kein loading-Attribut: "eager" ist der Standard.
fetchpriority hebt das Bild über die anderen Bilder,
die der Browser sonst gleichrangig anfordert. -->
<img
class="zw-hero__bild"
src="/images/hero-1200.avif"
srcset="/images/hero-800.avif 800w,
/images/hero-1200.avif 1200w,
/images/hero-2000.avif 2000w"
sizes="100vw"
width="2000" height="1125"
alt="Arbeitsplatz mit zwei Monitoren"
fetchpriority="high">
<h1 class="zw-hero__titel">Websites, die zuerst das Wichtige laden</h1>
</section>
fetchpriority="high" ist ein Hinweis, keine Anweisung, und ein
Browser, der das Attribut nicht kennt, ignoriert es folgenlos. Unterstützt wird
es in Chrome und Edge seit Version 101, in Safari seit 17.2 und in Firefox seit
132 – seit Oktober 2024 also in allen großen Engines. Sinnvoll ist es für genau
ein Bild pro Seite. Wer es an fünf Bilder hängt, lässt sie gegeneinander
antreten und hat nichts gewonnen.
Manchmal muss das Bild im CSS bleiben, etwa weil ein Theme es so vorgibt. Dann
hilft ein Vorabruf im <head>. Er macht das Bild für den
Preload-Scanner sichtbar, ohne das Markup umzubauen, und kann über
imagesrcset dieselbe Auswahl an Größen anbieten wie ein
<img>:
<link
rel="preload"
as="image"
href="/images/hero-1200.avif"
imagesrcset="/images/hero-800.avif 800w,
/images/hero-1200.avif 1200w,
/images/hero-2000.avif 2000w"
imagesizes="100vw"
type="image/avif"
fetchpriority="high">
Das gilt nicht nur für Bilder. Diese Seite lädt ihre Schriften auf dieselbe Art vorab: Sie stecken im Stylesheet und würden sonst erst angefordert, wenn das CSS vollständig da ist. Der Vorabruf im Layout kürzt diese Kette um eine Netzwerkrunde.
Geladen ist nicht gemalt
Die vierte Phase beginnt, wenn alle Bytes da sind, und endet, wenn das Element zu sehen ist. Dazwischen liegt alles, was den Browser am Malen hindert. Bei Text-LCP ist das sogar die einzige Phase nach dem ersten Byte, und dort ist sie auch am häufigsten das Problem.
- Stylesheets im head blockieren das Rendern, bis sie vollständig geladen sind. Je größer das CSS, desto länger steht die Seite leer – auch wenn das Bild längst im Speicher liegt.
- Synchrone Skripte im head halten den Parser an. Was nicht vor dem ersten Bild laufen muss, bekommt defer oder type="module".
- Webfonts mit font-display: block lassen Text in einer kurzen Blockphase unsichtbar. Für Fließtext ist swap die bessere Wahl; diese Seite nutzt block nur für die Icon-Schrift, deren Ligaturen sonst als Wörter aufblitzen würden.
- Anti-Flicker-Snippets von A/B-Test-Werkzeugen blenden die Seite mit opacity: 0 aus, bis ihr Skript geladen ist. Solange zählt kein Element als gemalt.
- Inhalt, den erst clientseitiges JavaScript erzeugt, existiert nicht, bevor das Bundle geladen und ausgeführt ist. Serverseitiges Rendern setzt ihn ins HTML.
Ein weiterer Posten ist der Hauptthread. Solange ein langer Task läuft, malt der Browser nicht, gleichgültig wie fertig der Inhalt ist. Die Partikelwellen im Kopf meiner Startseite sind zwar nur ein paar Kilobyte groß, aber sie richten einen WebGL-Kontext ein, übersetzen Shader und laden ein Gitter aus 22.500 Punkten auf die Grafikkarte. Das ist Arbeit, die mit dem ersten Bild konkurrieren würde. Das Modul wird deshalb nicht mit dem Rest geladen, sondern erst, wenn sein Canvas sichtbar ist und der Browser eine Leerlaufphase meldet:
// Vereinfacht: Die Deko kommt erst, wenn der Hauptinhalt
// steht. Vorher fiele das Einrichten von WebGL genau in die
// Phase, in der die Überschrift gemalt werden soll.
whenVisible(canvas, () => {
whenIdle(() => {
import('./digital-waves').then(({ init }) => init());
});
});
Erst dann: Bytes sparen
Erst wenn Entdeckung und Rendern im Griff sind, lohnt sich der Blick auf die Dateigröße. Dann aber spürbar, denn die Downloaddauer ist die einzige Phase, die mit der Verbindung des Besuchers schwankt – auf einem schwachen Mobilnetz wird aus einem Posten, der am Schreibtisch unauffällig ist, schnell der größte.
- Moderne Formate: AVIF oder WebP statt JPEG, mit einem JPEG-Fallback im picture-Element, wo ältere Geräte wichtig sind.
- Passende Größen über srcset und sizes. Ein 2400 Pixel breites Bild auf einem 400 Pixel breiten Display überträgt ein Vielfaches der nötigen Daten.
- Bild vom eigenen Host statt von einer fremden Domain. Jede zusätzliche Domain kostet einen Verbindungsaufbau; wo es nicht anders geht, verkürzt rel="preconnect" ihn.
- Lange Cache-Laufzeiten für Dateien mit Hash im Namen, damit der zweite Besuch gar nicht erst lädt.
Das erste Byte
Die erste Phase ist die Grundlast, auf der alles andere aufsetzt. Liegt sie schon über zwei Sekunden, ist eine gute LCP praktisch nicht mehr zu erreichen, egal wie sauber der Rest ist. Die üblichen Hebel sind unspektakulär:
- Weiterleitungsketten vermeiden. Jede Umleitung ist ein vollständiger Request vor dem eigentlichen; interne Links zeigen deshalb direkt auf die Ziel-URL und nicht auf eine alte Adresse, die erst weiterleitet.
- Seiten, die für alle Besucher gleich aussehen, aus einem Cache ausliefern statt bei jedem Aufruf neu zu rendern.
- Einzigartige Query-Parameter aus Kampagnen-Links nicht bis zum Server durchreichen, wenn sie das Caching aushebeln.
Bei einer Laravel-Anwendung kommt ein Schritt dazu, der gern vergessen wird: Die Konfiguration, die Routen und die Views werden ohne Cache bei jedem Request neu eingelesen und aufgebaut. Einmal pro Deployment zusammengefasst, entfällt das.
# Nach jedem Deployment. Fasst config:cache, route:cache,
# view:cache und event:cache in einem Befehl zusammen.
php artisan optimize
Die schnellste erste Byte-Zeit ist allerdings die, die der Besucher gar nicht erlebt. Chrome und Edge können eine Seite über die Speculation Rules im Hintergrund vorab rendern, sobald sich ein Klick abzeichnet – etwa weil die Maus eine Weile auf dem Link ruht. Beim Klick wird dann nur noch umgeschaltet, und die LCP fällt gegen null.
<!-- Browser ohne Unterstützung kennen den Skript-Typ nicht
und übergehen den Block. Kein Fallback nötig. -->
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/blog/*" },
"eagerness": "moderate"
}]
}
</script>
Das ist eine reine Verbesserung für Chromium-Browser: Safari unterstützt die Regeln seit 26.2 nur hinter einer Einstellung und ohne Prerender, Firefox gar nicht. Vorsicht bei Links, die etwas auslösen, etwa ein Abmelde- oder Warenkorb-Link – die gehören nicht in die Regel. Und für den Weg zurück lohnt ein Blick in die DevTools unter „Back/forward cache“: Landet eine Seite dort, ist sie beim Zurückblättern ohne jeden Request sofort wieder da.
In dieser Reihenfolge
Aus der Zerlegung ergibt sich die Arbeitsreihenfolge fast von selbst:
- Felddaten ansehen, nicht nur Lighthouse. PageSpeed Insights zeigt oben die echten Werte der letzten 28 Tage, getrennt nach Mobil und Desktop.
- Das LCP-Element bestimmen. Im Performance-Panel der DevTools einmal aufzeichnen und das Insight „LCP breakdown“ öffnen – dort stehen Element und Phasen.
- Die größte Phase zuerst angehen und dabei mit den Warte-Phasen beginnen. Sie kosten meist am meisten und sind am billigsten zu beheben.
- Danach Geduld: Die Felddaten bilden einen gleitenden Zeitraum von 28 Tagen ab. Eine Verbesserung ist erst nach einigen Wochen vollständig sichtbar.
Wer auch Safari und Firefox sehen will, misst selbst. Die web-vitals-Bibliothek liefert in ihrer Attribution-Variante genau die vier Phasen mit, die oben in der Tabelle stehen:
import { onLCP } from 'web-vitals/attribution';
// Ein Wert pro Seitenaufruf, gemeldet, sobald er feststeht:
// nach der ersten Eingabe oder spätestens, wenn die Seite in
// den Hintergrund geht. sendBeacon überlebt auch das Schließen.
onLCP(({ value, attribution }) => {
navigator.sendBeacon('/vitals', JSON.stringify({
pfad: location.pathname,
lcp: Math.round(value),
element: attribution.target,
ttfb: Math.round(attribution.timeToFirstByte),
ladeverzoegerung: Math.round(attribution.resourceLoadDelay),
ladedauer: Math.round(attribution.resourceLoadDuration),
renderverzoegerung: Math.round(attribution.elementRenderDelay),
}));
});
Fazit
LCP ist keine Eigenschaft eines Bildes, sondern einer Kette. Der Server liefert das HTML, der Browser findet darin das Bild, lädt es und malt es. Kompression verkürzt nur ein Glied davon, und oft nicht das längste. Die größten Gewinne stecken fast immer in den beiden Abschnitten, in denen gar nichts übertragen wird.
Und die Kette reißt gern wieder. Eine Seite, die beim Launch schnell war, bekommt ein neues Plugin, einen zweiten Tracking-Dienst, ein Hero-Bild aus dem Seitenbaukasten. Deshalb würde ich die Messung nicht als einmaliges Projekt sehen, sondern als Wert, den man im Blick behält – mit den Phasen daneben, damit man bei einem Rückschritt sofort weiß, wo man suchen muss.
Quellen
Die Zerlegung in vier Phasen und die Zielanteile nach Optimize Largest Contentful Paint auf web.dev, die Grenzwerte und Kandidaten nach Largest Contentful Paint. Die Felddaten zur Ladeverzögerung stammen aus Common misconceptions about how to optimize LCP. Browser-Unterstützung für LargestContentfulPaint, fetchPriority und die Speculation Rules API nach MDN, die Attribution nach der Dokumentation der web-vitals-Bibliothek.