Skip to content
Performance

Image Optimization Complete Guide

Everything about optimizing images for the web — formats, compression, responsive images, and lazy loading.

stellae.design

9 min read

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.

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

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

javascript
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

typescript
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

javascript
// 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

Performanceperformance

Was this article helpful?