Guide · Core Web Vitals

How to Fix LCP on Mobile: A Step-by-Step Walkthrough

Find your real LCP element, work through the four causes in the order they usually bite, and stop making the one mistake that delays the hero image you were trying to speed up.

What LCP actually measures

Largest Contentful Paint is the time from the start of the navigation until the largest image or text block inside the first viewport has finished rendering. Not the whole page, not every element, not "fully loaded": one element, the biggest one a visitor can see without scrolling.

The threshold is 2.5 seconds or less for a good rating. Between 2.5 and 4.0 seconds is "needs improvement", and above 4.0 seconds is poor. Crucially, the number that counts is the 75th percentile of real page loads on mobile, not the result of one test on your laptop. A page that loads in 1.2 seconds for you and 5 seconds for a visitor on a mid-range Android is a failing page, because the visitor is the one Google is measuring.

It helps to break LCP into its four parts, because the fix depends entirely on which part is slow: time to first byte, the delay before the browser starts fetching the element's resource, how long that resource takes to download, and the delay before it is finally rendered. Most fixes are aimed at part two.

INP and CLS, briefly

Core Web Vitals are three metrics, and LCP is only one of them. Interaction to Next Paint measures responsiveness: how long the page takes to visibly react to a tap or click, across the interactions of a visit. Good is 200 milliseconds or less at the 75th percentile. It replaced First Input Delay in March 2024, and it is much harder to game, because it is about long tasks on the main thread rather than about one early input. Cumulative Layout Shift measures how much the page moves under the reader's finger, and good is 0.1 or less.

The three interact, which is why fixing one in isolation sometimes makes another worse. Deferring a stylesheet can improve LCP and cause a flash of unstyled content; removing a shift often means reserving space, which can push the largest element later. Measure all three after every change.

Step one: identify the LCP element before changing anything

You cannot fix LCP without knowing which element is being measured, and it is usually not the element you assume. It is most often the hero image, sometimes a background image, and frequently the h1 or a large paragraph of text when the text renders before the image finishes. On a client-rendered page the LCP element is often something that barely exists in the HTML source.

Two things to understand before you look. First, the element differs between mobile and desktop, because the viewport is narrower, the layout reflows, and art-directed images are swapped by media query. Second, mobile is slower for reasons that have nothing to do with your code: a slower CPU, a colder cache and a higher-latency connection all push the same page over the threshold.

Get the answer from PageSpeed Insights, which names the LCP element in its diagnostics, or read it directly in the browser:

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const last = entries[entries.length - 1];
  console.log(last.element, Math.round(last.startTime), 'ms');
}).observe({ type: 'largest-contentful-paint', buffered: true });

The four causes, in the order they usually bite

1. Slow server response (high TTFB)

Nothing can render before the first byte arrives, so a two-second TTFB hands you a two-second LCP before the browser has done any work. This is the most common cause on dynamic sites and the one most often misdiagnosed as an image problem.

2. Render-blocking CSS and JavaScript

A stylesheet in the head blocks rendering until it downloads and parses. Synchronous scripts block parsing while they execute. Enough of either and the hero image is ready while the page is still not allowed to paint.

3. Unoptimised or un-sized hero images

A 2.4 megabyte JPEG served to a phone that needs a 480 pixel wide image, with no explicit dimensions, so the browser does not know the space to reserve and shifts the layout when it arrives.

4. Client-side rendering

If the hero only appears after a JavaScript bundle downloads, parses and executes, then fetches data, the LCP clock is waiting on the entire chain. Server-render or pre-render the first viewport wherever you can.

Work them in that order. Teams routinely spend a week compressing images while a 1.8 second TTFB sits untouched underneath, and the metric does not move.

Concrete fixes for each cause

Cut the time to first byte

Add a full-page cache so anonymous requests never reach the application, then an object cache for the database queries that remain, then an edge cache or CDN in front. Check for long-running queries on the page's critical path and for a database that is simply too small for the working set. On a self-managed server, TTFB is usually a disk, memory or configuration problem, which is why hosting-level work belongs in the same conversation. If your plan is running out of RAM under normal traffic, no amount of front-end tuning will rescue the metric, and moving to an NVMe KVM plan with predictable resources is a cheaper fix than months of tuning.

Unblock rendering

Inline the CSS needed for the first viewport and load the rest without blocking, using media and onload or a preload plus swap. Add defer to scripts that do not need to run before paint, and async only where order genuinely does not matter. Audit every tag manager entry and third-party script: analytics, chat widgets, heatmaps and A/B tools are frequently the largest remaining render cost, and most of them can wait for an interaction. Self-host your fonts, use font-display: swap, and preload only the one weight used above the fold.

Fix the hero image properly

Preload it so the fetch starts during HTML parsing rather than after layout, serve AVIF or WebP with a fallback, provide a srcset so a phone never downloads a desktop-sized file, and always set explicit width and height or an aspect ratio so the space is reserved and nothing shifts.

Descriptive alt text

Do not lazy-load the LCP image

This is the most common self-inflicted LCP wound. Someone adds loading="lazy" to every image on the site, which is correct for the twenty images below the fold and actively harmful for the one image Google is timing. A lazy image is not requested until the browser has laid out the page and worked out that the image is near the viewport, so you have added a full round trip of delay to the element you most needed to be fast.


Hero


Hero

Fix the LCP image first. Keep lazy loading everywhere else, where it is a genuine win.

Get the first viewport out of JavaScript

Server-render or statically generate the hero, its heading and its supporting copy so they exist in the delivered HTML. If the framework must hydrate, keep the hero out of the hydrated subtree and preload the data it needs. A hero that appears 3 seconds in because it waited on a bundle and an API call is a rendering-architecture decision, not a tuning problem, and it needs to be fixed at that level.

How to test: field data versus lab data

Field data comes from real visitors, aggregated in the Chrome User Experience Report and in the Search Console Core Web Vitals report, expressed as the 75th percentile over a rolling 28-day window and split between mobile and desktop. This is the number that matters, because it is what real devices actually experienced, and it is the number used for any search signal.

Lab data is a single synthetic load, usually Lighthouse inside PageSpeed Insights, on a fast machine with a throttled connection and an empty cache. It cannot tell you what your users experience, but it is far better at telling you why: it names the LCP element, lists blocking resources and shows the waterfall.

They disagree because they are measuring different populations. Field data averages thousands of devices, networks and cache states, including repeat visits where everything is already cached; lab data is one cold load on a simulated phone. Field data also uses percentile logic, so a page passes when 75 per cent of loads are under 2.5 seconds even if a quarter of your visitors wait considerably longer. Read them together: use the lab to find the cause, then confirm in the field that the fix changed the 75th percentile. Expect roughly a 28-day lag before a fix is fully visible, and do not conclude that a change failed on day three.

The honest caveat about Core Web Vitals

Core Web Vitals are a tiebreaker, not a ranking silver bullet. Passing them does not move you above a competitor with stronger content, better internal linking and more of the right links. Failing them does not necessarily cost you the rankings you already hold. Treat them as necessary hygiene, not as a growth strategy, and be sceptical of anyone selling a speed project as a ranking project.

The other half of the caveat matters as much: a fast site with thin content still will not rank. If a page takes three seconds to load and answers the query better than everything above it, it will often win anyway. Fix LCP because slow pages lose visitors regardless of Google, because it improves conversion, and because it removes an easy reason to be judged worse than a competitor. Do not fix it by stripping the page of the content that made it worth visiting, and do not let a metric dictate the design of a page that was converting.

Frequently asked questions about mobile LCP

Why is my LCP good on desktop and poor on mobile?

Because the measured element and the conditions both change. The narrower viewport reflows the layout and can promote a different element to largest, art direction may serve a different image, and the device has a much slower CPU and a higher-latency connection. Always diagnose mobile separately and never assume a desktop score transfers.

Should I preload every image on the page?

No. Preloading tells the browser to fetch something immediately and with high priority, so preloading more than the LCP image competes with the very resource you are trying to accelerate. Preload the hero, and let everything else be discovered normally.

My PageSpeed score is 60 but the field data passes. Which do I fix?

The field data is the one that reflects your visitors and the one used for any evaluation. A low lab score with passing field data usually means the lab is simulating a scenario your real traffic does not experience, often a cold cache. Use the lab for diagnosis, not for the verdict.

Does lazy-loading ever help LCP?

Rarely, and only indirectly. It frees bandwidth for the element that matters by stopping below-the-fold images from competing with it. Applied to the LCP element itself, it always hurts, because it delays the fetch by at least a layout pass.

How long after a fix does the CrUX data change?

The field data is a rolling 28-day window, so a change bleeds in gradually and a full reflection takes about a month. Verify the fix in the lab immediately, then confirm it in the field after four weeks rather than re-testing the server daily.

How much of LCP is my hosting?

More than most front-end guides admit. If time to first byte is over 600 milliseconds, no image or script optimisation will get you under 2.5 seconds, because you have already spent a large share of the budget before the page started arriving.

Want the causes named, not just the score?

We diagnose LCP, INP and CLS on real URLs and hand you a prioritised fix list: the element, the blocking resource, the shift source and the change to make. Server-level causes we can fix ourselves.