Complete Guide to Largest Contentful Paint (LCP)
Largest Contentful Paint (LCP) is one of Google's three Core Web Vitals metrics. It measures how long it takes for the largest content element in the viewport to become visible. A good LCP score — 2.5 seconds or less — signals to both Google and your users that your page loads quickly enough to be useful.
This guide covers everything developers need to know about LCP: what it actually measures under the hood, why it's the most impactful Core Web Vital for SEO, the most common causes of poor scores, and step-by-step optimization techniques with real code examples. Whether you're working with Next.js, WordPress, or vanilla HTML, you'll find actionable fixes here.
What is Largest Contentful Paint?
LCP reports the render time of the largest image, text block, or video visible within the viewport, relative to when the page first started loading. The browser continuously updates the LCP candidate as the page loads — earlier, smaller elements may initially be the LCP, but as larger elements render, the browser reassigns the LCP element.
The metric was introduced by Google in 2020 as a replacement for older loading metrics like Load and DOMContentLoaded, which don't accurately reflect what users actually see. Unlike those timing-based metrics, LCP correlates directly with the user's perception of "the page has loaded."
Which elements can be the LCP element?
The LCP API considers these element types as candidates:
<img>elements (including those inside<picture>)<image>elements inside an<svg><video>elements (the poster image or first frame)- Elements with a
background-imageloaded viaurl() - Block-level elements containing text nodes or inline-level text elements
Notably, <canvas> elements and animated GIF/video playback frames are not LCP candidates. The metric intentionally focuses on content that is most meaningful to users — the hero image, the main heading, or the primary content block.
How is LCP measured?
The browser uses the Largest Contentful Paint API to report LCP entries. The measurement process works as follows:
- The browser starts timing from the initial navigation
- As each element renders, the browser checks if it's the largest visible element
- Each time a new largest element appears, a new LCP entry is dispatched
- When the user first interacts (click, tap, keypress, scroll), the browser stops reporting new LCP entries
- The final LCP entry is the one reported
// Measure LCP using the web-vitals library
import {onLCP} from 'web-vitals';
onLCP(({value, entries}) => {
console.log('LCP:', value, 'ms');
// Log the LCP element for debugging
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP resource URL:', lastEntry.url);
});
LCP thresholds
Google categorizes LCP scores into three buckets:
Google evaluates LCP at the 75th percentile of page loads. This means that 75% of your page visits need to have an LCP at or below 2.5 seconds for the page to be rated "Good" in the Chrome User Experience Report (CrUX) and in Google Search Console.
Why LCP matters for SEO and UX
LCP is arguably the most impactful Core Web Vital for two reasons:
1. It's the metric users "feel" most directly. While CLS (layout shift) causes frustration and INP (interaction delay) blocks actions, LCP determines whether users perceive your page as "fast" or "slow." Research from Google shows that users form a judgment about page speed within the first 2-3 seconds — exactly the window LCP captures.
2. It's a direct Google ranking signal. Since June 2021, Core Web Vitals (including LCP) have been an official ranking factor. In the December 2025 core update, Google increased the weight of user experience signals, making LCP optimization even more impactful for search visibility.
Beyond SEO, the business case is clear. A 100ms improvement in LCP has been correlated with a 1.11% increase in session-based conversions, according to research by Deloitte. For e-commerce sites, every millisecond of LCP improvement translates directly to revenue.
Common causes of poor LCP
Poor LCP scores almost always come down to four root causes. Understanding which applies to your site is the first step to fixing it.
1. Slow server response time (TTFB)
Before the browser can render anything, it needs to receive the first byte of HTML from the server. A high Time to First Byte (TTFB) delays everything downstream, including LCP. Common culprits include: unoptimized database queries, no server-side caching, geographic distance between user and server (no CDN), and expensive server-side rendering without streaming.
2. Render-blocking resources
Stylesheets and synchronous JavaScript in the <head> block the browser from rendering any content until they're fully downloaded and parsed. A single 200KB CSS bundle from a CDN having a bad day can push your LCP past 4 seconds, even if the rest of your page is well-optimized.
3. Unoptimized images
When the LCP element is an image — which it is on approximately 72% of web pages — the image's loading performance directly determines LCP. Large file sizes, missing width/height attributes (causing layout recalculation), lack of responsive images, and using legacy formats (JPEG/PNG instead of WebP/AVIF) are all common issues.
4. Client-side rendering
Single-page applications that rely entirely on client-side rendering face an inherent LCP challenge. The browser must: download the HTML shell, download the JavaScript bundle, parse and execute the JavaScript, fetch data from APIs, and then render the content. Each step adds latency before the LCP element can appear.
Step-by-step LCP optimization
Follow these steps in order — they're arranged from highest impact to most granular. Most sites will see significant improvement from the first three steps alone.
Identify your LCP element
Before optimizing, you need to know exactly which element is the LCP element. Open Chrome DevTools, go to the Performance panel, record a page load, and look for the "LCP" marker in the timeline. Alternatively, use the Web Vitals extension or the web-vitals library to log the element programmatically.
// Quick LCP debugging snippet — paste in console
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.element);
console.log('LCP time:', entry.startTime, 'ms');
console.log('LCP size:', entry.size);
// Highlight the element visually
entry.element?.style.setProperty('outline', '3px solid red');
}
}).observe({type: 'largest-contentful-paint', buffered: true});
Optimize server response time
Target a TTFB under 800ms. The most impactful changes are: using a CDN (Cloudflare, Fastly, Vercel Edge) to serve content from nodes close to your users, implementing HTTP caching headers (Cache-Control, stale-while-revalidate), and using streaming SSR so the browser starts receiving HTML before the server finishes rendering the full page.
# Optimal caching headers for static pages
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
# For dynamic pages with personalization
Cache-Control: private, max-age=0, must-revalidate
Vary: Cookie
Eliminate render-blocking resources
Inline critical CSS for above-the-fold content directly in the <head>, then load the rest asynchronously. For JavaScript, use defer or async attributes. Move third-party scripts below the fold or load them on interaction.
<head>
<!-- Inline critical CSS -->
<style>
/* Only above-the-fold styles */
body { font-family: system-ui; margin: 0; }
.hero { min-height: 60vh; display: grid; place-items: center; }
</style>
<!-- Async load full stylesheet -->
<link rel="preload" href="/styles/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
<!-- Defer non-critical JS -->
<script src="/app.js" defer></script>
</head>
Optimize and preload the LCP image
If your LCP element is an image: convert to WebP or AVIF (40-60% smaller than JPEG), use srcset for responsive sizes, always set width and height, and add a <link rel="preload"> to start the download immediately. Never lazy-load the LCP image.
<!-- Preload the LCP image in <head> -->
<link rel="preload" as="image" href="/hero.webp"
fetchpriority="high"
imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
imagesizes="(max-width: 768px) 100vw, 50vw">
<!-- The image element —>
<img src="/hero-800.webp"
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
width="1200" height="630"
alt="Dashboard showing Core Web Vitals metrics"
fetchpriority="high"
decoding="async">
Preconnect to required origins
If your LCP resource is hosted on a different domain (CDN, image service, font provider), add preconnect hints to start the DNS lookup, TCP connection, and TLS handshake early. Each preconnect can save 100-300ms.
<!-- Preconnect to your CDN and font provider -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- DNS-prefetch as fallback for browsers without preconnect -->
<link rel="dns-prefetch" href="https://cdn.example.com">
Advanced LCP optimization techniques
Priority Hints
The fetchpriority attribute (supported since Chrome 101) lets you signal to the browser which resources are most important. Set fetchpriority="high" on the LCP image to override the browser's default resource prioritization. Conversely, set fetchpriority="low" on below-the-fold images to free up bandwidth.
<!-- High priority for LCP image -->
<img src="/hero.webp" fetchpriority="high" ...>
<!-- Low priority for non-critical images -->
<img src="/decoration.webp" fetchpriority="low" loading="lazy" ...>
Streaming SSR
Modern frameworks like Next.js 14+, Remix, and SolidStart support streaming server-side rendering. Instead of waiting for the entire page to render on the server, the browser starts receiving and rendering HTML as it's generated. This can cut LCP by 30-50% on pages with heavy server-side data fetching.
Font-related LCP optimization
When the LCP element is text, web font loading can significantly delay LCP. The browser won't render text until the font is available (or the fallback timeout triggers). To optimize: use font-display: swap to show fallback text immediately, preload critical fonts, and use size-adjust in @font-face to reduce layout shift when the web font loads.
/* Optimal font loading with size-adjusted fallback */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'MyFont-fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 95%;
descent-override: 22%;
line-gap-override: 0%;
}
Measuring LCP: Lab vs. Field
Understanding the difference between lab and field data is critical for LCP optimization. Lab tools (Lighthouse, PageSpeed Insights, WebPageTest) run on controlled hardware with simulated network conditions — useful for debugging but not representative of real user experience.
Field data from CrUX (accessible via PageSpeed Insights, Search Console, or the CrUX API) reflects actual user measurements. Google uses field data only for ranking decisions. Your Lighthouse score of 98 means nothing if real users on 3G connections see LCP of 5+ seconds.
Recommended measurement workflow:
- Check CrUX data in PageSpeed Insights to see field-level LCP at the 75th percentile
- Use the
web-vitalslibrary to collect real user data and segment by device type, geography, and connection speed - Use Lighthouse and DevTools Performance panel for debugging specific issues
- Set up continuous monitoring via a RUM (Real User Monitoring) tool to catch regressions early
Framework-specific LCP fixes
Each framework has unique LCP patterns and optimization paths. Here are the key considerations:
Next.js: Use the <Image> component with priority prop for LCP images. Enable ISR (Incremental Static Regeneration) for content-heavy pages. See our detailed guide on fixing LCP in Next.js.
WordPress: Use a caching plugin (WP Rocket, W3 Total Cache) with page caching. Implement a CDN. Use loading="lazy" on all images except the LCP image. Consider converting to WebP via a plugin like ShortPixel.
React SPA: Implement server-side rendering or static generation. If pure CSR is required, minimize bundle size with code splitting and tree shaking. Preload the critical data for the initial view.
Astro: Ships zero JavaScript by default — already a huge LCP advantage. Focus on image optimization and font loading. Use Astro's built-in <Image> component for automatic WebP/AVIF conversion.
Frequently asked questions
A good LCP score is 2.5 seconds or less. Scores between 2.5s and 4.0s need improvement, and anything above 4.0s is considered poor. Google uses the 75th percentile of page loads to assess LCP, meaning 75% of your visits need to be below 2.5s to get a "Good" rating in CrUX.
LCP considers these element types: <img> elements, <image> elements inside SVGs, <video> elements (the poster image), elements with CSS background-image loaded via url(), and block-level elements containing text nodes. Approximately 72% of LCP elements on the web are images.
FCP measures when any content first appears — a loading spinner, navigation text, or even a background color change. LCP measures when the largest meaningful content element appears. FCP often fires much earlier than LCP. A page might have FCP at 0.8s (showing the header) but LCP at 3.2s (when the hero image finally loads). Optimizing for LCP is more impactful because it reflects what users actually care about seeing.
No. LCP only considers elements within the initial viewport — the visible area without any scrolling. Elements below the fold are not LCP candidates. This is important for responsive design: the LCP element may differ between mobile and desktop because different amounts of content are visible in the initial viewport on each device size.
Yes, significantly. If your largest content is rendered client-side (e.g., by React, Vue, or Angular), the LCP measurement includes: JavaScript download time, parse and compile time, execution time, API data fetching, and DOM rendering. This is why server-side rendering (SSR) or static generation (SSG) can dramatically improve LCP — the browser receives ready-to-render HTML without waiting for JavaScript.
Lab tools (Lighthouse, DevTools) simulate conditions and are useful for debugging. Field data from the Chrome User Experience Report (CrUX) measures real user LCP. Google uses only field data for ranking. Access field data via PageSpeed Insights, Search Console, or the CrUX API. For continuous monitoring, use the web-vitals library to collect and report field data to your analytics platform.
The best approach combines lab and field data. Use Google PageSpeed Insights for quick field data from CrUX, Chrome DevTools Performance panel for detailed debugging, and the web-vitals JavaScript library for continuous real-user monitoring. For CI/CD integration, Lighthouse CI catches regressions before deployment. See our complete tools directory for 50 recommended tools.
Images are the LCP element on 70-80% of pages. Converting to AVIF format saves 50% over JPEG, responsive images prevent oversized downloads, and fetchpriority=high with preloading starts the download earlier. A comprehensive image optimization strategy typically reduces LCP by 40-60%. See our image optimization guide for step-by-step instructions.
Fix LCP in your framework
Step-by-step optimization guides for every major framework and platform:
Fix LCP in Next.js
Image optimization, priority hints, SSG/ISR, and streaming SSR for fast LCP.
FixFix LCP in React
Code splitting, lazy loading, and server-side rendering for React apps.
FixFix LCP in Vue
Nuxt SSR/SSG, async components, and image optimization in Vue applications.
FixFix LCP in Angular
Change detection, lazy loading modules, and server-side rendering in Angular.
FixFix LCP in WordPress
WebP images, caching plugins, theme optimization, and CDN setup.
FixFix LCP in Shopify
Theme optimization, image CDN, and Liquid template performance.
FixFix LCP in Webflow
Asset optimization, custom code management, and hosting configuration.
FixFix LCP in Squarespace
Template selection, image compression, and third-party script management.
FixFix LCP in Vite
Manual chunks, modulepreload polyfill, vite-imagetools, and hero image preload for Vite-built apps.
FixFix LCP in Gatsby
gatsby-plugin-image, AVIF/WebP, art-direction, and hero preload via gatsby-ssr.js.
FixFix LCP in Eleventy
eleventy-img, fetchpriority, critical CSS via eleventy-plugin-bundle, and edge cache headers.
GuideImage Optimization Best Practices
AVIF/WebP selection, responsive srcset/sizes, fetchpriority, lazy loading, and CDN delivery, framework-agnostic.
ComparisonNext.js vs Remix Performance
Head-to-head LCP, INP, CLS benchmarks for React meta-frameworks.