The Problem

Largest Contentful Paint (LCP) measures how quickly the largest visible element (usually a hero image) renders. Uncompressed, oversized images are the most common cause of poor LCP scores.

Symptoms

  • Lighthouse LCP score is "Poor" (>4 seconds)
  • Largest element is an image taking several seconds to load
  • Page appears blank until the image finally loads
  • LCP improves on fast connections but is terrible on 3G

Real Error Scenario

html
<!-- BAD: 2.4MB uncompressed JPEG as hero image -->
<img src="/images/hero-photo.jpg" alt="Hero" class="hero">

Network tab: `` hero-photo.jpg 2.4 MB 3.2s (3G) 800ms (Cable)

How to Fix It

Fix 1: Compress and Resize Images

```bash # Compress with ImageMagick convert hero-photo.jpg -quality 80 -resize 1920x hero-photo-compressed.jpg

# Convert to WebP cwebp -q 80 hero-photo.jpg -o hero-photo.webp

# Generate multiple sizes convert hero-photo.jpg -resize 480x hero-480.jpg convert hero-photo.jpg -resize 768x hero-768.jpg convert hero-photo.jpg -resize 1200x hero-1200.jpg ```

Fix 2: Use Responsive Images with srcset

html
<img
  src="/images/hero-768.jpg"
  srcset="/images/hero-480.jpg 480w,
          /images/hero-768.jpg 768w,
          /images/hero-1200.jpg 1200w,
          /images/hero-1920.jpg 1920w"
  sizes="(max-width: 768px) 100vw, 1200px"
  alt="Hero"
  class="hero"
  width="1200"
  height="600"
>

Fix 3: Use Modern Image Formats

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

AVIF typically achieves 50% smaller file sizes than JPEG.

Fix 4: Preload LCP Image

html
<link rel="preload" as="image" href="/images/hero.avif" imagesrcset="/images/hero-480.avif 480w, /images/hero-1200.avif 1200w">

Fix 5: Set Explicit Dimensions to Prevent CLS

html
<img
  src="/images/hero.avif"
  alt="Hero"
  width="1200"
  height="600"
  style="aspect-ratio: 2/1; object-fit: cover;"
>

Fix 6: Measure LCP

javascript
// Report LCP to analytics
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });