---
title: "How to Improve LCP: A Step-by-Step Guide | Evaluat"
description: "Improve LCP by fixing the biggest phase first. A step-by-step guide to cutting load delay, TTFB, and render-blocking resources, with code and 2025 data."
url: "https://www.evaluat.com/blog/how-to-improve-lcp"
last_modified: "2026-07-18"
---

[Blog](https://www.evaluat.com/blog) [Web Vitals & Metrics](https://www.evaluat.com/blog/category/web-vitals-metrics)

# How to improve LCP: a step-by-step optimization guide

Most teams trying to improve Largest Contentful Paint reach for a smaller image, and most of the time the image is not the problem. The browser is starting it too late. This guide is the practical playbook: the fixes in priority order, the code for each, the two 2025 techniques most sites have not adopted, and how to confirm the number actually moved for real users under load.

Written by: [Ahmad Farzan](https://www.evaluat.com/authors/ahmad-farzan) · 18 June 2026 · Updated 18 July 2026

![Two Largest Contentful Paint timelines compared. Before optimization the resource load delay phase is large and LCP lands at 4.3 seconds, rated poor. After starting the LCP image early the load delay nearly disappears and LCP drops to 2.3 seconds, under the 2.5 second good threshold.](https://www.evaluat.com/blog/how-to-improve-lcp-cover.svg)

Summary

To improve Largest Contentful Paint, or LCP, find the element that paints last, split its time into four phases, and fix the biggest phase first. On most pages that biggest phase isn't a heavy image, it's a late start: the browser simply doesn't begin loading the hero image early enough. According to the 2025 Web Almanac, only about sixty-two percent of mobile pages have a good LCP. And the data backs the late-start diagnosis: most origins with poor LCP spend less than ten percent of their LCP time actually downloading the image, while the request sits delayed on the client for roughly one point three seconds at the seventy-fifth percentile. So the cheapest, highest-leverage fix is making the LCP image start immediately: reference it in the initial HTML, never lazy-load it, and mark it high priority. After that, cut Time to First Byte with caching and edge delivery, shrink the download with modern formats like AVIF, clear render-blocking scripts and styles, and consider prerendering the likely next page with the Speculation Rules API; one site cut LCP by nearly eighteen percent that way. Finally, verify the fix where it counts: in field data, at the seventy-fifth percentile, and under realistic load, because Time to First Byte rises as traffic climbs, and a green score in a quiet lab isn't a green score on sale day.

Listen to this article · 1:36

[Download the audio summary (MP3)](https://www.evaluat.com/blog/audio/how-to-improve-lcp.mp3)

To improve Largest Contentful Paint, find the element that paints last, split its time into four phases, and fix the biggest phase first. On most pages the biggest phase is not a heavy image. It is a late start, where the browser does not begin loading the LCP image early enough. As of [the 2025 Web Almanac](https://almanac.httparchive.org/en/2025/performance), only 62% of mobile pages have a good LCP, so close to four in ten ship a hero that arrives too slowly.

This guide is the practical playbook: the fixes in priority order, the code for each, the two 2025 techniques most teams have not adopted yet, and how to confirm the number moved. If you want the concept first, start with [Largest Contentful Paint, explained](https://www.evaluat.com/blog/largest-contentful-paint). For the visual-stability companion, see [how to improve CLS](https://www.evaluat.com/blog/how-to-improve-cls).

## First, find the element and split its time

Every LCP fix starts with two measurements, not guesses: which element is the LCP element, and which of its four phases owns the most time. Optimizing the wrong element, or the right element’s wrong phase, is the most common way to spend a day and move nothing.

Find the element first. Chrome DevTools names it for you: open the Performance panel, record a page load, and the LCP marker points at the exact node. For a quick reading without DevTools, you can [test your page speed for free](https://www.evaluat.com/tools/website-speed-test) with Evaluat Pulse, which loads the page in a real browser, reports LCP alongside FCP and TTFB, and records the load so you can watch the moment the element paints. Across the web [76% of mobile pages have an image as their LCP element](https://almanac.httparchive.org/en/2025/performance) (2025 Web Almanac), so most LCP work lands on image delivery, but confirm yours before you touch anything.

Then split the time. LCP is the sum of four sequential phases: Time to First Byte, resource load delay, resource load duration, and element render delay. [web.dev’s ideal split](https://web.dev/articles/optimize-lcp) is roughly 40% TTFB, under 10% load delay, about 40% load duration, and under 10% render delay (web.dev, 2025). The full breakdown lives in [the LCP explainer](https://www.evaluat.com/blog/largest-contentful-paint); what matters here is the order of attack. And the order is rarely what people expect, because [most origins with poor LCP spend less than 10% of their LCP time downloading the image](https://web.dev/articles/top-cwv), with the image instead delayed on the client by about 1,290 milliseconds at the 75th percentile (web.dev, 2024). The download is usually a thin slice. The late start is the bloat.

The fixes below are ordered by where the time usually hides.

| Fix, in order                   | The phase it attacks                | The change                                                            | Effort vs payoff                                 |
| ------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------ |
| Start the LCP image immediately | Resource load delay                 | Drop `loading="lazy"`, add `fetchpriority="high"`, put it in the HTML | Low effort, usually the biggest win              |
| Cut Time to First Byte          | TTFB                                | Cache HTML, serve from the edge                                       | Medium effort, gates everything else             |
| Shrink the image download       | Resource load duration              | AVIF or WebP, `srcset`, a CDN                                         | Medium effort, only after the load delay is gone |
| Clear what blocks the paint     | Element render delay                | Defer non-critical JS, inline critical CSS                            | Medium effort, small but real                    |
| Prerender the next page         | The whole timeline, next navigation | Speculation Rules plus bfcache                                        | Low effort, near-instant repeat navigation       |

## Step 1: make the LCP image start loading immediately

This is the cheapest, highest-leverage fix, and the one most sites miss. The goal is simple: the browser should discover and request the LCP image as early as possible, in the first pass over the HTML. Get this right and a one or two second load delay can collapse toward zero with no change to the image itself.

Three rules do most of the work. Reference the image in the initial HTML response so the browser’s preload scanner finds it, never lazy-load it, and mark it high priority:

```html
<!-- In the server-rendered HTML, not injected later by JavaScript -->
<img
  src="/hero.avif"
  width="1280" height="720"
  fetchpriority="high"
  decoding="async"
  alt="A clear description of the hero image" />
```

The numbers say how much room there is here. Roughly [16 to 17% of pages still lazy-load their LCP image](https://almanac.httparchive.org/en/2025/performance), and only 17% of mobile pages with an image LCP use `fetchpriority`, up from 15% the year before (2025 Web Almanac). Both are self-inflicted late starts.

If the image is set in CSS or injected by a script, the preload scanner cannot see it, so add an explicit preload in the `<head>`:

```html
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
```

On a framework, use the built-in hint rather than hand-rolling it. Next.js exposes `priority` on `next/image` (`<Image src={hero} alt="..." priority />`), which sets eager loading and high fetch priority together; Astro’s `<Image>` takes `loading="eager"`. **Verify it worked** in the DevTools network waterfall: the LCP image request should begin near the very start, not after a stack of scripts.

## Step 2: cut Time to First Byte

TTFB is the first phase and, on a well-tuned page, the largest, around 40% of LCP. Nothing downstream can start until the first byte of HTML arrives, so this phase gates the other three. If your server takes 800 milliseconds to respond, no amount of image work gets you under 2.5 seconds.

The levers are familiar. Cache the HTML and your API responses so a request does not rebuild the page from scratch. Serve from an origin or CDN edge in the region closest to your customers, so the bytes travel a short distance. And remove slow synchronous work from the request path: an uncached database query, a third-party call, a cold serverless function.

One caveat that single-user testing hides: TTFB is the phase that [gets worse under load](https://www.evaluat.com/blog/core-web-vitals-load-testing). As concurrency rises the server queues requests, and the first byte that arrived in 200 milliseconds for one idle visitor can take a second or more at peak. A green TTFB in a quiet lab is not a green TTFB on sale day, which is why the final step is to measure under realistic load, not just once.

## Step 3: shrink the image download

Once the request starts on time, make the download quick. This is the classic “optimize your images” advice, and it is real, but it belongs third, not first, because it only helps after the load delay is gone.

Serve a modern format and size the image to the device. Converting to AVIF or WebP is the single biggest file-size win: in [DebugBear’s testing on the MDN blog](https://developer.mozilla.org/en-US/blog/fix-image-lcp/), an LCP image dropped 95%, from 1 MB to 46 KB, on conversion to AVIF (MDN, 2025). Then use responsive `srcset` so phones do not download desktop pixels:

```html
<img
  src="/hero-1280.avif"
  srcset="/hero-640.avif 640w, /hero-1280.avif 1280w, /hero-1920.avif 1920w"
  sizes="(max-width: 700px) 100vw, 1280px"
  width="1280" height="720"
  fetchpriority="high"
  alt="A clear description of the hero image" />
```

Always set `width` and `height` (or an aspect ratio) so the browser reserves space and the image does not shift layout in late, and serve it from a CDN so the bytes start close to the user. Keep the encoded image roughly matched to its display size; a 4000-pixel-wide photo in an 800-pixel slot is wasted download.

## Step 4: clear what blocks the paint

The element has arrived but has not painted. The render delay should be under 10% of LCP, and when it is not, the cause is almost always render-blocking resources in the `<head>` or fonts that reflow text after it appears.

Three fixes cover most cases. Defer non-critical JavaScript with `defer` or `type="module"` so scripts stop blocking the parser. Inline the small amount of CSS needed to render the top of the page and load the rest asynchronously, so the browser is not waiting on a full stylesheet to paint the hero. And set `font-display: swap` on web fonts so text paints immediately in a fallback face instead of staying invisible while the font downloads. None of these change the LCP element itself; they remove the things standing between its bytes and the screen.

## Step 5: make the next navigation instant

The first four steps speed up the current page. The newest lever speeds up the *next* one, and it is the technique most teams have not turned on yet. The [Speculation Rules API](https://developer.chrome.com/docs/web-platform/implementing-speculation-rules) lets Chrome prerender a page the user is likely to visit, in the background, before they click. When they do click, the page is already rendered, so it appears almost instantly and its LCP is effectively zero.

You declare the rules in a small script tag, with an eagerness setting that trades coverage against server cost:

```html
<script type="speculationrules">
{
  "prerender": [{
    "where": { "href_matches": "/*" },
    "eagerness": "moderate"
  }]
}
</script>
```

The payoff is documented. [Monrif cut LCP by up to 17.9% and lifted engagement by up to 8.9%](https://web.dev/case-studies/monrif-cwv) by pairing Speculation Rules prerendering with back/forward cache support (web.dev, 2025). The back/forward cache (bfcache) is the companion win: it restores a fully rendered page from memory on back and forward navigations, with a sub-second LCP, as long as you avoid `unload` listeners. Start with `moderate` eagerness on safe, idempotent links, watch your server load, and widen from there.

## Verify the fix held: at the 75th percentile, and under load

A fix you cannot measure is a guess. After each change, confirm the number moved where it counts: in the field, at the 75th percentile, and under realistic concurrency.

Read field LCP with Google’s web-vitals library, which wraps the browser’s native measurement, and report it at the 75th percentile rather than the mean, because [the published threshold is the 75th percentile](https://web.dev/articles/lcp) (web.dev, 2025) and an average hides the slow tail:

```js
import { onLCP } from 'web-vitals';

onLCP(({ value, rating }) => {
  // send value to your analytics; aggregate at p75, not as an average
  console.log(value, rating); // e.g. 2180, "good"
});
```

Two numbers will disagree, and that is expected: lab tools run one visitor on a quiet server, while field data reflects real devices, networks, and a busy origin. [Core Web Vitals: lab vs field](https://www.evaluat.com/blog/core-web-vitals-lab-vs-field) explains which to trust for what. The dimension a single lab run cannot reproduce is concurrency, and it is the one that bites in production, because TTFB swells as load rises. Re-running the page in a real browser at realistic load is how you catch an LCP that is fine for one user and poor at peak; that is the subject of [measuring Core Web Vitals at load](https://www.evaluat.com/blog/core-web-vitals-load-testing). To stop a fix from quietly regressing later, wire the check into CI as a budget, covered in [performance regression testing](https://www.evaluat.com/blog/performance-regression-testing).

## Common mistakes when improving LCP

The same handful of errors waste most LCP effort. Each has a one-line fix.

- **Optimizing the wrong element.** Swapping the hero does nothing if the LCP node is a heading. Confirm the element in DevTools before you touch it.
- **Lazy-loading the LCP image.** `loading="lazy"` on the element that should paint first guarantees a late start. Lazy-load below the fold, never the LCP.
- **Compressing the image before fixing the load delay.** A smaller file that still starts loading two seconds in is still slow. Fix the start time first, the size second.
- **Preloading a second large image.** A preload that competes with the real LCP image for bandwidth slows the one that matters. Preload the LCP image only, and only when it is discovered late.
- **Testing one idle user in the lab.** A 1.8-second Lighthouse score says little about the 4-second LCP your users get at peak. Lab has no concurrency.
- **Reporting the average.** The mean flatters you by hiding the slow quarter. Track and gate on the 75th percentile.

## Where Evaluat fits

Most of the steps above you can do with free tools: DevTools to find the element and split the phases, [Evaluat Pulse](https://www.evaluat.com/tools/website-speed-test) for a quick single-page reading in a real browser, Lighthouse or DebugBear for a lab audit. Use them; they are the right tools for a single page on a quiet server.

[Evaluat](https://www.evaluat.com/product/performance-testing) is built for the part those miss: what your LCP does when real traffic arrives. Each virtual user runs in its own isolated browser, so the LCP it records is the browser’s native value, captured while hundreds of users hit the site at once. When LCP crosses 2.5 seconds at peak, the per-session network log shows exactly when the image was requested and how long it took, and the session video shows the moment it painted, so a late start and a slow download read as the two different problems they are. [How it works](https://www.evaluat.com/how-it-works) covers the architecture.

One honest boundary: if you are load-testing a pure API with no page to render, a protocol tool like [k6](https://www.evaluat.com/vs/k6) is the lighter fit. Evaluat earns its place when the question is what a controlled real browser records under selected load conditions, not whether a server returns 200s. Field data remains the source for the full customer population.

Improving LCP is not a single trick. Find the element, split it into four phases, and fix the earliest, largest one first, which is usually the load delay and not the image. Then prove the fix held in the field, at the 75th percentile, under the load your users actually bring.

Test in real browsers. Debug in real sessions. [Book a demo](https://www.evaluat.com/demo?intent=performance).

![Ahmad Farzan, Founder at Evaluat](https://www.evaluat.com/authors/ahmad-farzan.jpg)

About the author

[Ahmad Farzan](https://www.evaluat.com/authors/ahmad-farzan) · Founder at Evaluat

Founder of Evaluat. Has spent years building and load-testing Adobe Commerce and Magento storefronts, and built Evaluat to test sites the way real browsers actually hit them.

[Ahmad Farzan on LinkedIn](https://www.linkedin.com/in/ahmadfarzan/) [Ahmad Farzan on GitHub](https://github.com/farzanahmad)

[More from Ahmad →](https://www.evaluat.com/authors/ahmad-farzan)

Common questions

## FAQ

### What is the fastest way to improve LCP?

Remove the resource load delay on the LCP image. Reference it directly in the initial HTML, never put loading="lazy" on it, and add fetchpriority="high". On most pages the LCP image starts loading far too late, so this single change often does more than any amount of image compression, and it takes minutes.

### Why did my LCP not improve after I optimized the image?

Because the download was probably never the bottleneck. web.dev reports that most origins with poor LCP spend less than 10% of their LCP time downloading the image; the time is lost earlier, in a slow first byte or a late start on the image request. Split your LCP into its four phases and fix the largest one, which is usually not the download.

### What is a good LCP score to aim for?

Good is 2.5 seconds or less at the 75th percentile of real page loads; above 4 seconds is poor. The 75th percentile matters because a fast average can still hide a slow quarter of visits, usually on weaker devices or at peak traffic, and that quarter is where users give up.

### Does preloading the image improve LCP?

It can, when the image is discovered late. A tells the browser to fetch it sooner, which cuts the load delay. But fetchpriority="high" on the image element is usually simpler and enough. Preload the LCP image only, never a second large image, or you will slow the real one down.

### Can a CDN or caching improve LCP?

Yes, on two phases. Caching the HTML and serving it from an edge close to your users cuts Time to First Byte, the first and often largest phase of LCP. Serving the LCP image from the same CDN shortens its download. Both help, and TTFB gains matter most because nothing downstream can start until the first byte arrives.

### Does prerendering improve LCP?

Dramatically, on the prerendered navigation. The Speculation Rules API lets Chrome render a likely next page in the background, so when the user clicks it appears almost instantly and LCP is effectively zero. Monrif combined prerendering with back/forward cache and cut LCP by up to 17.9% (web.dev, 2025). It helps repeat and onward navigations, not the very first load.

### How do I confirm LCP improved for real users?

Lab tools confirm a fix in principle; field data confirms it in practice. Read your LCP at the 75th percentile from the Chrome User Experience Report or your own real user monitoring, not as an average, and measure it under realistic concurrency, because LCP rises under load as the server queues requests.

Related reading

## More from the blog

[![The four phases of Largest Contentful Paint shown as a timeline from navigation start to LCP: Time to First Byte about 40 percent, resource load delay under 10 percent, resource load duration about 40 percent, and element render delay under 10 percent, with a good LCP at 2.5 seconds or less.](https://www.evaluat.com/blog/largest-contentful-paint-cover.svg)](https://www.evaluat.com/blog/largest-contentful-paint)

### [Largest Contentful Paint (LCP), explained for engineers](https://www.evaluat.com/blog/largest-contentful-paint)

[Your Largest Contentful Paint is the moment the biggest thing on the page, usually the hero image, finishes rendering, and Google treats it as a Core Web Vital. This guide explains what counts as the LCP element, the four phases LCP breaks into, why your lab and field numbers disagree, and how to fix and measure it under real load.](https://www.evaluat.com/blog/largest-contentful-paint)

[Ahmad Farzan · 7 May 2026](https://www.evaluat.com/blog/largest-contentful-paint)

[![Two Interaction to Next Paint timelines compared. Before optimization a long task creates a large input delay and INP is 520 milliseconds, rated poor. After breaking up the task and yielding to the main thread the input delay nearly disappears and INP drops to 180 milliseconds, under the 200 millisecond good threshold.](https://www.evaluat.com/blog/how-to-improve-inp-cover.svg)](https://www.evaluat.com/blog/how-to-improve-inp)

### [How to improve INP: a step-by-step optimization guide](https://www.evaluat.com/blog/how-to-improve-inp)

[Most teams trying to improve INP optimize the click handler, and most of the time the handler is not the problem. A long task was already running on the main thread when the user clicked, so the interaction had to wait. This guide is the practical playbook: the fixes in priority order, the code for each, the modern API most sites have not adopted yet, and how to confirm the number actually moved for real users under load.](https://www.evaluat.com/blog/how-to-improve-inp)

[Ahmad Farzan · 18 June 2026](https://www.evaluat.com/blog/how-to-improve-inp)

[![Core Web Vitals at load: a page holds a good 2.1 second Largest Contentful Paint for one user, but as concurrent virtual users rise, LCP climbs and crosses the 2.5 second good threshold, ending near 3.4 seconds at 500 users.](https://www.evaluat.com/blog/core-web-vitals-load-testing-cover.svg)](https://www.evaluat.com/blog/core-web-vitals-load-testing)

### [Core Web Vitals at load, explained](https://www.evaluat.com/blog/core-web-vitals-load-testing)

[A page can score green in a single-user Lighthouse run and still ship a red Largest Contentful Paint the moment real traffic arrives. Core Web Vitals change under load: the server slows, time to first byte grows, and interactions wait on a busy backend. This guide explains why each Vital moves under load, and how to measure them at concurrency.](https://www.evaluat.com/blog/core-web-vitals-load-testing)

[Ahmad Farzan · 1 June 2026](https://www.evaluat.com/blog/core-web-vitals-load-testing)

See it on your site

Test in real browsers.\
Debug in real sessions.

## Start with one real-browser page load.

Pulse loads a public URL once, returns LCP and CLS plus FCP and TTFB, grades the page, and keeps a video on a shareable link.

[Run a free speed test](https://www.evaluat.com/tools/website-speed-test) [Book a demo](https://www.evaluat.com/demo?intent=performance)
