Largest Contentful Paint is the time from navigation start until the largest image or text block in the viewport is painted. Google’s published threshold for “good” is 2.5 s at the 75th percentile, with anything over 4 s counted as poor.
Advice about fixing LCP often arrives as a list of tactics: compress the images, use a CDN, preload the hero. Each tactic addresses a different part of the metric, so a change can miss the bottleneck on your page. Start by finding which part is taking the time.
The four sub-parts
LCP decomposes into four consecutive phases. They cover the full metric without overlap, which makes the breakdown useful for choosing a fix. Google documents the same four-part model in its LCP optimization guide.
- Time to first byte (TTFB). Navigation start until the first byte of the document arrives. Everything else waits behind this.
- Resource load delay. From TTFB until the browser starts fetching the LCP resource. This is dead time: the browser knew about the document but had not yet discovered, or was not yet allowed to start, the thing that becomes the LCP element.
- Resource load duration. How long that fetch actually took.
- Element render delay. From the resource finishing until it is painted on screen.
If the LCP element is a text block rather than an image, there is no resource to fetch, so phases 2 and 3 collapse and the time lives in TTFB and render delay — usually render delay, caused by a render-blocking stylesheet or a webfont.
Getting the breakdown
In Chrome DevTools, the Performance panel records a load and marks the LCP event; the Insights sidebar gives you the phase breakdown and names the element. Lighthouse has an “Largest Contentful Paint element” audit that tells you which node was chosen, which is the first thing you need to know and the thing people most often assume rather than check.
Two things to confirm before you optimise anything:
- Which element is it, on the form factor that is failing? The largest element in a 390 px viewport is frequently not the largest in a 1440 px one.
- Is that element even worth being the LCP? Sometimes the answer is that a giant decorative background or a cookie banner has been elected LCP, and the fix is to change the page, not the delivery.
What moves each part
TTFB
Slow TTFB is a server and network problem, and no amount of image work will hide it. The usual causes, in the order they are worth checking:
- No caching in front of the origin. A page rendered per-request that could
be cached, or a CDN that is passing through because of a
Cache-Controlheader nobody meant to send. - Distance. An origin in one region serving a global audience, with no edge in between.
- Slow server work. Database queries, N+1s, uncached API calls on the render path.
- Redirect chains. Every hop before the real document is pure TTFB.
http://tohttps://towww.to the canonical path is three round trips before any useful byte moves.
Static generation can help when the HTML can be built ahead of time and served from an edge cache. It removes server rendering work from each request, although the actual result still depends on the cache and network path.
Resource load delay
This phase is easy to miss. Look for a gap between the document arriving and the hero image request starting. Causes include:
- The image is not in the initial HTML. If it is injected by JavaScript, set as a CSS background, or lives inside a client-rendered component, the browser’s preload scanner cannot find it while parsing, so the fetch starts late.
loading="lazy"on the LCP image. Lazy-loading the hero is a common own goal — it defers precisely the request you need earliest. Lazy-load below the fold, never above it.- Bandwidth contention. The hero is competing with scripts and fonts for a constrained connection.
The fixes are about discoverability and priority: put a real <img> in the
served HTML, add fetchpriority="high" to the LCP image, and use
<link rel="preload"> when the URL is genuinely not discoverable from the markup.
Note that fetchpriority="high" is a raise in priority, not a queue jump past
the round trips in front of it — it does not repair a fetch that starts two
seconds late for structural reasons.
Resource load duration
This is where the usual image advice applies.
- Serve modern formats. AVIF or WebP over JPEG/PNG.
- Serve the right dimensions.
srcsetandsizesso a phone is not downloading a desktop-width file. - Cut the bytes. A hero image exported at full resolution may be much larger than the space it occupies. Measure the file and responsive variants before changing it.
- Use the same origin, or preconnect. A hero served from a third-party host
pays DNS, TCP and TLS before the first byte of the image;
<link rel="preconnect">gets that handshake started early.
Element render delay
The resource arrived and the pixels still are not on screen. Look for:
- Render-blocking CSS. The browser will not paint until the CSS it needs is parsed. Split what is not needed for the initial view.
- Fonts, when the LCP element is text. A font-blocking period can delay the
text;
font-display: swaplets fallback text appear while the font loads, and preloading the font file can shorten the wait when that font is needed. - Hydration gating the paint. If the element only appears after a JavaScript framework has booted, your LCP includes your bundle download, parse and execution. Server-render the above-the-fold content.
- Long tasks. A busy main thread cannot paint. The same work that ruins INP will hold up LCP.
The order I work in
- Confirm which element is the LCP, on the failing form factor.
- Get the four-phase breakdown and find the phase that owns the most time.
- Fix that phase. Re-measure.
- Repeat — because fixing the dominant phase usually promotes a different one, and sometimes elects an entirely different LCP element.
That last point is the one worth internalising. LCP is not a single lever. It is a chain, and shortening the longest link just makes some other link the longest.
Lab numbers make this loop fast because they are reproducible. Field data is what finally confirms it. My published case study quotes a lab run on the Lighthouse 12 mobile preset — protectorguardrail.com at 1.7 s, measured on 5 August 2026. That is a recorded lab result; use your own page’s breakdown to decide which changes to make.