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
<!-- 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
<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
<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
<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
<img
src="/images/hero.avif"
alt="Hero"
width="1200"
height="600"
style="aspect-ratio: 2/1; object-fit: cover;"
>Fix 6: Measure LCP
// 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 });