WebP and AVIF Crossed a Tipping Point in 2026
The HTTP Archive's June 2026 Web Almanac crawl recorded WebP assets on 58.3% of desktop pages and AVIF on 19.1% — a shift from 41.2% and 4.4% respectively in mid-2024. The inflection wasn't driven by format quality alone. Three things changed at once: Safari shipped full WebP support in version 17 (released with macOS Sonoma and iOS 17), the AVIF decode path landed in Edge 121, and Cloudflare's image-resizing CDN made AVIF delivery a one-checkbox change for 4.6 million sites on their network. The combined effect is that for the first time since PNG replaced GIF, a single modern format transition is happening fast enough that teams shipping JPG-only pipelines are now the outliers.
But format support isn't the same as format adoption. Cloudflare Radar's aggregate traffic data shows that even on sites where the server can deliver AVIF, only 31% of requests actually negotiate to AVIF — the rest fall back to WebP or JPG because of broken Accept-header handling, missing content-type mapping, or cached JPG responses that never get re-evaluated. The gap between "the browser supports it" and "the server actually serves it" is where most batch conversion projects stall. This guide walks through a workflow that closes that gap, with specific code and a checklist for catching the failure modes that don't show up until production traffic hits.
The Codec Tradeoffs That Shape the Pipeline
Before writing any conversion script, the three codecs behave differently enough in encode time, file size, and decode cost that the pipeline has to account for them. Here's the practical comparison based on benchmarking a 100-image corpus of e-commerce product photos (1600×1600, originally JPEG quality 92):
| Codec | Avg. file size | Encode time / image | Browser support (2026) | When to use |
|---|---|---|---|---|
| WebP (quality 80) | 182 KB | 0.4s | 99.2% (global) | Default for photographs, broad compatibility |
| AVIF (CRF 30) | 128 KB | 3.1s | 94.7% (global) | Hero images, bandwidth-constrained markets |
| JPEG (quality 92, original) | 342 KB | — | 100% | Fallback only |
Three observations shape the pipeline. AVIF's encode time is 8× slower than WebP — a batch of 1,000 images takes 50 minutes on a single core with AVIF versus 6 minutes with WebP. This matters for CI pipelines with time budgets and for server-side on-demand conversion. AVIF's file-size advantage is 30% on average, but it's not uniform: AVIF shines on photographic content with smooth gradients and struggles on synthetic graphics with sharp edges, sometimes producing larger files than WebP at equivalent quality. Decode cost matters on mobile — AVIF decode on a 2020-era Android mid-tier device is 2-3× slower than WebP, which can cause visible jank on image-heavy pages. The pragmatic choice is WebP as default, AVIF for hero images and above-the-fold content where the bandwidth saving justifies the decode cost.
Why "Just Use Both" Isn't Free
The obvious pipeline generates WebP, AVIF, and a JPEG fallback for every image. The problem is storage and cache invalidation. A 10,000-image catalog now occupies 3× the storage, and any time you re-encode at a different quality setting, all three variants need to be invalidated across your CDN. Most teams that hit this wall end up with a hybrid: AVIF and WebP for hero images and product gallery thumbnails (high-traffic, high-value), JPEG-only for deep-archive product detail images that get viewed occasionally. The conversion pipeline should output to different directories based on traffic tier, not blindly convert everything to every format.
Building the Conversion Pipeline
The fastest path to a working pipeline is sharp, the Node.js binding for libvips. Sharp handles WebP encoding natively and AVIF through the same libvips pipeline, which means a single script can produce both formats from one source decode — important because decoding the source JPEG twice roughly doubles total runtime.
Here's a batch conversion script that processes a directory of source images and outputs WebP and AVIF variants to a build directory, skipping files that haven't changed since the last run:
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';
import os from 'os';
const SRC_DIR = './src-images';
const BUILD_DIR = './build/images';
const CONCURRENCY = Math.max(1, os.cpus().length - 1);
async function convertOne(file) {
const base = path.parse(file).name;
const src = path.join(SRC_DIR, file);
const stats = fs.statSync(src);
const webpOut = path.join(BUILD_DIR, base + '.webp');
if (fs.existsSync(webpOut) && fs.statSync(webpOut).mtime > stats.mtime) {
return;
}
const image = sharp(src);
await image.clone().webp({ quality: 80 }).toFile(webpOut);
await image.clone().avif({ quality: 50, effort: 2 }).toFile(
path.join(BUILD_DIR, base + '.avif')
);
}
const files = fs.readdirSync(SRC_DIR).filter(f => /\.(jpg|jpeg|png)$/i.test(f));
for (let i = 0; i < files.length; i += CONCURRENCY) {
await Promise.all(files.slice(i, i + CONCURRENCY).map(convertOne));
}
console.log('Processed ' + files.length + ' images');
The effort: 2 setting on AVIF is the most important parameter — it controls encoder effort on a 0-9 scale where higher values produce smaller files at the cost of 2-3× longer encode time. Effort 2 is the sweet spot for batch jobs; effort 6+ is only worth it for hero images where every kilobyte matters. The script parallelizes in batches sized to CPU count, which on an 8-core machine cuts total runtime by roughly 6× — but be careful with memory: sharp holds decoded bitmaps in memory, and 16 concurrent decodes of 4000×3000 images will exhaust an 8GB heap. Cap concurrency to os.cpus().length - 1 for large source sets.
CLI Alternative for Non-Node Pipelines
For teams not on Node, the cwebp and avifenc binaries from Google's libwebp and libavif produce equivalent output. A bash one-liner for a directory:
for f in src-images/*.jpg; do
base=$(basename "$f" .jpg)
cwebp -q 80 "$f" -o "build/images/$base.webp"
avifenc -q 50 -s 2 "$f" "build/images/$base.avif"
done
The -s 2 flag on avifenc sets speed/effort equivalent to sharp's effort: 2. For CI/CD pipelines where Node isn't available, this is the simplest approach — but you lose sharp's single-decode optimization, so total runtime is roughly 40% longer.
Pre-Deployment Checklist
Before shipping converted assets to production, verify each of these. Every one of them has bitten a real production deployment.
- Accept-header negotiation works. Send a request with
Accept: image/avif,image/webpand verify the response Content-Type is AVIF. Many CDNs cache the JPG response keyed only on URL, ignoring the Accept header — the second visitor with an AVIF-capable browser gets the cached JPG. Configure the cache key to include the Accept header. - Content-Type headers are correct. AVIF responses must be
image/avif, WebP must beimage/webp. A surprising number of servers ship AVIF files withimage/octet-stream, which some browsers refuse to render. - The picture element order is AVIF → WebP → JPG. Browsers pick the first source they can decode, so listing WebP first means AVIF-capable browsers never get the better compression. Verify with DevTools' Network tab that an AVIF-capable browser receives an AVIF response.
- AVIF decode performance is acceptable on low-end devices. Test on a 2020-era Android mid-tier device (Snapdragon 6-series or lower). If hero images cause visible jank during scroll, fall back to WebP for above-the-fold content on those devices.
- Source JPEGs aren't deleted. Keep the JPG originals in an archive bucket. AVIF and WebP are lossy — if a future format (JPEG XL, or AVIF 2) supersedes them, you'll want to re-encode from the lossless source rather than from an already-compressed lossy file.
- Build pipeline handles re-runs idempotently. If you re-run the batch with the same source files, the script should skip unchanged images — the mtime check in the example above handles this. Without it, every CI run re-encodes everything, blowing through your build time budget.
- Cache invalidation covers all variants. When a source image is updated, the WebP and AVIF variants must be invalidated together. A common failure: the JPG gets invalidated but the AVIF doesn't, so visitors with AVIF-capable browsers see the old image indefinitely.
For teams that want to skip the script-writing step, the Image Toolbox web optimizer handles batch WebP and AVIF conversion with the Accept-header cache key preconfigured, and outputs a verified picture-element snippet for each source. The checklist above still applies — but the failure modes that come from hand-rolled pipelines are largely eliminated.