Introduction
Largest Contentful Paint (LCP) measures how quickly the largest visible element (often the hero image) renders on screen. An uncompressed hero image is the most common cause of poor LCP scores. Google recommends LCP under 2.5 seconds, but uncompressed images can push this to 5-10 seconds on mobile connections:
Chrome DevTools Performance panel shows:
LCP: 6.2s (Poor)
Hero image: 2.4MB JPEG, loaded at full resolutionSymptoms
- Google PageSpeed Insights reports "Properly size images" and "Serve images in next-gen formats"
- LCP score above 2.5 seconds, especially on mobile
- Hero image takes several seconds to appear after page load starts
- Browser downloads a 2-5MB image when a 200-400KB version would suffice
- Cumulative Layout Shift (CLS) may also be affected if image dimensions are not specified
Common Causes
- Hero image uploaded without compression or resizing
- Image served in original format (JPEG/PNG) instead of WebP or AVIF
- Image loaded at full resolution (4000px wide) when displayed at 1200px max
- No
preloadhint for the hero image, causing delayed fetch priority - Image loaded via CSS
background-imagewhich deprioritizes loading vs<img>tags
Step-by-Step Fix
- 1.Convert hero images to modern formats using command-line tools:
- 2.```bash
- 3.# Convert to WebP with quality 80
- 4.cwebp -q 80 hero.jpg -o hero.webp
# Convert to AVIF (better compression) avifenc --speed 6 --min 20 --max 40 hero.jpg hero.avif ```
- 1.Serve responsive images with srcset:
- 2.```html
- 3.<picture>
- 4.<source srcset="/images/hero.avif" type="image/avif">
- 5.<source srcset="/images/hero.webp" type="image/webp">
- 6.<img
- 7.src="/images/hero.jpg"
- 8.alt="Hero banner"
- 9.width="1200"
- 10.height="600"
- 11.fetchpriority="high"
- 12.>
- 13.</picture>
- 14.
` - 15.Preload the hero image to prioritize its download:
- 16.```html
- 17.<head>
- 18.<link rel="preload" as="image" href="/images/hero.webp" imagesrcset="/images/hero-480.webp 480w, /images/hero-800.webp 800w, /images/hero-1200.webp 1200w" imagesizes="100vw">
- 19.</head>
- 20.
` - 21.This tells the browser to start downloading the hero image immediately, before CSS is parsed.
- 22.Configure server-side image optimization with Nginx:
- 23.```nginx
- 24.location ~* \.(jpg|jpeg|png|gif)$ {
- 25.expires 30d;
- 26.add_header Cache-Control "public, immutable";
# Serve WebP if browser supports it add_header Vary Accept; set $webp_suffix ""; if ($http_accept ~* "webp") { set $webp_suffix ".webp"; } try_files $uri$webp_suffix $uri =404; } ```
- 1.Automate image optimization in your build pipeline:
- 2.```javascript
- 3.// Sharp (Node.js) for build-time optimization
- 4.const sharp = require('sharp');
async function optimizeHero(input, output) {
await sharp(input)
.resize(1200, 600, { fit: 'cover' })
.webp({ quality: 80 })
.toFile(${output}.webp);
await sharp(input)
.resize(1200, 600, { fit: 'cover' })
.avif({ quality: 50 })
.toFile(${output}.avif);
}
```
Prevention
- Add image size validation to your CMS upload process (max 500KB for hero images)
- Use automated image CDNs (Cloudinary, imgix) that serve optimized formats based on browser support
- Set up LCP monitoring with Real User Monitoring (RUM) to detect regressions from new hero images
- Include image optimization in your CI/CD pipeline - fail the build if hero images exceed size thresholds
- Always specify
widthandheightattributes on hero images to prevent layout shift - Test LCP on mobile throttled connection (4G, 4x CPU slowdown) in Chrome DevTools before deploying