---
title: "Largest Contentful Paint (LCP) - Serpwise"
description: "Learn how to identify the Largest Contentful Paint element, measure field performance, and fix the server, resource, rendering, and discovery delays behind slow LCP."
url: "https://serpwise.ai/learn/largest-contentful-paint/"
source: "https://serpwise.ai/learn/largest-contentful-paint/"
---
[← Back to Learn](/learn/)

Performance & Core Web Vitals Warning

# Largest Contentful Paint (LCP)

Learn how to identify the Largest Contentful Paint element, measure field performance, and fix the server, resource, rendering, and discovery delays behind slow LCP.

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” from 2.5 to 4.0s, and “poor” above 4.0s, assessed at the 75th percentile of real-user field data. Lab tools show a controlled sample; CrUX shows field data from eligible Chrome visits.

## Why it matters

LCP is a Core Web Vital and one part of Google’s page-experience considerations. A good score does not guarantee higher rankings, and a poor score does not make a relevant page ineligible. The clearest reason to improve LCP is that users receive the page’s main content sooner.

The four causes of slow LCP, in rough order of frequency:

1. **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.
2. **Render-blocking resources.** Synchronous CSS and JS in `<head>` delay the first paint. The LCP element doesn’t render until the parser unblocks.
3. **Resource load time for the LCP element itself.** The hero image is 800 KB, served from a slow origin, no `preload`.
4. **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:

1. **CrUX field data** - PageSpeed Insights shows the real 75th-percentile LCP for any URL or origin with enough traffic. This is the canonical number.
2. **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>
```

3. **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 consumes a large share of the LCP timeline, improve the response path first. See the dedicated guide on [slow page response times](/learn/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.

From diagnosis to deployment

## Find the issue. Ship the fix.

Use Learn to understand the problem, then run Serpwise against your own site to see what can be approved and deployed.

[Run free audit](/audit/) [Book a demo](/demo/)