Image & Video Optimization: Modern Formats & Techniques

Easy•

Media optimization is critical - images are the largest resource on most pages. Master these techniques for dramatic performance gains.

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

•WebP: Universal modern format
•AVIF: Best compression, use with fallback
•JPEG: Legacy browsers only
•PNG: Icons, logos (or use SVG)

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:

•Browser picks appropriate size based on screen width
•sizes tells browser the display width
•w descriptor specifies image width in pixels

Picture 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 anything
•metadata: Preload metadata only (duration, dimensions)
•auto: Browser decides

Lazy 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:

•Automatic WebP/AVIF conversion
•Responsive images (srcset)
•Lazy loading
•Blur placeholder

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 quality
•w_800: Resize to 800px width

Imgix

<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)