How to Improve Largest Contentful Paint

Most LCP advice is a list of tactics with no way to choose between them. There is a better approach: LCP decomposes into four phases, one of them is almost always dominant, and the phase tells you which fixes will work and which will do nothing at all.

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:

  1. 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.
  2. 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.
  3. Resource load duration — how long the LCP resource itself took to download. This is the phase people assume dominates, and often it does not.
  4. 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.comhttps://example.comhttps://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.

Frequently Asked Questions

What is a good LCP?

2.5 seconds or less at the 75th percentile of real visits. Between 2.5 and 4 seconds needs improvement, and above 4 seconds is poor. The percentile matters as much as the number — an average of 2.5 s can easily hide a quarter of visits taking twice that.

Should I lazy-load my hero image?

No. loading="lazy" defers a fetch until layout confirms the image is visible, which adds delay to the exact resource you most need early. Lazy-loading is for images below the fold. Applying it site-wide by default is one of the most common causes of an unexplained LCP regression.

Does a CDN fix LCP?

It fixes the part of LCP that lives in TTFB and resource download time, which can be substantial for geographically distant visitors. It does nothing for load delay caused by an undiscoverable resource, or for render delay caused by a busy main thread. Measure which phase dominates before assuming a CDN is the answer.

Why does my LCP vary so much between page loads?

Because it depends on network conditions, device capability, and cache state, none of which are constant. That variance is exactly why the metric is assessed at the 75th percentile across many real visits rather than from any single measurement.

Stop checking this by hand

NetTests runs this kind of check on a schedule, stores historical results, and alerts your team the moment something changes.

Start monitoring free →
No credit card required  ·  Free plan available