Image Optimization
Images are typically the heaviest assets on a web page, often accounting for over half of total page weight. Choosing the right format, compressing aggressively, serving responsive sizes, and deferring off-screen images can cut load times dramatically without sacrificing visual quality.
Hub: Graphics & Web Design Related: SVG Optimization | Web Performance
Modern Image Formats
WebP
WebP is supported by every modern browser. It handles both lossy and lossless compression, transparency, and animation. Compared to JPEG, WebP typically achieves 25-35 percent smaller files at equivalent quality.
AVIF
AVIF, based on the AV1 video codec, offers even better compression than WebP -- often 50 percent smaller than JPEG. Browser support has expanded rapidly (Chrome, Firefox, Safari 16+). Encoding is slower, so it is best used as part of a build pipeline rather than on-the-fly.
JPEG XL
JPEG XL supports lossless transcoding of existing JPEG files (same quality, ~20 percent smaller) and progressive decoding. Browser support is still limited, so it serves as a progressive enhancement behind a <source> fallback chain.
The <picture> Element
The <picture> element lets the browser choose the best format it supports:
<picture>
<source srcset="/img/hero.avif" type="image/avif" />
<source srcset="/img/hero.webp" type="image/webp" />
<img src="/img/hero.jpg" alt="Sunset over mountain range"
width="1200" height="600" loading="lazy" decoding="async" />
</picture>
The browser evaluates <source> elements top to bottom and picks the first format it can decode. The <img> tag at the end is the mandatory fallback.
Responsive Images with srcset and sizes
srcset lets you provide multiple resolutions so the browser can download only the size it needs:
<img
srcset="
/img/photo-400.webp 400w,
/img/photo-800.webp 800w,
/img/photo-1200.webp 1200w,
/img/photo-1600.webp 1600w"
sizes="
(max-width: 600px) 100vw,
(max-width: 1000px) 50vw,
33vw"
src="/img/photo-800.webp"
alt="Hiking trail through alpine meadow"
width="1600" height="900"
loading="lazy" />
The sizes attribute tells the browser how wide the image will be rendered at each viewport size. Combined with srcset, the browser can calculate the optimal download candidate before layout is complete.
Art Direction
Art direction uses <picture> with media queries to serve entirely different crops for different screen sizes:
<picture>
<source media="(max-width: 600px)"
srcset="/img/hero-portrait.avif" type="image/avif" />
<source media="(max-width: 600px)"
srcset="/img/hero-portrait.webp" type="image/webp" />
<source srcset="/img/hero-landscape.avif" type="image/avif" />
<source srcset="/img/hero-landscape.webp" type="image/webp" />
<img src="/img/hero-landscape.jpg" alt="Product showcase"
width="1200" height="600" />
</picture>
On narrow screens the portrait crop puts the subject front and center. On wide screens the landscape version provides more context.
Lazy Loading
Native lazy loading defers off-screen images until the user scrolls near them:
<img src="/img/section-photo.webp"
alt="Team collaboration session"
loading="lazy"
decoding="async"
width="800" height="450" />
Always include explicit width and height (or use the CSS aspect-ratio property) so the browser can reserve space before the image loads, preventing Cumulative Layout Shift.
Do not lazy-load the Largest Contentful Paint (LCP) image -- that hero image should load eagerly and may benefit from fetchpriority="high".
<img src="/img/hero.webp" alt="Main banner"
fetchpriority="high" decoding="async"
width="1200" height="600" />
Compression Tools
Squoosh (Browser-Based)
squoosh.app provides an interactive comparison UI for experimenting with formats, quality settings, and resize dimensions. It runs entirely in the browser via WebAssembly -- no uploads required.
Sharp (Node.js)
Sharp is the go-to library for automated image processing in build pipelines:
const sharp = require('sharp');
async function optimizeImage(inputPath, outputPath) {
await sharp(inputPath)
.resize({ width: 1200, withoutEnlargement: true })
.webp({ quality: 80, effort: 6 })
.toFile(outputPath);
}
// Generate multiple sizes for srcset
const widths = [400, 800, 1200, 1600];
for (const w of widths) {
await sharp('original.jpg')
.resize({ width: w })
.avif({ quality: 50 })
.toFile(`output-${w}.avif`);
}
Build Pipeline Integration
Integrate sharp (or imagemin) into your build so every image is optimized automatically:
// scripts/optimize-images.js
const fs = require('fs');
const path = require('path');
const sharp = require('sharp');
const INPUT_DIR = path.join(__dirname, '../src/images');
const OUTPUT_DIR = path.join(__dirname, '../dist/images');
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const files = fs.readdirSync(INPUT_DIR).filter(f => /\.jpe?g$/i.test(f));
(async () => {
for (const file of files) {
const input = path.join(INPUT_DIR, file);
const stem = path.parse(file).name;
// WebP variant
await sharp(input)
.resize({ width: 1200, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(path.join(OUTPUT_DIR, `${stem}.webp`));
// AVIF variant
await sharp(input)
.resize({ width: 1200, withoutEnlargement: true })
.avif({ quality: 50 })
.toFile(path.join(OUTPUT_DIR, `${stem}.avif`));
}
console.log(`Optimized ${files.length} images.`);
})();
CDN Delivery
A content delivery network caches images at edge locations close to users. Many CDNs (Cloudflare, Imgix, Cloudinary) also offer on-the-fly format conversion and resizing:
https://cdn.example.com/img/hero.jpg?w=800&fmt=auto
The fmt=auto parameter tells the CDN to negotiate the best format based on the browser's Accept header -- AVIF for Chrome, WebP for Safari, JPEG as fallback. This eliminates the need to generate every format variant at build time.
When using a CDN with automatic format negotiation, you can simplify your HTML:
<img srcset="
https://cdn.example.com/img/hero.jpg?w=400 400w,
https://cdn.example.com/img/hero.jpg?w=800 800w,
https://cdn.example.com/img/hero.jpg?w=1200 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
src="https://cdn.example.com/img/hero.jpg?w=800"
alt="Product detail photo"
loading="lazy" width="1200" height="800" />
The CDN handles format conversion per request. You only need to vary the width parameter.
By combining modern formats, responsive markup, lazy loading, automated compression, and CDN delivery, you can routinely cut image payload by 60-80 percent while maintaining visual quality across every device and connection speed.