First, find the element
Largest Contentful Paint measures when the largest image or text block in the viewport finished rendering. Before optimising anything, find out which element that actually is — the browser picks it, and it is very often not what you assumed. It also differs between mobile and desktop, because a different set of elements fits above the fold.
Any field monitoring tool with attribution will name the element for you. In Chrome DevTools the Performance panel marks it on the timeline. The HTTP Request Timing tool loads a URL in a real browser and reports its LCP alongside a full resource waterfall, which is enough to see both the element and what was competing with it for bandwidth.
The four phases
Every LCP breaks into four consecutive spans of time. They always sum to the total, so whichever is largest is the only one worth working on:
- Time to First Byte — everything before your server's first byte arrives: redirects, DNS, TCP, TLS, and server processing. This is the floor; LCP can never be faster than TTFB. See what TTFB is.
- Resource load delay — the gap between the first byte arriving and the browser starting to fetch the LCP resource. Pure latency: the browser did not know it needed the image yet, or was blocked from asking.
- Resource load duration — how long the LCP resource itself took to download. This is the phase people assume dominates, and often it does not.
- Element render delay — the gap between the resource finishing and the element actually appearing. Usually the main thread being too busy to paint, or render-blocking CSS still outstanding.
The diagnostic value is in the ratio. If load duration is 200 ms and load delay is 1,800 ms, compressing the image harder is close to pointless — the browser spent nearly two seconds before it even started fetching it.
Fixing a dominant TTFB
A slow first byte poisons everything downstream. Look for redirect chains first — each hop is
a full round trip, and http://example.com → https://example.com →
https://www.example.com is two of them before any work starts. Then check
whether you are serving from a CDN edge or from one origin the whole world must reach. Then
look at the server: uncached database queries, cold serverless starts, and synchronous calls
to third-party APIs during page render are the usual causes.
Fixing a dominant load delay
This phase is nearly always self-inflicted, and it has the highest-leverage fixes on this page.
Never lazy-load the LCP element. This is the most common own goal in web
performance. loading="lazy" on a hero image tells the browser to defer it until
layout proves it is visible — which adds a guaranteed delay to the one image you most need
early. Lazy-loading is for images below the fold, and applying it site-wide by default will
reliably damage LCP.
Raise its priority. Browsers initially assign images a low priority. For the hero, say otherwise:
<img src="/hero.webp" width="1200" height="600" alt="…" fetchpriority="high">
Preload resources the browser cannot discover early. A CSS background image
is invisible to the preload scanner until the stylesheet has downloaded and parsed — that is
a textbook load-delay problem. Either use a real <img>, or preload it:
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
Preload deliberately, though — preloading many resources just re-sorts the queue and starves whatever you did not preload.
Fixing a dominant load duration
Here the resource genuinely is too big or too slow. Serve modern formats (WebP or AVIF),
size the image to its actual displayed dimensions rather than shipping a 3000px original to
a 400px slot, and use srcset so phones do not download desktop assets:
<img src="/hero-800.webp"
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 800px"
width="800" height="400" alt="…" fetchpriority="high">
If the LCP element is text rather than an image, the resource is the web font. Use
font-display: swap so text renders immediately in a fallback, and preload the
font file — an invisible-text period does not stop the clock.
Fixing a dominant render delay
The resource arrived and the browser could not paint it. Usually that means render-blocking
CSS still outstanding, or long JavaScript tasks occupying the main thread. Inline the
critical CSS needed for above-the-fold content and defer the rest; move non-essential scripts
to defer or async; and be suspicious of any third-party tag that
runs synchronously in the head. Client-side rendered pages hit this phase hardest, because
nothing paints until the framework has hydrated.
Common mistakes to avoid
Optimising an element that is not the LCP element. Compressing every image
on the page achieves nothing if the LCP element is an h1 waiting on a web font.
Applying loading="lazy" everywhere. Worth repeating, because it
usually arrives as a well-meaning site-wide change and shows up as an unexplained LCP
regression weeks later.
Preloading everything. Priority is relative. If everything is high priority, nothing is, and you have simply reshuffled which resource gets starved.
Testing only on your own machine. A developer laptop on office fibre with a warm cache is the least representative device that will ever load your site.
Judging the fix on one lab run. Lab numbers vary between runs. Confirm against field data, at the 75th percentile, split by device.
Troubleshooting
Why did LCP get worse after I added a preload? Because preloading is zero-sum. Promoting one resource demotes others; if you preloaded something that was not on the critical path, you delayed something that was.
Why is my LCP element different on mobile? A different set of elements fits in a narrow viewport, so a different one is largest. Optimise both — they may need different fixes.
Why is LCP fine in the lab and poor in the field? Your lab run is one fast device on a fast connection with an unrepresentative cache state. That gap is itself the finding: the problem is what the page costs on real hardware, not what your server does.
Why did LCP improve but the assessment did not? Field data is aggregated over a rolling 28-day window, so recent improvements are diluted by weeks of older measurements. It also fails if INP or CLS is failing — all three must pass.