Largest Contentful Paint: diagnosis and optimization

A page finishes downloading, the spinner stops, and a visitor still stares at a blank hero area waiting for the one thing they came for. That gap between “the page is technically loading” and “the main content is actually on screen” is what Largest Contentful Paint measures. Get it wrong and the visitor feels the wait; get it wrong at scale and Google notices too.

LCP records how quickly the largest visible element inside the viewport renders during page load. Google treats it as one of three Core Web Vitals, alongside Interaction to Next Paint and Cumulative Layout Shift. Those two are separate diagnostics with their own mechanics, and the broader Core Web Vitals framework has its own overview; this guide stays inside LCP and goes deep. The one number to anchor on: a page needs LCP of 2.5 seconds or less, measured at the 75th percentile of real page loads, to earn a “good” rating from Google (per web.dev’s LCP documentation).

What LCP actually measures #

The browser watches the page render and tracks the largest content element it can find in the viewport. The candidate is usually one of a few things:

  • A hero image or product image
  • A video’s poster frame
  • A block of text that occupies the most visual space, often an H1 or a lead paragraph
  • A background image displayed as content
  • An inline image or SVG

As the page loads, the browser measures the rendered size of each candidate and keeps updating which one counts. The final LCP value locks in when the page finishes loading or the user first interacts, whichever comes first.

The thresholds Google applies, per web.dev:

Rating LCP value
Good 2.5 seconds or less
Needs improvement 2.5 to 4.0 seconds
Poor over 4.0 seconds

The critical detail is the 75th percentile. A site whose median LCP is a comfortable 1.5 seconds but whose 75th percentile sits at 5 seconds is rated poor, because the rating reflects the experience of the slowest quarter of real users, not the median or the developer’s test device.

The subparts of LCP #

LCP is not a single thing to tune. web.dev breaks it into sequential subparts, and diagnosis begins with identifying which one dominates. For an image or video LCP element there are four:

Subpart What it measures
Time to First Byte (TTFB) Time from when the load starts until the first byte of the HTML arrives
Resource load delay Gap between TTFB and when the browser starts fetching the LCP resource
Resource load duration Time to download the LCP resource itself
Element render delay Gap between the resource finishing loading and the element rendering

One nuance the raw metric hides: resource load delay and resource load duration only exist when the LCP element needs a resource to load. When the LCP element is text rendered with an available font, there is no image to fetch, so LCP collapses to just two subparts, TTFB and render delay (per web.dev). That distinction changes where content-heavy sites should spend their effort.

The diagnostic question is always which subpart owns the time. An LCP of 4.5 seconds made of 3 seconds TTFB plus 1.5 seconds of everything else is a server problem. The same 4.5 seconds made of 500ms TTFB plus 4 seconds of resource load is an image or script problem. Chrome DevTools’ Performance panel, Lighthouse, and PageSpeed Insights all show this breakdown; real-user monitoring services report it from actual traffic.

TTFB and resource delivery #

TTFB is the foundation, and it is frequently the largest single subpart. It covers server processing (database queries, business logic, template rendering), network latency between user and origin, the TLS handshake on first connections, and DNS resolution. The moves that consistently reduce it are structural rather than cosmetic:

  • A CDN with edges near users, so cached responses come from nearby.
  • Caching the HTML response itself, which cuts the full server-processing window on cacheable pages.
  • Database query optimization, since a page running dozens of unindexed queries per load can shed hundreds of milliseconds with proper indexes.
  • HTTP/2 or HTTP/3 with connection reuse to reduce handshake overhead.

A site holding TTFB consistently under 500ms has effectively removed it as the bottleneck. A site sitting above 1 second has most of its LCP headroom locked in TTFB alone, which is why treating it as untouchable “infrastructure” is a common and costly mistake.

Resource load delay is the discovery problem: the browser knows the HTML arrived but cannot start fetching the LCP resource yet. It shows up when the image is referenced only in CSS (the browser must download and parse CSS first), loaded by JavaScript (must download and execute JS first), or lazy-loaded above the fold (the load waits for the image to enter the viewport, which for a hero image is immediately but the browser does not know that). The fixes:

  • Preload the LCP resource with <link rel="preload" as="image" href="hero.webp"> so the browser fetches it before normal discovery.
  • Mark the LCP image with fetchpriority="high", a 2022 Fetch Priority API addition that raises the resource’s priority relative to others. web.dev documents the Google Flights team cutting LCP by roughly 700ms from this single attribute.
  • Remove lazy-loading from above-the-fold images and reserve it for below-fold content.
  • Reference LCP images directly in HTML with <img> rather than through CSS or JS.

A discipline note web.dev is explicit about: preload and fetchpriority both work by overriding the browser’s own prioritization, and overriding too much breaks it. Preloading every hero variant, preloading fonts that are not used above the fold, or marking several images fetchpriority="high" cancels the signal, because when everything is high priority nothing is. The correct pattern is one preload per page aimed at the real LCP element, with fetchpriority="high" on that element only.

Resource load duration is the bandwidth problem, the actual download time. The levers are size and delivery: convert images to WebP or AVIF for smaller files at equal visual quality, serve responsive sizes with srcset and <picture> so a mobile viewport does not receive a 4000-pixel image, reduce quality aggressively enough to shrink the file without visible degradation, enable Brotli compression on the server or CDN, and use a modern HTTP version. For any image LCP the workflow is the same: identify the element in PageSpeed Insights, check its size and format, optimize the source, and serve it via CDN with compression.

Element render delay: when the pixels arrive but nothing shows #

Render delay is the gap between the LCP resource being fully downloaded and the element appearing on screen. The resource is ready; something is holding the paint. The usual culprits:

  • JavaScript blocking rendering while a large bundle parses and executes.
  • CSS that has not loaded, so the browser has the image but no rule telling it how to display.
  • Web fonts blocking text rendering, which matters most when the LCP element is text.
  • Layout thrashing from repeated style recalculations during load.

The optimizations map directly onto those causes: reduce JS bundle size through code splitting and dynamic imports, defer non-critical JS with defer or async to keep it off the critical path, inline the above-the-fold critical CSS and load the rest asynchronously, and set font-display: swap so text paints immediately in a fallback font and swaps when the web font arrives. For content-heavy sites where the LCP element is text, the font-loading strategy alone can move hundreds of milliseconds, since a slow web font under font-display: block holds the text invisible until it arrives.

Mobile is the real target #

Mobile LCP is consistently slower than desktop on the same site. Mid-range Android CPUs execute JavaScript two to five times slower than a desktop, cellular connections carry more latency, and network quality swings between fast 5G and weak LTE by location. Because Google evaluates the 75th percentile of real users, and that percentile on mobile skews toward slower devices and connections, a page that clocks 1.8 seconds on a developer’s iPhone can land at 4 seconds at the 75th percentile of actual mobile traffic. The practical takeaway is to optimize mobile-first; desktop LCP usually resolves itself once mobile is solid.

How to diagnose it #

The workflow that reliably produces fixable findings runs in five stages:

  1. Start with field data in Search Console’s Core Web Vitals report, which groups URLs by similarity and flags those rated “Poor” or “Needs improvement.”
  2. Move to PageSpeed Insights for representative URLs. PSI runs Lighthouse (lab) and reports Chrome User Experience Report field data; the field data shows what real users experience, the lab data shows what the page can do under ideal conditions.
  3. Identify the LCP element. PSI and Lighthouse name the candidate, and its type (image, text, video) decides which optimizations apply.
  4. Break LCP into subparts. Deciding whether this is a TTFB problem, a resource-load problem, or a render problem tells you where the effort goes.
  5. Ship the change and measure. Field data reflects real improvement over weeks; lab tests give an immediate before-and-after but not real-user variability.

Two recurring traps sit inside this workflow. Optimizing the wrong element wastes a sprint on a hero image that was never the LCP candidate, which is why step 3 exists. And optimizing lab scores while ignoring field data produces a better Lighthouse number while real users still wait; the change only counts if field data moves.

The hidden cost: third-party scripts #

Everything above assumes a site that controls what loads. Most production sites carry tag managers, analytics, ad scripts, consent platforms, chat widgets, A/B tools, and heatmap recorders, all injecting JavaScript that competes with the LCP candidate for bandwidth and main-thread time. The mechanism is direct: a third-party script downloads, parses, and executes on the main thread, and while it runs the browser cannot do the work of rendering the LCP element. A tag manager loading synchronously in the head can delay LCP by hundreds of milliseconds even when the hero image is small and well optimized.

The heaviest offenders tend to be consent management platforms that load eagerly before deferring other scripts, ad-tech header-bidding wrappers that make several network calls before the first ad, and chat widgets that ship 100 to 300KB before the page is even interactive. The diagnostic is to open the Chrome DevTools Performance panel, filter to main-thread activity during the LCP window, and read the flame graph; the blockers are usually obvious. The fixes are to audit which scripts are genuinely needed for first paint (most are not), defer the rest with defer or a load-event handler, and configure tag managers to fire on Window Loaded rather than Page View. The harder part is organizational: these scripts often belong to marketing or compliance rather than engineering, so cutting them requires cross-functional agreement about which are worth the cost.

LCP drifts, so plan for it #

A page tuned to good LCP does not stay there on its own. A single uncompressed hero upload, a font swap that turns a slow web font into the LCP element, or a CMS template change that defers critical CSS can push a good score back into poor. The sites that hold their scores treat LCP the way they treat error rates: monitoring built into the deployment pipeline, regression alerts when scores degrade, and explicit LCP budgets that production code cannot exceed without review. The ones that do not find themselves rediscovering the same problems every quarter, with the ranking cost recurring each time.

FAQ #

Is LCP a ranking factor?
LCP is part of Google’s page experience signals as one of the three Core Web Vitals. It is a real but modest signal that matters most as a tiebreaker between pages of similar quality and relevance, and it does not override content quality.

What LCP score do I need?
2.5 seconds or less at the 75th percentile of real page loads earns a “good” rating. Between 2.5 and 4 seconds is “needs improvement,” and over 4 seconds is “poor.”

Why is my LCP fine in Lighthouse but poor in Search Console?
Lighthouse is a lab test on a controlled device and network; Search Console reports field data from real users, whose slower devices and connections at the 75th percentile produce a worse result. Field data is what Google uses.

My LCP element is text, not an image. What changes?
When the LCP element is text rendered with an available font, there is no resource to fetch, so LCP has only two subparts, TTFB and element render delay. Effort shifts to server response time and font-loading strategy rather than image optimization.

Should I preload every important image?
No. Preload and fetchpriority="high" work by overriding browser prioritization, so applying them broadly cancels the signal. Use one preload per page on the actual LCP element, with fetchpriority="high" on that element only.

Start where the time actually is. Pull the subpart breakdown before touching code, confirm which element is the LCP candidate, and fix the subpart that owns the seconds rather than the one that is easiest to reach. Every millisecond above 2.5 seconds is a millisecond the visitor spent waiting for the content they came for, and closing that gap is the whole point.