Article No. 53

Largest Contentful Paint (LCP): Causes and Fixes

Abstract

Largest Contentful Paint measures how long it takes for the largest visible element in the viewport, usually a hero image, a large block of text, or a background image, to...

On this page

Largest Contentful Paint measures how long it takes for the largest visible element in the viewport, usually a hero image, a large block of text, or a background image, to finish rendering after a page starts loading. It’s recorded the moment that element is fully painted on screen, and it’s the metric most people mean when they talk informally about “page speed.”

This guide assumes you already know what Core Web Vitals are as a category. If you need that context first, or you’re not sure LCP is actually your problem, start with the Core Web Vitals overview instead. This post goes deep on LCP specifically: what causes it to be slow, and how to fix each cause.

The threshold

Per web.dev’s Core Web Vitals documentation, LCP is scored at the 75th percentile of real-user page loads, meaning at least 75% of visits to a URL need to hit the “good” threshold for that URL to be classified as good:

Rating LCP time
Good ≤2.5 seconds
Needs improvement 2.5–4.0 seconds
Poor >4.0 seconds

The four phases of LCP

The single most useful thing you can do before touching any fix is figure out which part of the LCP timeline is actually slow. Most generic advice skips straight to “compress your images,” which is the right fix maybe half the time and a waste of effort the other half. Per web.dev’s LCP breakdown, the metric is made up of four phases:

  1. Time to First Byte (TTFB). The time from navigation start until the browser receives the first byte of the HTML response.
  2. Resource load delay. The gap between TTFB and when the browser actually starts fetching the LCP resource (usually an image). If the LCP element doesn’t need a separate resource, like a text node using a system font, this is zero.
  3. Resource load duration. How long it takes to download the LCP resource once the browser starts fetching it. Also zero if there’s no resource to load.
  4. Element render delay. The gap between the resource finishing its download and the element actually being painted on screen.

web.dev gives rough proportional guidelines for how these four phases should split up a healthy LCP: TTFB around 40%, resource load delay under 10%, resource load duration around 40%, and element render delay under 10%. Two things matter about how to use that guidance. First, web.dev explicitly frames these as loose guidelines, not strict rules, and specifically cautions against converting the percentages into fixed millisecond targets, since the right absolute number depends on your total LCP budget and your specific page. Second, if you do the arithmetic on a 2.5-second (2,500ms) budget, 40% works out to roughly 1,000ms, not 800ms, so if you’ve seen a “TTFB budget” figure of 800ms elsewhere labeled as 40% of the budget, the math doesn’t hold (800 / 2,500 is 32%, not 40%). Use the proportions as a rough diagnostic sense of where time should go, not as hardcoded per-phase deadlines.

In practice, resource load delay is the phase most sites waste the most time in. It’s common to see a page where the browser doesn’t start requesting the actual LCP image until well over a second after TTFB, because the image reference is buried in JavaScript, lazy-loaded by default, or simply discovered late by the browser’s preload scanner.

Diagnosing which phase is slow

Two tools do this well:

  • PageSpeed Insights. Run your URL, and the Lighthouse report will break down LCP into its component phases with timing for each, both for mobile and desktop.
  • Chrome DevTools Performance panel. Record a page load, find the LCP marker in the timeline, and you can see exactly what the browser was doing (or waiting on) during each phase.

Once you know which phase is the bottleneck, the fix list narrows considerably.

The most common misconception: image compression usually isn’t the bottleneck

Most generic LCP advice jumps straight to “compress your hero image,” which feels intuitive but is often the wrong first move. Per web.dev’s analysis of real-world LCP data, the majority of origins with poor LCP spend less than 10% of their total LCP time actually downloading the LCP image. The median site with poor LCP spends roughly four times longer waiting to start the download (resource load delay, around 1,290ms in the data web.dev cites) than it spends on the download itself (around 350ms). Put differently: over half of a typical 2.5-second budget can be gone before the browser even requests the image.

This is exactly why diagnosing the phase before fixing anything matters. If your bottleneck is resource load delay, compressing the image harder will shave a few hundred milliseconds off a phase that was only ever 350ms to begin with, while the 1,290ms delay phase goes untouched. The fixes below for resource load delay (preload, fetchpriority, removing render-blocking resources ahead of the LCP element) address the phase where the time actually goes.

Fixing a slow TTFB

TTFB problems are server-side, not front-end. Common causes and fixes:

  • Slow hosting or an overloaded server. Upgrading hosting tier or moving to infrastructure closer to your users geographically both help directly.
  • No caching layer. Server-side caching (page caching, object caching, or a CDN edge cache) means most requests never hit your origin server or database at all.
  • Redirect chains before the HTML even loads. Each redirect hop adds a full round trip before TTFB starts counting from the final URL. Flatten redirect chains to a single hop wherever possible.
  • A CDN with poor edge coverage for your audience’s geography. If most of your traffic is in one region and your CDN’s nearest edge node is far away, TTFB suffers regardless of how fast your origin server is.
  • Unoptimized database queries or an application layer doing avoidable work per request. On dynamic sites (WordPress, most CMS platforms, custom applications), a slow TTFB is frequently a slow database query or an unnecessary API call happening synchronously before the HTML can be returned. Query profiling tools built into most frameworks, or database-specific slow-query logs, will usually surface this quickly.
  • No static generation or server-side rendering where it would help. If your content doesn’t change per request, generating it once ahead of time (static site generation) or caching the rendered HTML aggressively removes the per-request computation entirely, which is a larger win than tuning a slow query.

Because TTFB is measured from navigation start (which includes DNS lookup and connection setup, not just server processing time), a slow DNS provider or a missing HTTP/2 or HTTP/3 connection reuse can also inflate it before your server ever gets involved. Worth ruling out before assuming the problem is purely server-side.

Fixing render-blocking resources

If TTFB is fine but resource load delay is high, the browser is being blocked from discovering or fetching the LCP resource promptly. Common fixes:

  • Preload the LCP resource. A <link rel="preload"> tag for the hero image or critical font tells the browser to start fetching it immediately, rather than waiting to discover it while parsing the rest of the document.
  • Use fetchpriority="high" on the LCP element. Per web.dev’s Fetch Priority documentation, this attribute lets you explicitly signal that a resource matters more than the browser would otherwise assume, which can move it earlier in the fetch queue even without a preload tag. The two techniques can be combined: if a preload happens late in an otherwise busy head section, a fetchpriority="high" hint can still pull the fetch earlier.
  • Remove render-blocking CSS and JS ahead of the LCP element. Anything the browser must download and parse before it can start laying out the page pushes the render delay phase out. Inline critical CSS and defer non-critical stylesheets and scripts.
  • Never lazy-load the LCP element. Applying loading="lazy" to a hero image tells the browser to deliberately deprioritize it, which is the opposite of what you want for the single element the entire metric is measuring.
  • Extract and inline critical CSS. The CSS needed to render above-the-fold content, including the LCP element, can be inlined directly in the document <head> so the browser doesn’t need a separate round trip to fetch and parse a full stylesheet before it can paint anything. The rest of the stylesheet can load asynchronously.
  • Watch for long dependency chains. If the LCP image is only discoverable after the browser downloads and executes a JavaScript bundle (a common pattern in client-rendered image galleries or hero components built in a framework), that’s an entire extra round trip added to resource load delay. Referencing the image directly in the initial HTML, rather than injecting it via JavaScript after the fact, removes that dependency chain.

Mobile and desktop LCP scores frequently diverge, sometimes significantly, because mobile devices combine slower CPUs with less reliable network conditions, which extends both the resource load delay and render delay phases relative to desktop. If you’re only checking desktop lab data, you can miss a mobile-specific bottleneck entirely. Check both device types, since Search Console and CrUX report them separately.

Fixes specific to an image LCP element

If your LCP element is an image, most of what actually moves the needle is format and compression choice, which is genuinely a different topic with its own tradeoffs. Rather than duplicate that here, see the Image Optimization: Formats, Compression, and Lazy-Loading Done Right guide for the format decision framework and compression guidance. What belongs here is the LCP-specific piece: make sure the image is discoverable early (no lazy-loading, preloaded or fetchpriority="high"), and sized correctly for the viewport it’s actually rendering at, since serving an oversized image adds unnecessary download time regardless of compression quality.

Fixes specific to a text or font LCP element

When the LCP element is a large text block rather than an image, the render delay phase is usually dominated by font loading behavior:

  • Preload critical web fonts with <link rel="preload" as="font"> so the browser starts fetching them without waiting to discover them in the CSS.
  • Set font-display: swap or optional so text isn’t held invisible while a custom font downloads. This trades a brief flash of a fallback font for a faster paint, which is almost always the right tradeoff for LCP.
  • Self-host fonts instead of loading them from a third-party origin where possible, to avoid an extra DNS lookup and connection setup before the font can even start downloading.

Third-party scripts and LCP

Third-party scripts (analytics tags, ad scripts, chat widgets, and similar) can hurt LCP indirectly if they’re render-blocking or if they compete for bandwidth and main-thread time with the LCP resource during the critical loading window. The bigger and more direct impact of third-party scripts is usually on INP, since they tend to cause long tasks that block interaction response, which is covered in the Interaction to Next Paint (INP): Causes and Fixes guide. For LCP specifically, the practical fix is loading third-party scripts asynchronously or deferring them so they don’t compete with your LCP resource for early bandwidth.

Testing and monitoring

Lab data from PageSpeed Insights or a local Lighthouse run is useful for validating a specific fix before you ship it, but it’s a simulated single test run, not what your actual visitors are experiencing. Once a fix is live, confirm it worked using field data: Search Console’s Core Web Vitals report or the CrUX API, both reflecting real user page loads over a rolling 28-day window. Give a change at least a few days to show up in field data before concluding it did or didn’t work, since CrUX data updates on a delay and needs enough real traffic to produce a stable reading.

Checklist

  • Confirm LCP is actually your problem before optimizing (see the Core Web Vitals overview for how to check).
  • Identify which of the four phases is the bottleneck using PageSpeed Insights or DevTools.
  • If TTFB is slow: check hosting, caching, CDN coverage, and redirect chains.
  • If resource load delay is high: preload the LCP resource, add fetchpriority="high", remove render-blocking CSS/JS ahead of it.
  • Never lazy-load the LCP element.
  • If the LCP element is an image, hand off to the image optimization guide for format and compression specifics.
  • If the LCP element is text, fix font loading: preload, font-display: swap, self-host where practical.
  • Defer or async-load third-party scripts so they don’t compete with the LCP resource.
  • Validate fixes with field data (Search Console or CrUX), not just a single lab test.
Call Now Button