Images account for over 50% of the average web page's total weight. An unoptimized hero image can be 5MB; properly optimized, it's 100KB — with no visible quality loss. Image optimization is often the single biggest performance win you can make, directly improving Core Web Vitals (LCP), page load time, and user experience.

Modern Image Formats

  • JPEG: Best for photographs. Lossy compression. Universally supported. Use quality 75-85% for a good balance.
  • PNG: Best for graphics with transparency, icons, screenshots. Lossless but large file sizes for photos.
  • WebP: 25-35% smaller than JPEG at equivalent quality. Supports transparency (replacing PNG). 97%+ browser support in 2025.
  • AVIF: 50% smaller than JPEG. Best compression ratio. ~93% browser support. Slower to encode but worth it for frequently-served images.
  • SVG: Vector format — infinitely scalable, tiny file size. Perfect for logos, icons, illustrations.

The <picture> Element

Serve modern formats to modern browsers, with JPEG/PNG fallbacks.

Format Negotiation
<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 with srcset

Serve different image sizes based on the user's screen width. A mobile user on a 375px screen doesn't need a 2400px-wide image.

srcset & sizes
<img
  src="product-800.jpg"
  srcset="
    product-400.jpg 400w,
    product-800.jpg 800w,
    product-1200.jpg 1200w,
    product-1600.jpg 1600w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  alt="Product photo"
  width="800"
  height="600"
  loading="lazy"
>

Lazy Loading

Native lazy loading defers the loading of off-screen images until the user scrolls near them. This dramatically improves initial page load time.

Native Lazy Loading
<!-- Lazy load below-the-fold images -->
<img src="photo.webp" alt="Description" loading="lazy" width="600" height="400">

<!-- NEVER lazy-load the LCP image (above the fold) -->
<img src="hero.webp" alt="Hero" loading="eager" fetchpriority="high" width="1200" height="600">

Always Set Width & Height

Without explicit width and height attributes, the browser can't reserve space for the image before it loads. This causes Cumulative Layout Shift (CLS) — the page jumps around as images pop in. Setting dimensions lets the browser calculate the aspect ratio and reserve the correct space.

Prevent Layout Shift
<!-- ✅ Always include width and height -->
<img src="photo.webp" alt="Photo" width="800" height="600">

<!-- CSS to make it responsive while maintaining aspect ratio -->
img {
  max-width: 100%;
  height: auto;
}

Compression Tools

  • Sharp (Node.js): Convert, resize, and compress images programmatically. Extremely fast (libvips-based).
  • Squoosh: Google's browser-based image optimizer. Great for one-off optimizations.
  • ImageMagick: CLI tool for batch processing. Convert between formats, resize, compress.
  • CDN optimization: Cloudflare, Imgix, and Cloudinary transform images on-the-fly — auto-format, auto-resize, auto-quality.
Sharp Example (Node.js)
import sharp from 'sharp';

// Convert to WebP with 80% quality
await sharp('input.jpg')
  .resize(1200, 800, { fit: 'inside', withoutEnlargement: true })
  .webp({ quality: 80 })
  .toFile('output.webp');

// Generate multiple sizes
const sizes = [400, 800, 1200, 1600];
for (const size of sizes) {
  await sharp('input.jpg')
    .resize(size)
    .webp({ quality: 80 })
    .toFile(`output-${size}.webp`);
}

Quick Wins Checklist

  • ✅ Convert all photos to WebP (or AVIF with JPEG fallback)
  • ✅ Use SVG for logos and icons
  • ✅ Add loading="lazy" to all below-the-fold images
  • ✅ Set fetchpriority="high" on LCP images
  • ✅ Always include width and height attributes
  • ✅ Serve responsive images with srcset
  • ✅ Compress images to quality 75-85% — the difference is invisible
  • ✅ Use a CDN with auto-optimization

Try Our Free Developer Tools

Minify your HTML, CSS, and JavaScript for faster load times.