---
title: "LCP Optimization Guide: How to Improve Largest Contentful Paint"
description: "Complete guide to LCP optimization. Learn the causes of slow loading, image optimization techniques, preload, and Core Web Vitals best practices."
date: 2025-12-12
updated: 2026-03-16
category: Optimization
tags: ["Core Web Vitals", "LCP", "Performance", "SEO", "Optimization", "Lighthouse"]
url: https://uper.pl/en/blog/lcp-optimization-guide/
---

# LCP Optimization Guide: How to Improve Largest Contentful Paint

**Largest Contentful Paint (LCP)** is one of the three key [Core Web Vitals](/en/blog/core-web-vitals/) metrics that measures the loading time of the largest visible element on a page. Google uses LCP as a ranking factor, and a poor score can negatively impact your search result positions.

## What is LCP?

**LCP** measures the time from the start of page loading until the largest element in the visible area (viewport) is rendered. Typically, this element is:

- Hero image
- Large text block
- `<video>` element with a poster
- Element with a background image (background-image)

### LCP Thresholds

Google defines the following thresholds:

| Score | Rating |
|-------|--------|
| ≤ 2.5s | **Good** (green) |
| 2.5s - 4.0s | **Needs Improvement** (orange) |
| > 4.0s | **Poor** (red) |

## How to Measure LCP?

### Lab Tools (Lab data)

1. **Lighthouse** (DevTools → Lighthouse)
2. **PageSpeed Insights** - [pagespeed.web.dev](https://pagespeed.web.dev/)
3. **WebPageTest** - [webpagetest.org](https://www.webpagetest.org/)
4. **Chrome DevTools** → Performance panel

### Uper SEO Auditor

The [Uper SEO Auditor](https://chromewebstore.google.com/detail/uper-seo-auditor/khhpbeckpphaoiemjdijhbfpjnendage) extension displays Core Web Vitals results directly on the analyzed page, including LCP with the specific element responsible for the score. For example, on vwtirestore.com it detected an LCP of 5.23s caused by an unoptimized hero image:

![LCP of 5.23s detected by Uper SEO Auditor on vwtirestore.com — LCP element is a hero image rendered in 5232ms](/blog/vwtirestore-com-lcp-issues.png)

### Field Data (Real User Data)

1. **[Google Search Console](/en/blog/google-search-console/)** → Core Web Vitals
2. **Chrome UX Report (CrUX)** - real data from Chrome users
3. **web-vitals library** - measurements on your own site

```javascript

onLCP(console.log);
// { name: 'LCP', value: 2547, rating: 'needs-improvement' }
```

### Identifying the LCP Element

In Chrome DevTools:

1. Open the **Performance** tab
2. Press **Ctrl+Shift+E** (record with reload)
3. Look for the **LCP** marker on the timeline
4. Click to see which element is LCP

## Main Causes of Slow LCP

### 1. Slow Server Response Time (TTFB)

**Time to First Byte** is the time from sending the request to receiving the first byte of response. If TTFB is high, LCP will automatically be delayed.

**Solutions:**
- Use a CDN (Cloudflare, Fastly, AWS CloudFront)
- Enable Brotli/Gzip compression
- Optimize database queries
- Use server-side caching (Redis, Memcached)
- Consider SSG instead of SSR for static pages

### 2. Render-blocking Resources

CSS and JavaScript in `<head>` block page rendering.

**Solutions:**

```html
<!-- Instead of -->
<link rel="stylesheet" href="styles.css">

<!-- Use inline critical CSS -->
<style>
  /* Critical CSS for above-the-fold */
  .hero { ... }
</style>
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
```

```html
<!-- JavaScript - use defer or async -->
<script defer src="app.js"></script>
<script async src="analytics.js"></script>
```

### 3. Slow Resource Loading

Images, fonts, and other resources must be downloaded before LCP is rendered.

### 4. Client-side Rendering

SPAs (Single Page Applications) render content after JavaScript loads, which delays LCP.

**Solutions:**
- Use SSR (Server-Side Rendering)
- Pre-rendering / SSG (Static Site Generation)
- Progressive Hydration

## Image Optimization - Key to Good LCP

Images are the most common LCP element. Their optimization has the biggest impact on scores.

### Image Formats

| Format | Use Case | Compression |
|--------|----------|-------------|
| **WebP** | Universal, great compression | 25-35% smaller than JPEG |
| **AVIF** | Best compression, but slower decoding | 50% smaller than JPEG |
| **JPEG** | Fallback for older browsers | Baseline |
| **PNG** | Graphics with transparency | Lossless |

```html
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero image" width="1200" height="600">
</picture>
```

### Responsive Images

Don't load a 4K image on a phone!

```html
<img
  src="hero-800.jpg"
  srcset="
    hero-400.jpg 400w,
    hero-800.jpg 800w,
    hero-1200.jpg 1200w,
    hero-1600.jpg 1600w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  alt="Hero image"
  width="1200"
  height="600"
>
```

### Lazy Loading vs Eager Loading

The LCP element should **NOT** have `loading="lazy"`!

```html
<!-- Hero image (LCP) - load immediately -->
<img src="hero.jpg" alt="Hero" fetchpriority="high">

<!-- Images below the fold - lazy load -->
<img src="product.jpg" alt="Product" loading="lazy">
```

### Image Dimensions

Always specify `width` and `height` to avoid layout shift:

```html
<img src="hero.jpg" width="1200" height="600" alt="Hero">
```

## Preload - Speeding Up Critical Resources

**Preload** tells the browser that a resource will be needed soon and should be downloaded with high priority.

### Preload for LCP Image

```html
<head>
  <link rel="preload" as="image" href="hero.webp" type="image/webp">
  <!-- Or responsively -->
  <link
    rel="preload"
    as="image"
    href="hero-mobile.webp"
    media="(max-width: 600px)"
  >
  <link
    rel="preload"
    as="image"
    href="hero-desktop.webp"
    media="(min-width: 601px)"
  >
</head>
```

### Preload for Fonts

Fonts often delay text rendering:

```html
<link
  rel="preload"
  href="/fonts/inter.woff2"
  as="font"
  type="font/woff2"
  crossorigin
>
```

### Preconnect to External Domains

If the LCP image is on a CDN, establish the connection earlier:

```html
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
```

## Fetch Priority API

Modern browsers support **fetchpriority** for controlling resource priority:

```html
<!-- High priority for LCP image -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">

<!-- Low priority for less important images -->
<img src="decoration.jpg" fetchpriority="low" alt="Decoration">
```

```html
<!-- High priority for critical CSS -->
<link rel="stylesheet" href="critical.css" fetchpriority="high">
```

## CSS Optimization

### Critical CSS Inline

Extract CSS needed for rendering above-the-fold and insert inline:

```html
<head>
  <style>
    /* Critical CSS - viewport only */
    header { ... }
    .hero { ... }
    nav { ... }
  </style>
  <!-- Rest of CSS asynchronously -->
  <link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
</head>
```

Tools for extracting Critical CSS:
- **Critical** (npm package)
- **Critters** (webpack plugin)
- **PurgeCSS** (removing unused CSS)

### Avoid @import

```css
/* Bad - creates request chain */
@import url('fonts.css');
@import url('components.css');

/* Good - use <link> in HTML */
```

### Minification and Compression

```bash
# CSS minification
npx csso styles.css -o styles.min.css

# Brotli compression
brotli -f styles.min.css
```

## JavaScript Optimization

### Removing Render-blocking JS

```html
<!-- Bad - blocks rendering -->
<script src="app.js"></script>

<!-- Good - defer for main JS -->
<script defer src="app.js"></script>

<!-- Async for independent scripts -->
<script async src="analytics.js"></script>
```

### Code Splitting

Don't load the entire bundle on every page:

```javascript
// React - lazy loading components
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

// Dynamic import
if (userClickedButton) {
  const module = await import('./feature.js');
}
```

### Tree Shaking

Make sure your bundler removes unused code:

```javascript
// Import only what you need
import { debounce } from 'lodash-es'; // Good
import _ from 'lodash'; // Bad - imports entire library
```

## Font Optimization

### Font-display

```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap; /* Show fallback font, swap when loaded */
}
```

| Value | Behavior |
|-------|----------|
| `swap` | Immediate fallback, swap after loading |
| `optional` | Short fallback, may not load font |
| `fallback` | Short fallback (100ms), then swap |
| `block` | Invisible text until loaded (FOIT) |

### Font Subsetting

Load only the characters you need:

```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153; /* Latin only */
}
```

Tools:
- **Glyphhanger** - automatic subsetting
- **Google Fonts** - `&text=` or `&subset=` parameter

### Self-hosting Fonts

Hosting fonts on your own server eliminates external requests:

```html
<!-- Instead of Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">

<!-- Host locally -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
```

## Server and CDN Optimization

### Enable HTTP/2 or HTTP/3

HTTP/2 allows parallel resource loading:

```nginx
# nginx.conf
server {
    listen 443 ssl http2;
    # ...
}
```

### Cache Headers

```nginx
# Long cache for static resources
location ~* \.(js|css|png|jpg|webp|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}
```

### Brotli Compression

```nginx
# nginx.conf
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript image/svg+xml;
```

### Edge Caching (CDN)

Use a CDN with edge locations close to users:
- Cloudflare
- Fastly
- AWS CloudFront
- Vercel Edge Network

## LCP Optimization Checklist

### Priority 1 - Most Important

- [ ] Identify the LCP element on the page
- [ ] Preload for LCP image/resource
- [ ] `fetchpriority="high"` for LCP element
- [ ] Remove `loading="lazy"` from LCP element
- [ ] Optimize images (WebP/AVIF, compression)

### Priority 2 - Important

- [ ] Critical CSS inline
- [ ] Defer/async for JavaScript
- [ ] Preconnect to external domains
- [ ] Responsive images (srcset)
- [ ] font-display: swap for fonts

### Priority 3 - Additional

- [ ] HTTP/2 or HTTP/3
- [ ] Brotli compression
- [ ] CDN for static resources
- [ ] Self-hosting fonts
- [ ] JavaScript code splitting

## Summary

**LCP** is a critical performance metric that directly impacts user experience and Google rankings. Key actions:

1. **Identify the LCP element** - usually hero image
2. **Preload critical resources** - LCP image, fonts
3. **Optimize images** - format, size, responsiveness
4. **Eliminate blocking resources** - Critical CSS, defer JS
5. **Improve TTFB** - CDN, cache, server optimization

Regular monitoring of LCP in Google Search Console and Lighthouse will help maintain good results and respond to issues. The goal is **under 2.5 seconds** for 75% of users. For a holistic view of how LCP optimization connects with other technical SEO factors, read our [web technologies and SEO guide for 2026](/en/blog/web-technologies-seo-google-rankings/).

<FaqBlog
  questions={[
    {
      question: 'What is LCP and what is a good score?',
      answer: 'LCP (Largest Contentful Paint) measures the render time of the largest visible element on a page — usually a hero image or heading. A good score is <strong>under 2.5 seconds</strong>. Anything above 4 seconds is classified as poor by Google.'
    },
    {
      question: 'What are the most common causes of slow LCP?',
      answer: 'The three main causes are: slow server response time (TTFB above 800ms), unoptimized hero images (no WebP/AVIF, no compression), and render-blocking CSS and JavaScript that delay page rendering.'
    },
    {
      question: 'How do I optimize images to improve LCP?',
      answer: 'Use modern formats (<strong>WebP or AVIF</strong>), set <strong>fetchpriority="high"</strong> on the hero image, add a <strong>preload</strong> link in the HTML head, and ensure images have explicit width/height dimensions.'
    },
    {
      question: 'Does a CDN help improve LCP?',
      answer: 'Yes, a CDN (Content Delivery Network) significantly improves LCP by reducing the distance between the server and the user. This shortens both TTFB and asset download times. In tests, a CDN can improve LCP by 40-60% for users far from the origin server.'
    }
  ]}
  heading="Frequently Asked Questions"
  id="faq"
/>

## Sources

1. **web.dev - Largest Contentful Paint (LCP)**
[https://web.dev/articles/lcp](https://web.dev/articles/lcp)

2. **web.dev - Optimize LCP**
[https://web.dev/articles/optimize-lcp](https://web.dev/articles/optimize-lcp)

3. **Google Search Central - Core Web Vitals**
[https://developers.google.com/search/docs/appearance/core-web-vitals](https://developers.google.com/search/docs/appearance/core-web-vitals)

4. **Chrome Developers - Fetch Priority API**
[https://developer.chrome.com/docs/lighthouse/performance/priority-hints](https://developer.chrome.com/docs/lighthouse/performance/priority-hints)

5. **web.dev - Preload critical assets**
[https://web.dev/articles/preload-critical-assets](https://web.dev/articles/preload-critical-assets)

6. **MDN - Resource Hints**
[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload)
