Introduction
Images typically account for 50-70% of a web page's total weight. Optimizing them is the single most impactful performance improvement you can make. A poorly optimized hero image can add seconds to your load time.
Modern image optimization goes beyond simple compression. It involves choosing the right format, serving the right size for each device, loading images at the right time, and leveraging CDNs for delivery.
This guide covers everything from format selection to responsive image implementation, with practical code you can use in any project.
Key Concepts
Modern Image Formats
WebP offers 25-35% smaller file sizes than JPEG with comparable quality. AVIF goes further with 50% savings but has slower encoding. Use AVIF with WebP fallback for maximum savings.
<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
<img
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
src="/hero-800.webp" alt="Hero" width="1200" height="600" loading="lazy">
Compression Quality
Quality 75-85 for JPEG/WebP is the sweet spot — visually identical to 100 but significantly smaller. For AVIF, quality 60-70 produces excellent results. Always test with real images.
Practical Examples
1. Build-Time Optimization with Sharp
import sharp from 'sharp';
async function optimizeImage(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: 65 }).toFile(`${output}.avif`);
for (const width of [400, 800, 1200, 1600]) {
await sharp(input).resize(width).webp({ quality: 80 }).toFile(`${output}-${width}.webp`);
}
}
2. Next.js Image Component
import Image from 'next/image';
export function Hero() {
return (
<Image src="/hero.jpg" alt="Hero" width={1200} height={600}
priority placeholder="blur" blurDataURL="data:image/jpeg;base64,..." />
);
}
3. SVG Optimization
// npx svgo input.svg -o output.svg
// svgo.config.js
module.exports = {
plugins: ['preset-default', 'removeDimensions',
{ name: 'removeViewBox', active: false }],
};
Best Practices
- ✅ Use WebP/AVIF as primary formats with JPEG/PNG fallbacks
- ✅ Always specify width and height attributes to prevent layout shift
- ✅ Use native loading='lazy' for below-fold images
- ✅ Set fetchpriority='high' on the LCP image
- ✅ Serve responsive images with srcset and sizes
- ❌ Don't serve uncompressed PNGs for photographic content
- ❌ Don't lazy-load above-the-fold images
Common Pitfalls
- 🚫 Using PNG for photographs — use JPEG/WebP/AVIF instead
- 🚫 One-size-fits-all images — use responsive images
- 🚫 Forgetting art direction — sometimes you need different crops for mobile
- 🚫 Over-compressing — quality below 60 creates visible artifacts