Der Filmstrip im PageSpeed-Report zeigt eine fertig gemalte Seite nach gut anderthalb Sekunden. Die Metrik darüber sagt 6,7 Sekunden LCP. Beides steht im selben Bericht, und beides stimmt. Wer an dieser Stelle anfängt, Bilder zu komprimieren oder das Hero-Bild zu preloaden, optimiert am falschen Ende. Der Wert kommt nicht vom Bild.
Genau dieser Fall war der Ausgangspunkt der V3-Optimierung von emit-solution.com am 09.07.2026. Der Performance-Score ging am Ende von 70 auf 92 bis 96, das simulierte LCP von 6,7 auf 2,4 Sekunden. Der größte Teil dieser Verbesserung hatte nichts mit dem LCP-Element zu tun.
Foto: lukechesser / Unsplash
Der Standardmodus misst nicht, er simuliert
Lighthouse lädt die Seite im Standardmodus ungedrosselt. Erst danach rechnet ein Modell die Metriken auf ein langsames Mobilgerät hoch. In core/config/constants.js steht dazu throttlingMethod: 'simulate' als Default. Die Simulations-Engine heißt Lantern und lebt seit 2024 nicht mehr im Lighthouse-Repo, sondern im Trace-Engine-Teil der Chrome DevTools. Lighthouse re-exportiert sie nur noch.
Das Mobilprofil mobileSlow4G ist im Code hart hinterlegt.
mobileSlow4G: {
rttMs: 150,
throughputKbps: 1.6 * 1024,
requestLatencyMs: 150 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
downloadThroughputKbps: 1.6 * 1024 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
uploadThroughputKbps: 750 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
cpuSlowdownMultiplier: 4,
},
Das entspricht laut Kommentar im Code WebPageTests Fast-3G-Profil und ungefähr dem 75. Perzentil der 4G-Verbindungen. Desktop rechnet mit desktopDense4G, also 40 ms RTT, 10 Mbit/s und CPU-Faktor 1. Die Lighthouse-Doku benennt die Grenzen dieses Ansatzes selbst und spricht von einer “inherent inaccuracy”, weil alternative Ausführungspfade vorhergesagt werden müssen. Für tiefe Analysen empfiehlt sie Packet-Level-Throttling.
Wie der LCP-Graph entsteht
Lantern baut pro Metrik zwei Abhängigkeitsgraphen, einen optimistischen und einen pessimistischen, simuliert beide getrennt und mittelt das Ergebnis. Für LCP gilt {intercept: 0, optimistic: 0.5, pessimistic: 0.5}, der ausgewiesene Wert ist also schlicht der Mittelwert zweier Modellrechnungen.
Interessant ist, wie der Graph zugeschnitten wird. Er endet am beobachteten LCP-Zeitstempel, und bei Netzwerk-Knoten entscheidet die Endzeit.
// Exclude all nodes that ended after cutoffTimestamp
// (except for the main document which we always consider necessary)
const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
if (endedAfterPaint && !node.isMainDocument()) {
return false;
}
Das ist der Punkt, an dem die verbreitete Kurzfassung ungenau wird. Es sind nicht alle Requests, die vor dem LCP starten. Es sind alle Requests, die vor dem beobachteten LCP fertig geworden sind. Ein Request, der früh startet und erst nach dem Paint endet, fliegt aus dem Graphen. Ein kleines Tracking-Pixel, das 80 Millisekunden vor dem Paint durchläuft, bleibt drin. Bei CPU-Knoten gilt dagegen die Startzeit, mit dem Kommentar im Code, dass das Paint-Event innerhalb der render-blockierenden Task liegen kann.
Der optimistische Graph filtert lediglich Bilder mit Priorität Low oder VeryLow heraus. Der pessimistische nimmt alles und packt zusätzlich alle CPU-Knoten dazu, die ein Layout durchgeführt haben. Lighthouse-Maintainer adamraine schreibt im offenen Issue 15737 selbst, der optimistische Graph sei “extremely similar to the pessimistic graph”.
Foto: alessiozaccaria / Unsplash
Und dann kommt der Schritt, der alles erklärt.
static override getEstimateFromSimulation(simulationResult: Simulation.Result): Simulation.Result {
const nodeTimesNotOffscreenImages = Array.from(simulationResult.nodeTimings.entries())
.filter(entry => LargestContentfulPaint.isNotLowPriorityImageNode(entry[0]))
.map(entry => entry[1].endTime);
return {
timeInMs: Math.max(...nodeTimesNotOffscreenImages),
nodeTimings: simulationResult.nodeTimings,
};
}
Math.max über die Endzeiten aller Knoten. Der simulierte LCP ist definitionsgemäß der Zeitpunkt, an dem der letzte Knoten im Graphen fertig wird, nicht der Zeitpunkt eines Paints. Derselbe Maintainer formuliert die Konsequenz im Issue deutlich, nämlich dass das Verkürzen eines unwichtigen Requests den LCP verbessern kann, nur weil dieser Request zufällig der letzte im Graphen war.
Dazu kommt, dass der Simulator die verfügbare Bandbreite gleichmäßig auf alle laufenden Requests aufteilt, über connection.setThroughput(this.throughput / inFlight). Viele parallele Anfragen verlangsamen im Modell also jede einzelne. Bei maximal zehn gleichzeitigen Requests und einem Startzeit-Aufschlag nach Priorität, von 0 Sekunden bei VeryHigh bis 2 Sekunden bei VeryLow, summiert sich das schnell.
Schritt eins der Diagnose
Zuerst brauchst du beide Zahlen nebeneinander. Ein lokaler Lauf im selben Modus wie PSI.
npx lighthouse https://example.com \
--form-factor=mobile --screenEmulation.mobile \
--throttling-method=simulate \
--only-categories=performance \
--output=json --output-path=audit.json \
--chrome-flags="--headless=new"
Dann die entscheidende Zeile.
jq '.audits.metrics.details.items[0] | {simuliert: .largestContentfulPaint, beobachtet: .observedLargestContentfulPaint, fcp_beobachtet: .observedFirstContentfulPaint, ttfb: .timeToFirstByte, lcpLoadDelay, lcpLoadDuration, lcpRenderDelay}' audit.json
Beide Werte schreibt dasselbe Artefakt in dasselbe Objekt. Dieselbe Abfrage funktioniert gegen die PageSpeed-Insights-API, dort unterhalb von lighthouseResult.
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2Fexample.com&strategy=mobile&category=performance&key=$PSI_KEY" \
| jq '.lighthouseResult.audits.metrics.details.items[0] | {simuliert: .largestContentfulPaint, beobachtet: .observedLargestContentfulPaint}'
Als Gegenprobe hilft ein Lauf ohne Simulation. Mit --throttling-method=provided liefert Lighthouse den Trace-Wert direkt und setzt den CPU-Multiplikator auf 1. Was dabei herauskommt, ist keine Aussage über langsame Netze, aber es trennt sauber zwischen Renderproblem und Kettenproblem.
Ein Hinweis zur Erwartungshaltung. Ein lokaler Lauf reproduziert PSI nicht eins zu eins. Google hat den CPU-Throttling-Faktor für PageSpeed Insights am 05.12.2024 angepasst, laut Release Note “to account for the low CPU performance benchmarks typical in PageSpeed Insights production environments”. Eine Zahl nennt Google dabei nicht. Der lokale Lauf dient der Diagnose, nicht dem Nachstellen des Punktestands.
Genauso wenig taugt ein einzelner Lauf als Urteil. Weil die Simulation auf einem ungedrosselten Trace aufsetzt, schlägt jede Auslastung des Runners direkt auf die Eingangsdaten des Modells durch. Bei der V3-Optimierung hat identischer Code einmal 52 und einmal 95 Punkte ergeben. Seitdem werte ich grundsätzlich drei Läufe aus und nehme den Median. Ein guter Schnelltest dafür ist der TBT. Liegt er über einer halben Sekunde, während alle anderen Werte unverändert aussehen, war der Runner ausgelastet und das Ergebnis sagt nichts über die Seite. Dasselbe gilt lokal. Ein Lighthouse-Lauf auf einer Maschine, auf der nebenher ein Build läuft, ist verschwendete Zeit. Erst die eigene CPU beruhigen, dann messen, danach das JSON auswerten.
Foto: kmuza / Unsplash
Schritt zwei, die Kette sichtbar machen
Wenn der beobachtete Wert klein und der simulierte groß ist, liegt das Problem in der Kette. Der network-requests-Audit ist in Lighthouse 13 weiterhin vorhanden, in der Gruppe “hidden” mit Gewicht 0, und liefert pro Request unter anderem networkRequestTime, networkEndTime, priority, transferSize und isLinkPreload.
jq --argjson lcp "$(jq '.lighthouseResult.audits.metrics.details.items[0].observedLargestContentfulPaint' psi.json)" '
.lighthouseResult.audits["network-requests"].details.items
| map(select(.networkEndTime != null and .networkEndTime <= $lcp))
| sort_by(-.networkEndTime)
| .[0:10]
| map({url, resourceType, priority, transferSize, networkRequestTime, networkEndTime})' psi.json
Das ist die Liste, auf die es ankommt. Ganz oben steht der Kandidat, der im Modell den Wert setzt. Das ist nicht zwangsläufig das LCP-Bild. Im V3-Fall standen dort spekulative Font-Loads, dekorative Vollbild-Layer und eine Frame-Sequenz. Ergänzend lohnt audits['largest-contentful-paint-element'] für das Element samt Phasen-Aufteilung sowie der Filmstrip aus audits['screenshot-thumbnails'], den man wirklich anschauen sollte statt nur zu exportieren.
Vom Befund zur Maßnahme
Drei Muster decken die meisten Fälle ab.
Beobachtet und simuliert liegen beide hoch. Dann ist es ein echtes Renderproblem und die üblichen Hebel greifen, also TTFB, render-blockierende Ressourcen, Bildgröße. Im V3-Fall war 42 KB externes CSS allein rund 3,5 Sekunden simuliertes LCP wert. Erst nach dem Inlining wurden die anderen Probleme überhaupt messbar. Wer mit Astro arbeitet, sollte dabei eine Falle kennen. build: { inlineStylesheets: 'always' } zusammen mit dynamischen import('@fontsource/...') führt dazu, dass die CSS-Chunks nicht als Dateien geschrieben werden, der Vite-Preload-Helper sie aber trotzdem anfordert. Ergebnis ist ein 404-Regen. Der Ausweg war die FontFace-API mit statischen woff2-Dateien in public/.
Beobachtet niedrig, simuliert hoch, und die Kette besteht aus Netzwerk-Knoten. Dann geht es nicht darum, weniger zu laden, sondern später zu starten. Zuverlässig war dafür nur ein Gate am largest-contentful-paint-Eintrag per PerformanceObserver. loading="lazy" greift nicht, wenn das Element im Viewport liegt, selbst bei opacity: 0. requestIdleCallback feuert auf schnellen Maschinen praktisch sofort. window.load ebenfalls, mit identischem Code einmal 2,1 und einmal 6,2 Sekunden. Wie so ein Gate im Detail aussieht, habe ich in Nachladen erst nach dem LCP beschrieben.
Beobachtet niedrig, simuliert hoch, und der Render-Delay-Anteil dominiert. Dann stecken CPU-Knoten in der Kette, mit Faktor 4 bewertet. Im V3-Fall half eine Defer-Queue für synchrone Inline-Skripte im Body, geflusht nach DOMContentLoaded über rAF und setTimeout(0). Der beobachtete Paint fiel dabei von 1,34 auf 1,07 Sekunden, und die CPU-Knoten waren aus der Lantern-Kette raus. Ebenfalls wirksam war, nur die tatsächlich genutzten Webfonts eager zu laden, im Fall von 13 auf 3. Und ein Detail, das gegen die Intuition läuft. Ein Vollbild-<img> als LCP-Element verschlechterte die Simulation deutlich, weil der Bild-Load damit in die Kette wanderte. Text-LCP schlug Bild-LCP.
Was du nicht tun solltest, ist Preloads zu entfernen, nur weil die Zahl dadurch sinkt. Issue 16539 beschreibt genau diesen Effekt und ist offen. Es gibt keine Zusage, dass Google das ändert.
Foto: wwarby / Unsplash
Was Lighthouse 13 an der Diagnose ändert
Lighthouse 13.0 erschien am 10.10.2025 und braucht Node 22.19 oder neuer. PageSpeed Insights läuft laut Release Notes seit dem 20.10.2025 darauf. Acht Audits sind laut Chrome-Blog ersatzlos aus Report und JSON verschwunden, darunter offscreen-images, font-size und first-meaningful-paint. Wer Skripte gegen diese Keys geschrieben hat, bekommt jetzt null.
An der Bewertung ändert das nichts. Der Chrome-Blog stellt klar, dass sich am Performance-Scoring nichts geändert hat, weil es auf den Metriken beruht und nicht auf den Audits. Die Gewichte sind unverändert, also TBT 30, LCP 25, CLS 25, FCP 10, Speed Index 10 und INP 0.
An die Stelle der alten Audits sind Insights getreten, alle mit Gewicht 0. Relevant für die LCP-Diagnose sind vor allem lcp-breakdown-insight, lcp-discovery-insight, network-dependency-tree-insight und render-blocking-insight. Der Fahrplan dahin lief über Lighthouse 12.6 mit Chrome 137, wurde ab 12.7 im Juni 2025 Default und ist mit Chrome 139 in PSI und DevTools ausgerollt.
Wann der Laborwert egal ist
Google bewertet nach Felddaten aus CrUX, nicht nach dem Laborwert aus der Simulation. Der Search-Console-Bericht zu den Core Web Vitals stützt sich ausschließlich darauf und zeigt den Wert, den 75 Prozent der Seitenaufrufe der letzten 28 Tage erreicht haben. PSI schreibt den Unterschied selbst in die Doku, nämlich dass Labordaten zum Debuggen taugen, reale Engpässe aber nicht zwingend abbilden.
Praktisch heißt das zweierlei. Der simulierte Wert ist ein Diagnosewerkzeug und ein Verkaufsargument, kein Rankingfaktor. Und wenn die Felddaten grün sind, während PSI 6 Sekunden anzeigt, ist die Seite in Ordnung und das Modell erzählt etwas über Requests, nicht über Nutzer. Umgekehrt gilt das genauso. Ein grüner Laborwert bei roten Felddaten ist ein Hinweis darauf, dass die echten Geräte und Netze deiner Besucher anders aussehen als mobileSlow4G.
Wenn du einen PageSpeed-Report vor dir hast, bei dem die Zahl und der Filmstrip nicht zusammenpassen, schick mir gern die URL. Ich schaue mir observed gegen simuliert und die Request-Kette an und sage dir, ob dein Problem im Rendering liegt oder im Graphen.
Häufige Fragen
Warum zeigt PageSpeed Insights ein anderes LCP als die Chrome DevTools?+
Weil beide unterschiedliche Dinge tun. Die DevTools-Performance-Aufzeichnung zeigt den tatsächlich beobachteten Paint auf deinem Rechner. PageSpeed Insights läuft im Standardmodus mit throttlingMethod simulate und rechnet den ungedrosselten Ladevorgang anschließend auf ein langsames Mobilprofil hoch. Im Lighthouse-Standardprofil sind das 1638 Kbps, 150 ms RTT und ein CPU-Faktor von 4. Für PageSpeed Insights hat Google den CPU-Faktor laut Release Note vom 05.12.2024 angepasst, ohne dabei eine Zahl zu nennen. Der eine Wert ist eine Beobachtung, der andere eine Modellrechnung.
Wo finde ich den echten LCP-Wert im Lighthouse-Report?+
Im JSON unter audits.metrics.details.items[0].observedLargestContentfulPaint. Im selben Objekt liegen der simulierte Wert als largestContentfulPaint sowie timeToFirstByte, lcpLoadDelay, lcpLoadDuration und lcpRenderDelay. In der HTML-Ansicht ist das nicht sichtbar, du brauchst also den JSON-Export oder die PageSpeed-Insights-API.
Bringt es etwas, Preloads zu entfernen, damit der PageSpeed-Wert besser wird?+
Für die Zahl oft ja, für die Nutzer meist nein. Im offenen Lighthouse-Issue 16539 beschreibt der Melder genau diesen Fall. Nach dem Entfernen von modulepreload-Hinweisen verbesserte sich der simulierte Wert, während die Hydration von rund 500 auf rund 2000 Millisekunden stieg. Wer so optimiert, tauscht echte Ladezeit gegen Punktestand.
Warum schwanken meine PageSpeed-Ergebnisse so stark?+
Weil die Runner sich CPU teilen und der ungedrosselte Trace die Grundlage der Simulation ist. Bei der V3-Optimierung von emit-solution.com hat identischer Code einmal 52 und einmal 95 Punkte gemessen. Sinnvoll ist deshalb nur der Median aus drei Läufen. Ein TBT über 0,5 Sekunden bei sonst gleichen Werten spricht für einen ausgelasteten Runner, nicht für ein Problem auf der Seite.
Du willst mehr erfahren?
In einem kostenlosen Erstgespräch besprechen wir, wie du diese Themen für dein Unternehmen nutzen kannst. Kein Verkaufsgespräch, sondern eine ehrliche Einschätzung.
Kostenloses Erstgespräch vereinbaren



