TL;DR / LCP measures the time until the largest visible content element (image, video poster, or text block) finishes rendering in the viewport, serving as the primary Core Web Vital for perceived load speed.
How It Works
Page Load Timeline
┌────────┐ ┌────────┐ ┌────────┐
│ TTFB │─────────→│ FCP │─────────→│ LCP │
└────────┘ └────────┘ └────────┘
LCP Candidates:
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐
│ <img> │ │ <video> │ │ bg-image │ │ text block │
└─────────┘ └─────────┘ └──────────┘ └────────────┘
Largest by viewport area -> final LCP
Reported until user interaction
Largest Contentful Paint is the Core Web Vital for loading performance, representing the moment the user perceives the page as primarily loaded. It replaced the less reliable First Meaningful Paint (FMP) heuristic. LCP measures when the largest content element in the viewport finishes rendering -- not when it starts loading, not when the DOM is ready, but when pixels are actually painted to the screen.
LCP candidate elements are: <img> elements, <image> inside <svg>, <video> elements (the poster image or first frame), elements with background-image via CSS, and block-level elements containing text nodes. The browser evaluates candidates by their viewport area (not their natural/intrinsic size). An image displayed at 300x200 CSS pixels in the viewport has an area of 60,000, regardless of whether the source image is 3000x2000.
The browser reports LCP entries progressively. As the page renders, each new largest element triggers a new LargestContentfulPaint entry. A small text heading renders first (LCP candidate 1), then a hero image loads and renders (LCP candidate 2, larger area). The final LCP value is the last entry reported before the first user interaction (click, tap, scroll, or keypress). After user interaction, no more LCP entries fire -- the assumption is that post-interaction content changes are user-initiated, not load-related.
Google's thresholds: Good is under 2.5 seconds, Needs Improvement is 2.5-4.0 seconds, Poor is above 4.0 seconds. Measured at the 75th percentile of page loads in CrUX.
LCP time decomposes into four sub-parts, each an optimization target. Time to First Byte (TTFB) is the server response time -- affected by server processing, CDN caching, and network latency. Resource load delay is the time between TTFB and when the LCP resource starts downloading -- caused by render-blocking CSS/JS, resource discovery delays, and network contention. Resource load duration is the download time of the LCP resource itself. Element render delay is the time between download completion and actual rendering -- caused by render-blocking resources, main-thread work, and compositing delays.
Preloading the LCP resource is the highest-impact optimization. If the LCP element is an image referenced in CSS or loaded by JavaScript (not directly in HTML), the browser cannot discover it until CSS is parsed or JS executes. Add <link rel="preload" as="image" href="hero.webp"> in the <head> to start the download immediately with the HTML parse. For responsive images, use imagesrcset and imagesizes on the preload link.
fetchpriority="high" on the LCP image element tells the browser to prioritize this resource above other images. By default, images start at low priority until the browser determines they are in the viewport. The fetchpriority hint skips this detection delay.
Avoid lazy-loading the LCP element. loading="lazy" defers loading until the element approaches the viewport, which is counterproductive for above-the-fold content. The LCP image should load eagerly and as early as possible.
Server-Side Rendering (SSR) and static generation improve LCP by delivering the LCP element's HTML in the initial response rather than requiring JavaScript to render it. A client-side rendered React app must download JS, parse it, execute it, fetch data, then render -- each step adds latency before the LCP element even exists in the DOM.
Image optimization directly reduces resource load duration. Use modern formats (WebP, AVIF) for smaller file sizes. Serve appropriately sized images via srcset -- do not serve a 2000px image for a 400px viewport slot. Use a CDN with edge caching to minimize download latency.
Render-blocking CSS delays LCP because the browser will not render any content until CSSOM construction is complete. Inline critical CSS for above-the-fold content in the HTML response. Load non-critical CSS asynchronously via <link rel="preload" as="style"> with an onload handler that switches it to a stylesheet.
Gotchas
- LCP stops reporting after user interaction -- if the largest element loads after the user scrolls or clicks, it is not captured; for SPAs, route changes do not reset LCP measurement
- CSS background images count, but only on block-level elements -- a
background-imageon a<span>is not an LCP candidate; the element must generate a block-level box - Invisible or zero-opacity elements are excluded --
opacity: 0,visibility: hidden, or zero-dimension elements are not LCP candidates; elements that animate from invisible to visible report LCP at the time they become visible - LCP measures render time, not load time -- an image can finish downloading but not render because a render-blocking stylesheet has not loaded yet; the LCP timestamp is when pixels actually paint
- Third-party scripts that inject above-the-fold content affect LCP -- A/B testing scripts that rewrite hero content or inject banners delay the real LCP element's rendering