LCP is the render time of the largest content element visible in the viewport — usually the hero image, a large block of text, or a video poster. Google rates it “good” under 2.5s, “needs improvement” 2.5–4.0s, and “poor” above 4.0s, assessed at the 75th percentile of real-user field data from CrUX. Lab tools (Lighthouse, PageSpeed Insights synthetic) show a single sample; CrUX is the score Google actually uses for ranking.
Why it matters
LCP is a confirmed Core Web Vital and a confirmed ranking signal as part of the page experience system. The effect is real but narrow — it’s a tiebreaker between otherwise-equivalent results, not a primary ranking lever. The bigger impact is downstream: a 4-second LCP correlates with measurable bounce rate increases that hurt engagement metrics, which do influence rankings indirectly.
The four causes of slow LCP, in rough order of frequency:
- Slow TTFB. If the server takes 1.5s to respond, LCP can’t be under 2.5s no matter what the page does. Fix this first.
- Render-blocking resources. Synchronous CSS and JS in
<head>delay the first paint. The LCP element doesn’t render until the parser unblocks. - Resource load time for the LCP element itself. The hero image is 800 KB, served from a slow origin, no
preload. - Client-side rendering delay. The LCP element is rendered by JS, which runs after hydration, which runs after the bundle parses.
How to detect it
Three sources, used together:
- CrUX field data — PageSpeed Insights shows the real 75th-percentile LCP for any URL or origin with enough traffic. This is the canonical number.
- Web Vitals JS library — emit LCP from your own RUM:
<script type="module">
import { onLCP } from 'https://unpkg.com/web-vitals?module';
onLCP(({ value, entries }) => {
const el = entries[entries.length - 1]?.element;
console.log('LCP:', value, 'Element:', el);
});
</script>
- Chrome DevTools → Performance — record a load, look for the LCP marker in the timeline, click it to see which element triggered it.
The single most useful insight is which element is the LCP. You can’t fix LCP without knowing whether you’re optimizing an image, a font-rendered headline, or a hydration delay.
The fix
Fix 1 — Improve TTFB first
If TTFB > 600ms, no LCP fix will help much. See the dedicated guide on slow page response times.
Fix 2 — Preload the LCP image
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
For responsive images, preload the right candidate:
<link rel="preload" as="image"
imagesrcset="/hero-800.webp 800w, /hero-1600.webp 1600w"
imagesizes="100vw" />
fetchpriority="high" on the <img> itself helps too — Chrome will boost its priority in the network queue:
<img src="/hero.webp" fetchpriority="high" alt="…" />
Fix 3 — Eliminate render-blocking CSS and fonts
Inline critical CSS for above-the-fold content; lazy-load the rest:
<head>
<style>/* Inlined critical CSS — hero, header, layout */</style>
<link rel="preload" href="/full.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/full.css"></noscript>
</head>
For fonts, preload the WOFF2 and use font-display: swap to avoid a blocked render waiting for the font:
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin />
@font-face {
font-family: "Brand";
src: url("/font.woff2") format("woff2");
font-display: swap;
}
Fix 4 — Avoid client-side rendering for the LCP element
If the LCP element is rendered by React/Vue/etc. after hydration, server-render it. The HTML response should contain the LCP element directly.
Next.js (App Router)
import Image from "next/image";
export default function HeroSection() {
return (
<Image
src="/hero.webp"
alt="…"
width={1600}
height={900}
priority // emits preload and fetchpriority=high
/>
);
}
WordPress
Plugins like WP Rocket, Perfmatters, and FlyingPress automate critical CSS extraction and image preloading. Verify with PageSpeed Insights after enabling — automated tools occasionally inline the wrong stylesheet for variant templates.
Nginx (HTTP/2 server push alternative — early hints)
location / {
add_header Link "</hero.webp>; rel=preload; as=image";
}
Or use 103 Early Hints if your stack supports it.
Common pitfalls
- Optimizing LCP based on Lighthouse alone. Lighthouse is a synthetic single-sample lab test. CrUX is the field data Google uses. They often diverge.
- Preloading the wrong image. If the hero is a background image set via CSS, the browser doesn’t know to preload it. Hint explicitly.
loading="lazy"on the LCP image. Lazy-loading the LCP element is self-sabotage. The LCP image must load eagerly.- Large hero animations or carousels. The first carousel slide is the LCP. Optimize that one image specifically; the rest can lazy-load.
- Slow third-party scripts in
<head>. Analytics, A/B testing, consent management — defer or load async. They delay first paint.