Performance
LCP: the five worst causes
LCP is the time of the largest visible element: good under 2.5 s. The five causes that hurt it most, in the order worth fixing.
Key points
- LCP measures when the largest element in the first viewport appears.
- If TTFB is above 800 ms, optimising the image does not move the metric.
- The field endpoint says whether a page passes, Lighthouse says why.
LCP is the time when the largest element in the first viewport appears: under 2.5 s is good. The five causes that hurt it most are high TTFB, an unprioritised hero image, render-blocking CSS, fonts that hide the text and markup built on the client.
The five causes, most common first
| Cause | Signal in the lab | Fix |
|---|---|---|
| High TTFB | The document arrives after 800 ms | HTML cache and a closer server |
| Hero without priority | The LCP image has low priority | fetchpriority=high and preload |
| Blocking CSS | A stylesheet before the first paint | Critical CSS inline, the rest deferred |
| Font that hides | Invisible text during the swap | font-display swap and a close fallback |
| Client-side content | LCP discovered after hydration | Hero markup rendered by the server |
Order matters: with a TTFB above 800 ms, optimising the image moves nothing, because the browser does not yet know the image exists. In our registry TTFB p75 is 509.8 ms over 262 readings, so the first fix concerns the document.
Finding the element that counts
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
console.log(Math.round(last.startTime), last.element, last.url);
}).observe({ type: "largest-contentful-paint", buffered: true });
The last entry is the one that counts: LCP changes whenever a larger element enters the viewport. If element is empty the candidate is a background image, which the browser does not name, and you have to find it by hand in the Performance panel.
The command that repeats the measurement
npx lighthouse https://example.com --preset=desktop --only-categories=performance --output=json --output-path=./lcp.json
Lighthouse runs once under simulated throttling: it compares two builds, it does not decide whether a page passes. The verdict stays with the field, in /api/rum/summary or in the Chrome UX Report.
The limitation you have to name
Our sample is the beacon's local registry: one site, with automated sessions inside. A p75 of 1,672.7 ms here says nothing about the reader's site, and the real threshold lives only in the Chrome UX Report at the 75th percentile, split between mobile and desktop.
Where to continue
In the catalogue: hero components, image stream hero and hero blocks. The guides: srcset and sizes and what Core Web Vitals are; the sibling article on CLS and the comparison of Next.js and Vite.
Frequently asked questions
Does LCP measure the full page load?
No. It measures when the largest element in the first viewport appears, usually a hero image or a heading. The rest of the page can finish later.
What is the LCP threshold?
2.5 s at the 75th percentile of visits, split between mobile and desktop. Above 4 s the page needs work.
Why does preloading the hero image help?
Because the browser finds the file before it has read the CSS and JavaScript that hide it. With fetchpriority=high the image moves up the request queue.
Is TTFB part of LCP?
Yes. LCP starts at navigation, so every millisecond of TTFB adds up. With a TTFB of 800 ms half of the 2.5 s budget is already spent.
Can I measure LCP without field data?
Only in the lab, with Lighthouse or WebPageTest. The lab explains the cause but cannot say whether real users pass: that needs the Chrome UX Report.