---
title: "Fix Large Images for Better LCP"
description: "Optimize LCP images with responsive srcset, modern formats like WebP and AVIF, and proper compression. Reduce image size without losing quality."
canonical_url: "https://unlighthouse.dev/learn-lighthouse/lcp/large-images"
last_updated: "2025-01-18"
---

[73% of mobile pages have an image as their LCP element](https://almanac.httparchive.org/en/2024/performance). When that image is unoptimized, nothing else you do matters - you're bottlenecked by download time.

The median site keeps LCP images under 100KB, but [8% of sites serve LCP images over 1MB](https://almanac.httparchive.org/en/2024/media). That's 5-10 seconds of download time on 3G.

## What's the problem?

Large images slow LCP in a direct, linear way: bigger file = longer download. The resource load duration, how long the image takes to download, accounts for roughly 40% of typical LCP time.

**Common causes:**

- Using JPEG or PNG instead of WebP/AVIF (2-3x larger)
- Serving desktop-sized images to mobile devices
- No compression or using maximum quality
- Ignoring responsive images (srcset/sizes)
- Animated GIFs instead of video formats

Lighthouse flags this through multiple audits: "Properly size images," "Serve images in next-gen formats," "Efficiently encode images," and the combined "Improve image delivery" insight.

### The math

A 1MB hero image on a 3G connection (1.5 Mbps):

- Download time: ~5.3 seconds
- Your LCP cannot be faster than 5.3 seconds

The same image optimized to 100KB:

- Download time: ~530ms
- LCP budget now available for other things

## How to identify this issue

### Chrome DevTools

1. Open Network tab (F12)
2. Filter by "Img"
3. Find your LCP image (usually the largest above-fold image)
4. Check the "Size" column
5. Click the image, check "Content-Length" in headers

**Red flags:**

- LCP image > 200KB on desktop
- LCP image > 100KB on mobile
- Download time > 500ms on good connection

### Lighthouse

Look for these audits:

- "Properly size images" - Image dimensions vs display size
- "Serve images in next-gen formats" - JPEG/PNG that should be WebP/AVIF
- "Efficiently encode images" - Compression opportunities
- "Improve image delivery" - Combined recommendations

### Webpagetest

Waterfall chart shows exact download time per image. Look for long bars on image requests.

## The fix

### 1. use modern image formats

WebP and AVIF are 30-50% smaller than JPEG at equivalent visual quality.

```html
<!-- Before: JPEG only -->
<img src="hero.jpg" alt="Hero">

<!-- After: modern formats with fallback -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero" width="1200" height="600">
</picture>
```

**Format comparison at ~80 quality:**

<table>
<thead>
  <tr>
    <th>
      Format
    </th>
    
    <th>
      Typical size
    </th>
    
    <th>
      Browser support
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      JPEG
    </td>
    
    <td>
      150KB
    </td>
    
    <td>
      100%
    </td>
  </tr>
  
  <tr>
    <td>
      WebP
    </td>
    
    <td>
      90KB
    </td>
    
    <td>
      97%+
    </td>
  </tr>
  
  <tr>
    <td>
      AVIF
    </td>
    
    <td>
      60KB
    </td>
    
    <td>
      93%+
    </td>
  </tr>
</tbody>
</table>

Use AVIF as primary, WebP as fallback, JPEG for ancient browsers.

### 2. use responsive images

Don't serve 2400px images to 400px mobile screens.

```html
<img
  src="hero.webp"
  srcset="
    hero-400.webp 400w,
    hero-800.webp 800w,
    hero-1200.webp 1200w,
    hero-1600.webp 1600w
  "
  sizes="100vw"
  width="1200"
  height="600"
  alt="Hero"
>
```

**Understanding srcset and sizes:**

- `srcset` lists available images with their widths (`400w` = 400px wide)
- `sizes` tells browser how wide the image will display
- Browser picks the optimal image based on screen size and DPR

**Common sizes patterns:**

```html
<!-- Full width hero -->
sizes="100vw"

<!-- Full width mobile, half width desktop -->
sizes="(max-width: 768px) 100vw, 50vw"

<!-- Fixed width sidebar image -->
sizes="300px"

<!-- Complex layout -->
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
```

### 3. compress appropriately

Quality 80-85 is visually indistinguishable from 100 for most images.

```bash
npx sharp-cli hero.jpg -o hero.webp -- webp -q 80

convert hero.jpg -quality 80 hero.webp

# Cwebp (Google)
cwebp -q 80 hero.jpg -o hero.webp
```

**Quality guidelines:**

<table>
<thead>
  <tr>
    <th>
      Image type
    </th>
    
    <th>
      Quality
    </th>
    
    <th>
      Rationale
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Hero/LCP
    </td>
    
    <td>
      80-85
    </td>
    
    <td>
      Needs to look sharp
    </td>
  </tr>
  
  <tr>
    <td>
      Product photos
    </td>
    
    <td>
      75-80
    </td>
    
    <td>
      Balance quality/size
    </td>
  </tr>
  
  <tr>
    <td>
      Thumbnails
    </td>
    
    <td>
      70-75
    </td>
    
    <td>
      Small display size
    </td>
  </tr>
  
  <tr>
    <td>
      Backgrounds
    </td>
    
    <td>
      60-70
    </td>
    
    <td>
      Often blurred/dimmed
    </td>
  </tr>
</tbody>
</table>

### 4. set explicit dimensions

Always include `width` and `height` to prevent layout shifts and help browser allocate space.

```html
<img
  src="hero.webp"
  width="1200"
  height="600"
  style="width: 100%; height: auto;"
  alt="Hero"
>
```

Or use CSS aspect-ratio:

```css
.hero-img {
  width: 100%;
  aspect-ratio: 2 / 1;
  object-fit: cover;
}
```

### 5. use an image CDN

Image CDNs (Cloudinary, imgix, Cloudflare Images) handle format selection, resizing, and compression automatically.

```html
<!-- Cloudinary: automatic format, width, quality -->
<img
  src="https://res.cloudinary.com/demo/image/upload/w_800,q_auto,f_auto/hero.jpg"
  srcset="
    https://res.cloudinary.com/demo/image/upload/w_400,q_auto,f_auto/hero.jpg 400w,
    https://res.cloudinary.com/demo/image/upload/w_800,q_auto,f_auto/hero.jpg 800w,
    https://res.cloudinary.com/demo/image/upload/w_1200,q_auto,f_auto/hero.jpg 1200w
  "
  sizes="100vw"
  alt="Hero"
>
```

Benefits:

- Automatic WebP/AVIF serving
- Dynamic resizing
- Global CDN caching
- No build-time image generation

### Complete example

```html
<picture>
  <!-- AVIF for modern browsers -->
  <source
    type="image/avif"
    srcset="
      hero-400.avif 400w,
      hero-800.avif 800w,
      hero-1200.avif 1200w
    "
    sizes="100vw"
  >
  <!-- WebP fallback -->
  <source
    type="image/webp"
    srcset="
      hero-400.webp 400w,
      hero-800.webp 800w,
      hero-1200.webp 1200w
    "
    sizes="100vw"
  >
  <!-- JPEG for old browsers -->
  <img
    src="hero-800.jpg"
    srcset="
      hero-400.jpg 400w,
      hero-800.jpg 800w,
      hero-1200.jpg 1200w
    "
    sizes="100vw"
    width="1200"
    height="600"
    fetchpriority="high"
    decoding="async"
    alt="Descriptive alt text"
  >
</picture>
```

## Why this works

Image download time is governed by simple physics: file size divided by bandwidth. You cannot make bytes download faster - you can only send fewer bytes.

Modern formats use better compression algorithms:

- WebP: Predictive coding, superior entropy encoding
- AVIF: AV1 video codec applied to images, excellent for photos

Responsive images make sure you're not sending 4x more pixels than needed:

- 2400px image at 2x DPR for 600px display = 4.8 megapixels
- 1200px image at 2x DPR for 600px display = 1.2 megapixels
- That's 75% fewer pixels to compress

Combined, format + responsive + compression reduces image size by 80-90%.

## Framework-specific solutions

<callout icon="i-logos-nextjs-icon">

**Next.js**

`next/image` handles everything - format selection, responsive sizes, lazy loading:

```jsx
import Image from 'next/image'

export function HeroImage() {
  return (
    <Image
      src="/hero.jpg"
      width={1200}
      height={600}
      priority // For LCP image
      sizes="100vw"
      alt="Hero"
    />
  )
}
```

Configure quality in next.config.js:

```js
export default {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    quality: 80,
  },
}
```

</callout>

<callout icon="i-logos-nuxt-icon">

**Nuxt**

Use `@nuxt/image` with a provider (ipx, cloudinary, etc.):

```html
<NuxtImg
  src="/hero.jpg"
  width="1200"
  height="600"
  sizes="100vw sm:100vw md:100vw lg:100vw"
  format="webp"
  quality="80"
  preload
  alt="Hero"
/>
```

Configure in nuxt.config.ts:

```ts
export default defineNuxtConfig({
  image: {
    quality: 80,
    format: ['webp', 'avif'],
    screens: {
      sm: 640,
      md: 768,
      lg: 1024,
      xl: 1280,
    },
  },
})
```

</callout>

<callout icon="i-logos-react">

**React**

No built-in solution. Use an image component library or build your own:

```jsx
function ResponsiveImage({ src, alt, sizes = '100vw' }) {
  const base = src.replace(/\.[^.]+$/, '')
  const ext = src.match(/\.[^.]+$/)?.[0] || '.jpg'

  return (
    <picture>
      <source
        type="image/avif"
        srcSet={`${base}-400.avif 400w, ${base}-800.avif 800w, ${base}-1200.avif 1200w`}
        sizes={sizes}
      />
      <source
        type="image/webp"
        srcSet={`${base}-400.webp 400w, ${base}-800.webp 800w, ${base}-1200.webp 1200w`}
        sizes={sizes}
      />
      <img
        src={`${base}-800${ext}`}
        srcSet={`${base}-400${ext} 400w, ${base}-800${ext} 800w, ${base}-1200${ext} 1200w`}
        sizes={sizes}
        alt={alt}
        fetchPriority="high"
      />
    </picture>
  )
}
```

Or use an image CDN and construct URLs dynamically.

</callout>

## Verify the fix

After implementing:

1. **Network tab**: Check new file sizes, should be significantly smaller
2. **Response headers**: Verify Content-Type shows webp or avif
3. **Lighthouse**: Image audits should pass or show smaller potential savings
4. **Compare LCP**: Before/after measurement

**Expected improvement:** Reducing a 500KB JPEG to 100KB WebP can save 400-800ms on typical connections.

## Common mistakes

### Wrong sizes attribute

If `sizes` doesn't match actual layout, browser downloads wrong image.

```html
<!-- Wrong: says full width, but image is actually half width -->
<img sizes="100vw" src="..." style="width: 50%">

<!-- Right: matches layout -->
<img sizes="50vw" src="..." style="width: 50%">
```

### Over-compressing

Quality too low creates visible artifacts. Test visually.

```bash
cwebp -q 30 hero.jpg -o hero.webp  # Will look bad

cwebp -q 80 hero.jpg -o hero.webp
```

### Forgetting high-dpr displays

2x and 3x displays need larger source images.

```html
<!-- srcset should include sizes for high-DPR -->
srcset="
  hero-400.webp 400w,   /* 400px @ 1x */
  hero-800.webp 800w,   /* 400px @ 2x, 800px @ 1x */
  hero-1200.webp 1200w  /* 400px @ 3x, 600px @ 2x */
"
```

### Only optimizing desktop

Mobile users on slow networks benefit most from image optimization. Test on mobile.

## Related issues

Often appears alongside:

- [Prioritize LCP Image](/learn-lighthouse/lcp/prioritize-lcp-image) - Optimized image + priority = fast LCP
- [LCP Lazy Loaded](/learn-lighthouse/lcp/lcp-lazy-loaded) - Don't lazy-load your optimized hero

## Test your entire site

Image optimization varies by page. Your homepage hero might be perfect while blog post images are unoptimized PNGs. [Unlighthouse](/) scans your entire site and identifies pages with oversized images.
