Web Performance

LCP: how to optimize Largest Contentful Paint

LCP optimization: how to improve Largest Contentful Paint for Core Web Vitals

LCP is the core loading performance metric in Core Web Vitals. It measures how quickly users see the main content of a page. We cover what drives LCP, why it slows down, and how to get it into the green threshold on real-world projects.

Largest Contentful Paint (LCP) is the Core Web Vitals metric that has influenced Google rankings since 2021. It is not just a technical number — it reflects how quickly users perceive a page as loaded. A poor LCP means visitors see a blank screen or an unloaded hero image and leave before the content appears.

Load timeline: LCP fires the moment the largest viewport element is painted. The goal is to land in the green zone under 2.5 seconds.
According to Google, improving LCP from 'poor' to 'good' reduces the bounce rate by an average of 24%. On e-commerce sites this directly impacts conversion.

What is LCP and why it matters

LCP measures the time from the start of navigation to the moment the largest content element (text or image) becomes visible in the viewport. This is the threshold Google uses to consider loading 'done' from a user experience standpoint.

Unlike FCP (First Contentful Paint), which fires on the first pixel, LCP tracks meaningful content specifically. A page might show a spinner at 0.3 s, but if the hero image appears at 5 s — the LCP is 5 s.

< 2.5 s

Good LCP

Google's green threshold. The target for most pages

2.5–4 s

Needs improvement

Yellow zone — LCP is noticeable to users, optimisation needed

> 4 s

Poor LCP

Red zone — significant damage to UX and rankings

75th

CrUX percentile

Google evaluates LCP at the 75th percentile of real Chrome users

Google uses Chrome User Experience Report (CrUX) data — real measurements from real Chrome users. Your PageSpeed Insights lab score may show green while CrUX shows yellow. Use CrUX field data: that is what Google uses for ranking decisions.

Which elements the browser considers LCP candidates

The browser does not track every element — it watches specific types and selects the largest by area within the viewport at the time loading completes.

<img> elements
Standard images. The most common LCP candidate on most pages. Time = moment the decoded image appears in the viewport.
<image> inside SVG
Images within SVG elements. Treated the same as regular img elements for LCP purposes.
CSS background-image
Background images via CSS. Key nuance: the browser does not start loading a background-image until the CSSOM is built. This is one of the primary sources of LCP delay.
Text blocks
<p>, <h1>–<h6>, <div>, and other elements with text content. If a text block is larger than any image, it becomes the LCP.
<video> with poster
The poster frame of a video element (poster attribute). If a video occupies most of the screen, the poster must load as fast as possible.
Elements inside <iframe> and elements with no visual size (display: none, visibility: hidden, opacity: 0) are not counted as LCP. If your hero banner is hidden before JS initialisation, LCP may be measured against a completely different element.

How LCP time breaks down

Google decomposes LCP into 4 sub-components. Understanding each one is key to correctly diagnosing and prioritising optimisations.

ComponentWhat it measuresTypical share
TTFB (Time to First Byte)Time until the first byte of HTML arrives from the server10–40%
Resource Load DelayGap between TTFB and when the LCP resource starts loading10–30%
Resource Load DurationTime to download the LCP resource itself (image, font)30–60%
Element Render DelayGap between resource load completion and its appearance in viewport0–20%

Rule: attack the component with the largest share. If TTFB accounts for 3 of 5 seconds of LCP — optimising images will have minimal impact. Fix server response time first.

Main causes of slow LCP

90% of poor LCP scores share the same root causes. Diagnosis takes 15 minutes in DevTools or PageSpeed Insights.

Slow TTFB

Server responds in more than 600 ms. Causes: no caching, weak hosting, heavy database queries, no CDN. With TTFB > 800 ms, achieving LCP < 2.5 s is nearly impossible without other optimisations.

Unoptimised images

The LCP image weighs 500 KB+ as JPEG instead of 80 KB as WebP/AVIF. No fetchpriority="high" attribute, no preload. The browser discovers the image late and loads it slowly.

Render-blocking resources

CSS and JS in <head> without async/defer block rendering. Until the browser loads and executes them, the LCP element cannot appear. Every extra script in head adds delay.

Slow font loading

If LCP is a text block and the font takes 2+ seconds to load, the browser shows FOIT (invisible text) or FOUT (system font). The time until the correct text appears equals the LCP.

Lazy load on the LCP element

The loading="lazy" attribute on the main hero image is one of the most common mistakes. The browser deliberately defers loading — a direct hit to LCP.

LCP in client-side rendering

In SPAs (React, Vue, Angular) without SSR, content is generated in the browser via JavaScript. The browser sees empty HTML → loads JS → renders content. The entire chain accumulates into LCP.

How to optimise LCP: a step-by-step plan

LCP optimisation is not a single trick — it is a set of interrelated improvements. Work sequentially, starting with the highest-impact items.

Step 1: Reduce TTFB to 200–400 ms

TTFB is the foundation. If the server is slow, all other optimisations work against that ceiling.

  1. Configure HTTP caching: Cache-Control: max-age=31536000 for static assets, s-maxage for CDN proxies.
  2. Enable compression: Brotli (preferred) or gzip for HTML, CSS, JS. Brotli delivers 15–25% smaller files than gzip.
  3. Connect a CDN: Cloudflare, Fastly, BunnyCDN. For static sites, edge caching reduces TTFB to 20–50 ms.
  4. Optimise the database: indexes on frequently queried fields, query caching via Redis or Memcached.
  5. Use HTTP/2 or HTTP/3: multiple requests over a single connection with no head-of-line blocking.
TTFB target: < 200 ms with server + CDN. If you have Nginx + static HTML + CDN — this is achievable. If you have PHP/Node without caching and live database queries — you need application-level optimisation first.

Step 2: Optimise the LCP image

If the LCP element is an image (hero, product image), this is a high-ROI optimisation tree.

  • Convert to modern formats: WebP (25–35% savings vs JPEG) or AVIF (40–55% vs JPEG). Use <picture> with a fallback.
  • Add fetchpriority="high" to the LCP image — the browser will load it with top priority.
  • Remove loading="lazy" from the LCP element. Lazy load is only appropriate for images below the first viewport.
  • Add <link rel="preload" as="image"> in <head> for the LCP image — the browser starts loading before body is parsed.
  • Specify width and height attributes to prevent CLS. The browser reserves space before the image loads.
  • Configure srcset and sizes for responsive loading: mobile gets a 400 px image instead of a 1600 px one.
The combination of fetchpriority="high" + preload + WebP/AVIF + proper srcset delivers an average 1–2 second LCP improvement on real e-commerce projects.

Step 3: Eliminate render-blocking resources

Every render-blocking resource adds delay to LCP. The goal: zero blocking resources in the critical path.

Resource typeProblemSolution
JavaScript in <head>Browser stops HTML parsing and executes the scriptdefer or async on all non-critical scripts
CSS in <head>Browser renders nothing until all CSS is loadedCritical CSS inline + async loadCSS for non-critical styles
Third-party scriptsAnalytics, chat bots, pixels loading in headMove to body or load with delay (setTimeout 3000)
Fonts without preloadBrowser discovers the font only after parsing CSS<link rel="preload" as="font"> + font-display: swap

Step 4: Optimise fonts (when LCP is text)

When the LCP element is a text block (heading, lead), the problem is often font render delay.

  1. Add font-display: swap or optional to @font-face. The browser shows the system font immediately and swaps in the custom one after loading.
  2. Use <link rel="preload" as="font" type="font/woff2" crossorigin> for critical fonts in <head>.
  3. Limit the character set: use subsetting to load only the needed glyphs (Latin, Cyrillic).
  4. Self-host fonts instead of loading from Google Fonts — eliminates an extra DNS lookup and connection.
  5. Configure size-adjust, ascent-override, descent-override in @font-face to reduce CLS during font swap.

Step 5: SSR/SSG for JavaScript frameworks

If the site runs on React, Vue, or Next.js without SSR — LCP will be high by default: the browser must download, execute JavaScript, and only then render the content.

Server-Side Rendering (SSR)
HTML is generated on the server for each request. The browser receives ready HTML, parses it, and the LCP element appears without waiting for JS. Best option for dynamic content.
Static Site Generation (SSG)
HTML is generated at build time. The server delivers a ready file — maximum TTFB and LCP speed. Ideal for blogs, landing pages, and non-personalised pages.
Incremental Static Regeneration (ISR)
SSG with periodic regeneration (Next.js). Combines the speed of static files with data freshness. Optimal for most CMS content.
Streaming SSR
The server starts sending HTML before rendering completes — the browser progressively displays content. React 18 + Next.js 13+ support this out of the box.
Partial Hydration and Islands Architecture (Astro, Qwik) are an even more effective approach: only interactive components hydrate, static content renders instantly. For content-heavy sites this yields a 0.5–1 s LCP gain.

Step 6: CSS background-image as LCP

If the LCP element uses a CSS background-image, the browser cannot start loading it until the CSSOM is built. This is one of the most insidious causes of slow LCP.

  • Replace CSS background-image with <img fetchpriority="high"> — the browser discovers it during HTML parsing.
  • If replacement is not possible — use <link rel="preload" as="image"> to start loading manually.
  • For responsive backgrounds use imagesrcset and imagesizes in the preload tag.
  • Move the CSS containing the LCP background-image into an inline <style> — eliminates one network request.

Tools for measuring and diagnosing LCP

Correct diagnosis matters more than blind optimisation. Use both tool types: Lab (synthetic tests) and Field (real users).

ToolData typeWhat it providesWhen to use
PageSpeed InsightsLab + Field (CrUX)LCP score, sub-component breakdown, specific recommendationsInitial audit, verifying changes
Chrome DevTools PerformanceLabLoad waterfall, exact cause of LCP delay, flame chartDeep diagnosis of a specific problem
Lighthouse CILabAutomated checks in CI/CD, LCP history by releaseRegression control
WebPageTestLabTests from multiple regions, video comparison, filmstrip, waterfallCDN verification, regional LCP
Google Search Console (CWV)Field (CrUX)Real LCP by URL groups, pages with issuesProduction monitoring
web-vitals.jsFieldJavaScript API for collecting LCP from real users into custom analyticsCustom monitoring, RUM

How to diagnose LCP in Chrome DevTools

DevTools is the most precise tool for understanding exactly what is slowing LCP on a specific page.

  1. Open DevTools → Performance → press Record, reload the page, stop the recording.
  2. In Timings find the LCP marker — it shows the exact time and the element.
  3. Study the waterfall: find the LCP element's resource and check when its loading started relative to TTFB.
  4. Look for red bars — these are render-blocking resources. They appear right before LCP.
  5. Network tab → Filter by type Image — find the LCP image and check its priority (should be High).
  6. Coverage tab — shows unused CSS/JS. Large amounts of unused code = LCP delay.
In DevTools Network, the LCP image should show: Priority: High, Initiator: Preload or Parser (not a script). If Initiator is a script or XHR — the image is discovered too late.

FAQ

PageSpeed Insights shows both lab data (Lighthouse) and field data (CrUX). If it shows green, you may be looking at the Lighthouse score. CrUX in PSI and Search Console reflects real Chrome users, who may have slow connections, older devices, or different geolocations. Always use CrUX/Field data — that is what Google uses for ranking.
Partially — yes. Connecting a CDN, configuring HTTP caching, enabling Brotli compression — these are server configuration changes. Converting images to WebP/AVIF and compressing them also requires no code changes on the site. But fetchpriority, preload, removing lazy load from the hero, and SSR all require code changes.
Focus on fonts and TTFB. Add preload for the web font, use font-display: swap, self-host fonts. If the text is in a large block lacking critical CSS, ensure the CSS affecting that block is not render-blocking. Also check TTFB: even without images, a slow server means high LCP.
Yes. LCP is part of Core Web Vitals — the Page Experience signal that Google officially uses as a ranking factor since 2021. Poor LCP won't crash rankings overnight, but all else being equal, a site with good LCP has an advantage. The effect is most visible on mobile.
Prioritise pages with high organic traffic and landing pages for paid traffic. Google Search Console shows which URLs have 'Poor' LCP — start there. Homepage, category pages, and product cards in e-commerce are typically first. Technical improvements (fonts, CDN, Brotli) apply globally.