Image & Video Optimization: Modern Formats & Techniques
Media optimization is critical - images are the largest resource on most pages. Master these techniques for dramatic performance gains.
Quick Navigation: Modern Image Formats ⢠Responsive Images ⢠Lazy Loading ⢠Video Optimization ⢠Image CDN & Automation ⢠Best Practices
Quick Decision Guide
Quick Wins:
Format: Convert to WebP (80% smaller than JPEG), AVIF (even smaller) Responsive: Use srcset - serve appropriate size for each device Lazy load: loading="lazy" for below-fold images Dimensions: Always set width and height to prevent layout shift CDN: Use image CDN (Cloudinary, Imgix) for automatic optimization
Result: 50-80% reduction in image size with better quality.
Modern Image Formats
WebP
~30% smaller than JPEG, ~80% smaller than PNG with transparency.
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback">
</picture>Browser support: 95%+ (all modern browsers)
AVIF
~50% smaller than JPEG, better than WebP.
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback">
</picture>Browser support: 90%+ (Chrome 85+, Firefox 93+)
Format Comparison
Original JPEG: 1MB
WebP: 300KB (70% smaller)
AVIF: 150KB (85% smaller)When to Use Each
Responsive Images
srcset - Multiple Sizes
Serve different sizes for different screens:
<img
srcset="
image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w
"
sizes="(max-width: 600px) 400px, 800px"
src="image-800w.jpg"
alt="Responsive image"
loading="lazy"
/>How it works:
sizes tells browser the display widthw descriptor specifies image width in pixelsPicture Element - Art Direction
Different images for different sizes:
<picture>
<!-- Mobile: Square crop -->
<source
media="(max-width: 600px)"
srcset="mobile-square.jpg"
/>
<!-- Desktop: Wide crop -->
<source
media="(min-width: 601px)"
srcset="desktop-wide.jpg"
/>
<img src="default.jpg" alt="Responsive art direction" />
</picture>Density Descriptors
Serve higher resolution for retina displays:
<img
srcset="image.jpg 1x, image@2x.jpg 2x, image@3x.jpg 3x"
src="image.jpg"
alt="Hi-DPI image"
/>Lazy Loading
Native Lazy Loading
<!-- Lazy load (loads when near viewport) -->
<img src="image.jpg" loading="lazy" alt="Lazy loaded">
<!-- Eager load (default, loads immediately) -->
<img src="hero.jpg" loading="eager" alt="Hero image">Browser support: 95%+ (all modern browsers)
Implementation Strategy
<!-- First 2-3 images: eager -->
<img src="hero.jpg" loading="eager" alt="Hero">
<img src="feature1.jpg" loading="eager" alt="Feature 1">
<!-- Rest: lazy -->
<img src="feature2.jpg" loading="lazy" alt="Feature 2">
<img src="feature3.jpg" loading="lazy" alt="Feature 3">Prevent Layout Shift
Always specify dimensions:
<img
src="image.jpg"
alt="Description"
width="800"
height="600"
loading="lazy"
/>Or use aspect ratio:
img {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}Video Optimization
Video Formats
<video controls preload="metadata">
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
</video>Preload options:
none: Don't preload anythingmetadata: Preload metadata only (duration, dimensions)auto: Browser decidesLazy Load Videos
<!-- Don't autoplay, load poster only -->
<video
poster="thumbnail.jpg"
preload="none"
controls
>
<source src="video.mp4" type="video/mp4">
</video>YouTube/Vimeo Optimization
Use facade (covered in Import on Interaction):
function VideoEmbed({ videoId }) {
const [play, setPlay] = useState(false);
if (play) {
return (
<iframe
src={`https://youtube.com/embed/${videoId}?autoplay=1`}
allow="autoplay"
/>
);
}
return (
<div
onClick={() => setPlay(true)}
style={{
backgroundImage: `url(https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg)`
}}
>
<button>ā¶ļø Play</button>
</div>
);
}Saves: ~600KB per embed until user clicks
Image CDN & Automation
Next.js Image Component
Automatic optimization:
import Image from 'next/image';
<Image
src="/photo.jpg"
width={800}
height={600}
alt="Auto-optimized"
loading="lazy"
quality={85}
/>Features:
Cloudinary
<!-- Original: 1MB JPEG -->
<img src="https://res.cloudinary.com/demo/image/upload/photo.jpg">
<!-- Optimized: Auto format, quality, size -->
<img src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_800/photo.jpg">URL transformations:
f_auto: Auto format (WebP/AVIF)q_auto: Auto qualityw_800: Resize to 800px widthImgix
<img src="https://demo.imgix.net/photo.jpg?auto=format,compress&w=800">Benefits: No build step, on-the-fly optimization, global CDN
Best Practices
Checklist
ā Use modern formats (WebP minimum, AVIF when possible)
ā Implement srcset for responsive images
ā Lazy load below-fold images
ā Set dimensions (width/height) to prevent CLS
ā Compress at 80-85% quality (sweet spot)
ā Use CDN for automatic optimization
ā Optimize early - don't upload 5MB images
Common Mistakes
<!-- ā Bad: No dimensions (causes CLS) -->
<img src="image.jpg" alt="Image">
<!-- ā
Good: With dimensions -->
<img src="image.jpg" width="800" height="600" alt="Image">
<!-- ā Bad: No lazy loading -->
<img src="below-fold.jpg" alt="Not visible">
<!-- ā
Good: Lazy loaded -->
<img src="below-fold.jpg" loading="lazy" alt="Lazy">
<!-- ā Bad: Single size for all devices -->
<img src="huge-4k.jpg" alt="Oversized">
<!-- ā
Good: Responsive sizes -->
<img srcset="small.jpg 400w, large.jpg 1200w" alt="Responsive">Performance Impact
Before optimization:
- 10 images, all JPEG
- Total: 8MB
- LCP: 4.5s
After optimization:
- 10 images, WebP with srcset
- Lazy loading enabled
- Total: 1.2MB (85% reduction)
- LCP: 1.2s (73% faster)