Images are usually the heaviest part of a web page. Modern formats, WebP and AVIF, deliver the same visual quality at a fraction of JPEG and PNG sizes, and every current browser supports both. Converting your images, and serving them correctly, is one of the most effective speed improvements for any site.
Format comparison
| Format | Typical size vs JPEG | Transparency | Animation | Encode speed | Support |
|---|---|---|---|---|---|
| JPEG | baseline | No | No | Fast | Universal |
| PNG | larger (for photos) | Yes | No | Fast | Universal |
| WebP | 25–35% smaller | Yes | Yes | Fast | All modern browsers |
| AVIF | 40–55% smaller | Yes | Yes | Slow | All modern browsers |
AVIF compresses best, but encodes slowly. WebP is the safe, fast default. Many sites generate both, and serve AVIF when the browser accepts it.
Convert from the command line
sudo apt install -y webp libavif-bin libvips-tools
cwebp -q 80 photo.jpg -o photo.webp
avifenc --min 20 --max 35 -s 6 photo.jpg photo.avif # lower = better quality; -s speed 0–10
vips copy photo.jpg photo.webp[Q=80] # libvips: very fast for batches
Batch-convert a folder (keeping originals):
find /var/www/site/images -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) -print0 |
while IFS= read -r -d '' f; do
[ -f "$f.webp" ] || cwebp -quiet -q 80 "$f" -o "$f.webp"
[ -f "$f.avif" ] || avifenc -j all --min 20 --max 35 "$f" "$f.avif" >/dev/null
done
Encoding AVIF is CPU-heavy. Run large batches with nice at quiet times. See process priority with nice.
Serve them: option 1, the <picture> element
<picture>
<source srcset="/img/hero.avif" type="image/avif">
<source srcset="/img/hero.webp" type="image/webp">
<img src="/img/hero.jpg" width="1200" height="600" alt="Server rack" fetchpriority="high">
</picture>
The browser picks the first format it supports. Always keep width and height to avoid layout shifts. Most static site generators and frameworks (Astro, Next.js, Hugo) generate this automatically. See static site generators.
Option 2: Nginx content negotiation (same URL)
Serve photo.jpg.avif or photo.jpg.webp automatically when the browser’s Accept header allows it:
map $http_accept $img_suffix {
default "";
"~*image/avif" ".avif";
"~*image/webp" ".webp";
}
server {
# …
location ~* ^/images/.+\.(jpe?g|png)$ {
add_header Vary Accept;
try_files $uri$img_suffix $uri =404;
expires 30d;
}
}
Vary: Accept tells caches and CDNs that the response depends on the Accept header. With a CDN, make sure it respects Vary, or use the CDN’s own image optimisation. See Nginx static file caching.
Option 3: image CDNs and on-the-fly processing
- CDN image services (Cloudflare Images and Polish, Bunny Optimizer, and others) convert and resize at the edge.
- Self-hosted image servers (imgproxy, Thumbor) resize and convert on request, with caching.
That’s useful for user-uploaded images in many sizes. See CDN for websites.
WordPress
WordPress supports WebP and AVIF uploads (AVIF since 6.5, if the server’s image library supports it), and plugins convert existing libraries. See WordPress image optimization.
Quality tips
- Start around WebP q=75–82, and AVIF min/max 20–35. Compare visually at 100% zoom.
- Resize to the displayed dimensions first. Format savings don’t fix a 4000-pixel image shown at 800 pixels.
- Keep originals, so you can re-encode later with better encoders or settings.
- Measure with Lighthouse (“Serve images in next-gen formats”, and LCP). See web performance testing.
Quick reference
| Task | Command / config |
|---|---|
| JPEG → WebP | cwebp -q 80 in.jpg -o out.webp |
| JPEG → AVIF | avifenc --min 20 --max 35 in.jpg out.avif |
| Fast batches | vips copy in.jpg out.webp[Q=80] |
| HTML fallback | <picture> with AVIF, WebP, JPEG |
| Same URL | Nginx map $http_accept + try_files + Vary: Accept |
Related: website speed optimization and HTTP caching headers.