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: Use modern formats such as WebP or AVIF when they produce smaller files for the asset Responsive: Use srcset - serve appropriate size for each device Lazy load: loading="lazy" for non-critical below-fold images; keep likely LCP media eager Dimensions: Always set width and height to prevent layout shift CDN: Use image CDN (Cloudinary, Imgix) for automatic optimization
Result: Smaller transfers and better visual quality when format, dimensions, and compression are chosen per asset. Always measure output quality and bytes.
Modern Image Formats
WebP
Often smaller than JPEG or PNG for photographic and transparent assets, depending on image content and encoder settings.
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback">
</picture>Browser support: broadly supported in modern browsers; keep fallbacks when your audience requires them
AVIF
Often compresses better than WebP for photographs, but encoding cost, decode support, and quality settings matter.
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback">
</picture>Browser support: supported in modern evergreen browsers; keep WebP/JPEG fallbacks for compatibility
Format Comparison
Original JPEG: 1MB
WebP: smaller if the asset compresses well
AVIF: often smaller still, with quality and decode trade-offsWhen 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: broadly supported in modern browsers; keep fallbacks when your audience requires them
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 non-critical below-fold images
ā Set dimensions (width/height) to prevent CLS
ā Tune quality per asset; 80-85% is a common starting point, not a universal rule
ā Use CDN for automatic optimization
ā Optimize early - avoid shipping oversized source images to the browser
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)