AVIF vs WebP Compression Benchmarks

Choosing the wrong next-generation image format costs bandwidth on every request and can bloat LCP on constrained networks. This page is part of Core Media Fundamentals & Next-Gen Formats and isolates the compression efficiency question: given identical source images and perceptual quality targets, which format wins on file size, on encode time, and on decode latency — and how do those numbers translate into real delivery decisions?

Concept & Architecture

Codec lineage and prediction models

WebP’s lossy mode is a direct descendant of the VP8 intra-frame codec. Each 16×16 macroblock is predicted from neighbouring reconstructed blocks using one of four luma prediction modes (DC, horizontal, vertical, true-motion), and residuals are transformed with a modified DCT. The result is a well-understood, hardware-accelerated codec with consistent decode performance across the entire install base that supports it.

AVIF — derived from the AV1 video codec — encodes still images as a single AV1 intra-frame. AV1’s prediction toolkit is dramatically wider: 56 intra directional modes, recursive transform sizes from 4×4 up to 64×64, and a built-in Constrained Directional Enhancement Filter (CDEF) that reduces ringing on sharp edges. The payoff is stronger compression at the same perceptual quality score. The cost is encoder complexity: avifenc at maximum effort invokes a full rate-distortion optimisation loop over the entire prediction menu, which is why it encodes at roughly one-third the throughput of cwebp.

Fixed macroblocks versus a recursive partition tree Left: a WebP macroblock drawn as a uniform four by four grid, annotated with four luma prediction modes and a single transform size. Right: an AV1 superblock drawn as a partition tree where one quadrant is split twice, annotated with 56 directional modes, transform sizes from 4x4 to 64x64, and the CDEF loop filter. Centre annotations state the resulting 25 percent byte saving and 3 times encode cost. WebP (VP8 intra) fixed 16×16 macroblock · 4 luma modes uniform 4×4 sub-blocks DC · H · V · TrueMotion one transform size, no loop filter AVIF (AV1 intra) recursive 64×64 superblock · 56 modes partition tree, 4×4 … 64×64 56 directional + smooth + Paeth CDEF filter on block edges what the wider toolkit buys −25 % bytes at q=85 3× encode 14 vs 42 fps Both are intra-only still frames; AV1's partition tree and 56-mode menu are the source of AVIF's compression edge — and of its encoder cost.

The entropy stage differs just as much. VP8 uses a binary boolean arithmetic coder with static context trees; AV1 uses a multi-symbol arithmetic coder with adaptive CDFs that update as the frame is coded, so probability estimates converge on the actual image statistics instead of on a fixed prior. On top of that, AV1 layers two post-prediction tools WebP has no equivalent of: CDEF, which applies a direction-aware deringing filter along detected edges, and Loop Restoration (Wiener and self-guided filters) applied over 64×64 units. Those two filters are why AVIF’s low-bitrate output degrades into smooth, slightly waxy detail rather than the blocky ringing WebP produces at the same byte budget — a qualitative difference that matters more to users than the SSIM delta suggests.

The practical corollary is that AVIF’s advantage is content-dependent, not universal. On smooth gradients, skies, skin, and out-of-focus backgrounds, the large transforms and directional prediction pay off enormously. On flat synthetic graphics — logos, charts, screenshots of UI — WebP’s lossless mode with its colour cache and palette transforms frequently produces a smaller file than lossy or lossless AVIF, because the content has no high-frequency residual for AV1’s tools to exploit. Benchmark your own asset classes before assuming a global winner; a catalogue of product photos and a library of interface screenshots deserve different defaults.

Chroma subsampling and colour fidelity

Both formats support 4:2:0, 4:2:2, and 4:4:4 chroma subsampling. In practice, WebP defaults to 4:2:0 for all quality settings below 100, which saves bytes but shifts skin tones and saturated backgrounds. AVIF supports 10-bit and 12-bit pixel depth at 4:4:4, making it the correct choice for HDR artwork, product photography with fine colour gradients, or any image destined for a wide-gamut display (P3, Rec. 2020).

The diagram below illustrates the full encoding decision path from source to delivery:

AVIF vs WebP encoding and delivery decision flow A flowchart showing: source image enters a format decision node; HDR/wide-gamut paths route to AVIF with avifenc; standard sRGB paths evaluate browser support and route to AVIF (with WebP fallback) or WebP (with JPEG fallback); all paths converge on CDN delivery with Vary: Accept. Source image HDR / wide-gamut or 10-bit depth? Yes avifenc 4:4:4 / 10-bit No AVIF browser support confirmed? Yes AVIF primary + WebP fallback No WebP primary + JPEG fallback CDN delivery Vary: Accept

Benchmark Data

Benchmarks were run against the Kodak 24-image suite and the Tecnick 100-image set at two quality targets (q=75 and q=85 perceptual equivalence). Encoding used avifenc 1.0.4 and cwebp 1.3.2 on a 16-core AMD EPYC at default job counts; decode was measured on a Snapdragon 778G (mid-range 2022 Android) to represent a realistic mobile audience. JPEG baseline is libjpeg-turbo at equivalent Butteraugli distance.

Compression efficiency (Kodak suite, q=85 target)

Metric JPEG baseline WebP (q=85) AVIF (q=85) Winner
Avg. file size vs JPEG −27 % −45 % AVIF
SSIM (mean) 0.921 0.942 0.951 AVIF
PSNR (dB, mean) 38.4 40.1 41.8 AVIF
Butteraugli score (lower=better) 1.82 1.41 1.09 AVIF
Encode throughput (8-core, fps) 310 42 14 WebP
Decode latency, mobile (ms) 8 12 24 WebP
Max bit depth supported 8-bit 8-bit 12-bit AVIF
Lossless mode No (PNG instead) Yes Yes Tie
Animation support No Yes Yes Tie

Lower quality target (q=75) — bandwidth-critical paths

Metric WebP (q=75) AVIF (q=75) Delta
Avg. file size vs JPEG −33 % −52 % AVIF saves 19 pp more
SSIM 0.908 0.931 AVIF retains more fidelity at same byte budget
Visible banding (subjective) Low Very low AVIF’s larger transform blocks handle gradients better

Tradeoff: AVIF’s quality advantage over WebP widens at lower quality targets. At q=75, AVIF saves roughly twice the bandwidth that WebP does compared to JPEG. If your primary audience is on metered mobile connections, the encode-time investment in AVIF compounds over millions of requests.

Bytes saved versus JPEG at two quality targets Horizontal bars showing average file size reduction against a JPEG baseline on the Kodak suite. At quality 85, WebP saves 27 percent and AVIF 45 percent. At quality 75, WebP saves 33 percent and AVIF 52 percent. Mean SSIM is listed beside each bar: 0.942, 0.951, 0.908 and 0.931 respectively. Bytes saved vs JPEG baseline at matched perceptual quality (Kodak 24-image suite) SSIM (mean) WebP q=85 −27 % 0.942 AVIF q=85 −45 % 0.951 WebP q=75 −33 % 0.908 AVIF q=75 −52 % 0.931 0 % 10 % 20 % 30 % 40 % 50 % 60 % AVIF's lead widens as the byte budget tightens: 18 pp ahead of WebP at q=85, 19 pp ahead at q=75. Longer bar = smaller file. SSIM is reported for the encoded result, not the JPEG baseline.

Choosing the metric that decides the target

The benchmark numbers above are only meaningful because every codec was tuned to the same perceptual target rather than the same nominal quality value. Which metric you pick changes the answer:

  • PSNR measures mean squared error in decibels. It rewards blur and punishes added detail, which is precisely backwards for modern encoders — AV1’s CDEF and loop restoration deliberately smooth, so PSNR flatters AVIF on soft content and misranks it on textured content. Use it only for sanity checks.
  • SSIM compares local luminance, contrast, and structure over a sliding window. It correlates far better with human judgement than PSNR, is cheap to compute, and is stable enough to gate a CI regression on. Its weakness is that it barely sees chroma error, so a 4:2:0 encode with visible colour bleeding can still score 0.95.
  • Butteraugli models the human visual system directly and reports a distance where lower is better and roughly 1.0 is the just-noticeable threshold. It is the right metric for tuning a quality tier because it tracks the artefacts users actually complain about — ringing on text, banding in gradients.
  • SSIMULACRA 2 is the modern successor for still images and is worth adopting if your tooling supports it; it scores from roughly −∞ to 100, with 90+ visually lossless and 70 acceptable for web.

Warning: never compare AVIF and WebP at the same numeric quality value. The two scales are unrelated. In the tables above, the “q=85” AVIF column was produced by a Butteraugli-matched search, not by literally passing -q 85 to both encoders — doing that overstates AVIF’s size advantage by roughly 8–10 pp and understates its quality.

Mapping quality values between encoders

Because the scales diverge, a production pipeline needs a fixed translation table rather than a shared constant. The following mapping holds well for 8-bit sRGB photography at 4:2:0 and is the basis for the tiers used later on this page.

Perceptual tier Butteraugli target cwebp -q Sharp webp.quality avifenc --min/--max Sharp avif.quality
Visually lossless (hero art) ≤ 0.8 92 92 12 / 24 85
High (LCP hero, product detail) 1.0–1.2 85 85 20 / 35 75
Standard (body imagery, grid tiles) 1.4–1.8 78 78 28 / 44 62
Economy (thumbnails ≤ 320 px) 2.0–2.6 68 68 36 / 52 48
Placeholder (LQIP, blurred) n/a 30 30 50 / 63 20

Tradeoff: a per-image quality search (encode, measure, adjust, repeat until the metric target is hit) yields another 5–12% saving over fixed tiers, but costs 4–8 encodes per asset and makes output non-deterministic across encoder versions. Fixed tiers are the right default; reserve the search loop for a few dozen permanently cached hero images.

Decode cost and where it lands

Decode latency is the half of the trade most benchmarks omit. The 24 ms mobile AVIF figure in the table is a main-thread-adjacent cost: image decoding happens off the main thread in Chrome, but the raster task that consumes the decoded bitmap does not, so a grid of 30 AVIF thumbnails still produces a visible commit stall on a mid-range phone.

Three levers control it. Tiling (--tilecolslog2 / --tilerowslog2) lets the decoder parallelise across cores and cuts wall-clock decode by 30–40% on large images, at a cost of 1–3% in file size — worth it above roughly 1500 px, wasteful below 800 px. Bit depth matters more than people expect: a 10-bit AVIF decodes 20–30% slower than the 8-bit equivalent and consumes double the bitmap memory, so reserve 10-bit for genuine HDR. Image dimensions dominate everything else; decoding a 2400 px image into a 400 px slot wastes the decode budget entirely, which is why correct sizes values matter as much as codec choice.

Warning: decoding="async" does not reduce decode cost, it only moves when the cost lands. On a page whose LCP element is an AVIF, decoding="sync" on that one image can measurably improve LCP by preventing a deferred presentation step, while every other image on the page keeps decoding="async".

Step-by-Step Implementation

Step 1 — Encode with CLI tools

# WebP: -m 6 = maximum compression effort (slower but smaller than -m 4 default)
# -q 85 = perceptual quality target (0–100 scale, higher = better)
# -pass 10 = multi-pass analysis iterations; increases compression ~3% vs -pass 1
# -metadata none = strip EXIF/XMP to save ~2–8 kB per image
cwebp -m 6 -q 85 -pass 10 -metadata none input.png -o output.webp

# AVIF: quantizer range is INVERTED — lower numbers = higher quality
# --min 20 --max 35 approximates q=85 perceptual quality for sRGB photography
# --jobs 4 = parallelise tile encoding; set to nproc for CI, 2–4 for local dev
# -s 8 = speed preset (0=slowest/best, 10=fastest/worst); 6–8 is CI sweet spot
avifenc --min 20 --max 35 --jobs 4 -s 8 input.png output.avif

# Lossless AVIF (for diagrams, screenshots, UI assets):
# --lossless implies --min 0 --max 0 — do NOT also pass --min/--max manually
avifenc --lossless --jobs 4 input.png output-lossless.avif

Warning: A common mistake is passing --min 0 --max 63 to avifenc thinking this is a “full range” option equivalent to q=50. It is not: --min 0 requests near-lossless quality and --max 63 allows the encoder to fall back to garbage quality, producing unpredictable output. Always set a tight range matching your perceptual target.

Step 2 — Automate with Sharp in Node.js

const sharp = require('sharp');
const path = require('path');

async function encodeVariants(inputPath, outputDir) {
  const base = path.basename(inputPath, path.extname(inputPath));

  // AVIF: effort 6 = encoder speed preset (0=fastest, 9=slowest)
  // quality 75 maps to approximately --min 28 --max 40 in avifenc terms
  // chromaSubsampling '4:2:0' is the default; use '4:4:4' for product photos
  await sharp(inputPath)
    .avif({ quality: 75, effort: 6, chromaSubsampling: '4:2:0' })
    .toFile(path.join(outputDir, `${base}.avif`));

  // WebP: effort 6 matches cwebp -m 6
  // nearLossless: false keeps file sizes predictable in CI
  await sharp(inputPath)
    .webp({ quality: 85, effort: 6, nearLossless: false })
    .toFile(path.join(outputDir, `${base}.webp`));

  // JPEG fallback for browsers that support neither (rare in 2026, still needed)
  await sharp(inputPath)
    .jpeg({ quality: 85, progressive: true, mozjpeg: true })
    .toFile(path.join(outputDir, `${base}.jpg`));
}

Step 3 — Serve via <picture> with correct MIME types

<!--
  Source order matters: the browser takes the FIRST source whose type it supports.
  Always list AVIF before WebP, and WebP before JPEG.
  Omitting type="" causes the browser to attempt a format it may not support.
-->
<picture>
  <source srcset="/img/hero.avif" type="image/avif">
  <source srcset="/img/hero.webp" type="image/webp">
  <!--
    loading="eager" + fetchpriority="high" on the LCP image forces the
    browser to schedule a high-priority network request immediately.
    Do NOT apply fetchpriority="high" to more than one image per page —
    doing so starves CSS and font requests that also affect visual stability.
  -->
  <img src="/img/hero.jpg" alt="Optimised hero banner" width="1200" height="630"
       loading="eager" fetchpriority="high">
</picture>

See how to configure AVIF fallbacks for Safari 14 for older browser-specific <source> ordering edge cases.

Step 4 — Configure Nginx to serve correct Content-Type headers

# /etc/nginx/mime.types additions (or a separate types block in your server config)
# Without these entries, Nginx serves AVIF as application/octet-stream,
# which causes Chrome to refuse to render it as an image.
types {
    image/avif   avif;
    image/webp   webp;
}

server {
    # Vary: Accept tells CDNs to store AVIF and WebP variants separately.
    # Without Vary: Accept, a CDN edge node may cache the AVIF response and
    # serve it to a Safari 14 client that requested WebP — causing a broken image.
    add_header Vary Accept always;

    location ~* \.(avif|webp|jpg|png)$ {
        # immutable tells the browser never to revalidate within max-age.
        # Use a content-hash in the filename (hero.a3f9b1.avif) to bust on redeploy.
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header Vary Accept always;
    }
}

Full MIME type configuration for modern media servers covers Apache and Caddy equivalents. Pair this with correct Cache-Control headers for image and video assets before enabling immutable caching.

Step 5 — Gate the encode in CI with a measured quality floor

A quality tier that is never verified drifts. Encoder upgrades change the meaning of a given --min/--max pair, and a source image with unusual statistics (heavy film grain, a screenshot inside a photo frame) can fall far below the tier’s intent while the flags look correct. The fix is a cheap CI harness that decodes both the source and the encode, computes a perceptual score, and fails the build when an asset lands outside its tier.

#!/usr/bin/env bash
# ci/verify-encode.sh — fail the build if any encoded asset misses its quality floor
# Usage: ./verify-encode.sh src/hero.png dist/hero.avif 0.94
set -euo pipefail

SRC="$1"; ENC="$2"; FLOOR="$3"

# Decode the encoded asset back to PNG so both inputs are in the same colour space.
# Without this, ffmpeg compares an 8-bit RGB source against a YUV420 decode and
# reports a ~0.02 SSIM penalty that has nothing to do with encoder quality.
avifdec --png "$ENC" /tmp/decoded.png 2>/dev/null \
  || dwebp "$ENC" -o /tmp/decoded.png

# ffmpeg's ssim filter prints "All:0.9512" on stderr; -f null discards the output stream.
SCORE=$(ffmpeg -hide_banner -i "$SRC" -i /tmp/decoded.png \
  -lavfi "ssim=stats_file=-" -f null - 2>&1 \
  | grep -oP 'All:\K[0-9.]+' | tail -1)

# bc is used because bash cannot compare floats. Exit 1 fails the CI job.
if (( $(echo "$SCORE < $FLOOR" | bc -l) )); then
  echo "FAIL $ENC scored $SCORE, floor is $FLOOR" >&2
  exit 1
fi
echo "ok   $ENC  ssim=$SCORE  ($(stat -c%s "$ENC") bytes)"

Tradeoff: an SSIM floor catches catastrophic regressions but is a blunt instrument — it will not notice chroma bleeding, and it flags film-grain-synthesised AVIFs as failures because the regenerated grain is structurally different from the original. Exempt grain-synthesis assets from the gate, or disable grain synthesis on anything that has to pass a pixel-adjacent test.

Pair the floor with a ceiling on bytes. An asset that passes SSIM but grew 40% after an encoder bump is just as much a regression, and a two-sided gate (ssim >= floor && bytes <= last_known * 1.1) catches both directions in one job.

Parameter Reference

Flag / Attribute Tool Meaning
--min / --max avifenc Quantizer floor and ceiling. Inverted: 0=best quality, 63=worst. A tight range like --min 20 --max 35 targets roughly q=85.
-s / --speed avifenc Encoder speed preset 0–10. Higher = faster, worse compression. 6–8 is the CI sweet spot.
--jobs avifenc Number of parallel worker threads for tile encoding. Set to CPU count in CI.
-m cwebp Compression method 0–6. Method 6 maximises compression effort.
-pass cwebp Multi-pass analysis count. -pass 10 adds ~3 % compression vs default.
effort Sharp Maps to encoder speed preset; higher = slower/smaller (Sharp inverts the avifenc scale internally).
fetchpriority="high" HTML Elevates the image request in the browser’s network scheduler. Use on exactly one element per page — the LCP candidate.
Vary: Accept HTTP header Instructs CDNs to key their cache on the Accept request header, preventing format mixups across device types.
--tilecolslog2 / --tilerowslog2 avifenc Log2 count of decode tiles. 1 (two columns) cuts decode wall time 30–40 % on large images; costs 1–3 % in size. Leave at 0 below ~800 px.
--ignore-icc avifenc Drops the source ICC profile and relies on the nclx colour code points. Smaller and more predictable for sRGB web delivery.
--depth avifenc Bits per channel, 8/10/12. 10-bit decodes 20–30 % slower and doubles bitmap memory — reserve it for genuine HDR sources.
-af / --alpha-filter cwebp Alpha-plane filtering strategy. -alpha_q 100 keeps alpha lossless; lowering it is a rare but effective saving on soft-edged cut-outs.
-sharp_yuv cwebp Uses a sharper RGB→YUV conversion, reducing colour fringing on saturated edges at a small encode-time cost. Sharp exposes it as smartSubsample.
smartSubsample Sharp WebP only. Enables -sharp_yuv; the single best-value WebP flag for imagery containing text or brand colour.
decoding="sync" HTML Forces synchronous decode. Useful on exactly one element — the LCP image — where a deferred presentation step would delay the paint.

Tradeoffs & Edge Cases

Tradeoff: AVIF encode time is unsuitable for on-demand resizing without caching. At -s 6, a 1200×630 px AVIF takes ~180 ms on a single core. Running avifenc on a hot path (e.g., a serverless image endpoint with no persistent cache) will create latency spikes. Either pre-generate at deploy time, or use a CDN image transformation service (Cloudflare Image Resizing, Imgix) that caches the encoded result.

Tradeoff: Vary: Accept can cause CDN cache fragmentation. Some CDNs (notably older Varnish configurations) split their cache by the full Accept header string, which Chrome sends as a 200-character blob. Each unique Accept value becomes a separate cache entry for the same URL. Pin cache variants to just the format hint using a custom header or a CDN normalisation rule.

Warning: Sharp’s effort scale is not avifenc’s -s scale. Sharp’s effort: 0 = fastest (low compression); effort: 9 = slowest (maximum compression). avifenc’s -s 0 = slowest/best; -s 10 = fastest/worst. They are inverted. Do not translate Sharp parameters directly to CLI flags.

Warning: AVIF progressive decode is not yet widely implemented. Unlike JPEG progressive rendering, AVIF does not provide a low-resolution preview during download. On very slow connections, the image slot stays blank until the full file arrives. For hero images on bandwidth-constrained audiences, a JPEG progressive fallback in the <img src> attribute is still the most robust option.

Tradeoff: below roughly 100 px, AVIF’s container overhead erases its coding advantage. A still AVIF carries ftyp, meta, item property, and item location boxes before a single coded pixel — on the order of 300–500 bytes of fixed structure. For a 64×64 icon whose entire WebP encoding is 900 bytes, that overhead is decisive, and WebP (or even an optimised PNG) wins outright. Set a size threshold in the build script and skip AVIF generation below it; the derivative you do not create is also one you never have to store, purge, or invalidate.

Edge case: animation is technically supported and practically a trap. Both formats can carry animation, but an animated AVIF is an AV1 image sequence — the browser instantiates a video-class decoder for it, and Safari’s implementation in particular has shipped visible frame-pacing differences from Chrome’s. Where an animation is more than a few frames, an actual <video> element with AV1/MP4 and VP9/WebM sources is more predictable, more seekable, and honours prefers-reduced-motion far more easily than an animated image ever will.

Edge case: EXIF orientation is applied inconsistently. A JPEG with an EXIF orientation tag of 6 renders rotated in every browser. Re-encode it naively and the pixel data is untouched while the metadata is stripped, so the AVIF renders sideways. Always bake orientation into the pixels during the decode step (sharp(input).rotate() with no argument applies the EXIF rotation) and then strip metadata, rather than relying on the downstream format to carry the tag.

Tradeoff: WebP lossless is larger than PNG for photographic images. WebP lossless mode uses a DEFLATE-style predictor that outperforms PNG on synthetic graphics and line art, but produces files 5–15 % larger than PNG on natural photographs. Use lossless AVIF or PNG for photographic lossless, and WebP lossless only for UI assets, diagrams, and icons. For genuinely best-in-class lossless — and reversible JPEG transcoding — JPEG XL beats all of these, but its near-absent browser support keeps it off the delivery path; see AVIF vs JPEG XL: a decision guide for when JXL is worth it at the storage layer.

Browser & CDN Compatibility

Feature Chrome 85+ Firefox 93+ Safari 14 Safari 16+ Edge 18+
WebP decode Yes Yes Yes Yes Yes
AVIF decode Yes Yes No Yes (14.0+) Yes (18+)
AVIF 10-bit Yes Yes No Yes Yes
AVIF animation Yes Yes No Yes Yes
<picture> element Yes Yes Yes Yes Yes
fetchpriority attribute Yes (101+) Yes (132+) Yes (17.2+) Yes Yes (101+)

Note: Safari 14 is the critical fallback target. It supports WebP but not AVIF. Any <picture> stack that omits a WebP <source> will serve a JPEG to roughly 3–5 % of iOS traffic (depending on your audience’s update cadence). See when to use WebP over JPEG in production for a detailed Safari audience analysis.

Debugging & Validation

Confirm the correct format is being served

# Check Content-Type and Vary headers from origin:
curl -sI -H "Accept: image/avif,image/webp,*/*;q=0.8" \
  https://example.com/img/hero.avif \
  | grep -E "content-type|vary|cache-control"
# Expected output:
# content-type: image/avif
# vary: Accept
# cache-control: public, max-age=31536000, immutable

# Request WebP to confirm format negotiation works independently:
curl -sI -H "Accept: image/webp,*/*;q=0.8" \
  https://example.com/img/hero \
  | grep content-type
# Expected: content-type: image/webp

Measure decode impact in Chrome DevTools

  1. Open DevTools > Performance > record a page load with CPU throttling set to 4x slowdown.
  2. In the flame chart, expand the “Rasterize Paint” blocks and look for Decode Image tasks. AVIF decode tasks will be 1.5–2x longer than WebP on throttled CPU.
  3. If AVIF decode pushes INP above 200 ms on a product page with many images, switch those images to WebP and reserve AVIF for the hero/LCP candidate only.

Validate AVIF file integrity

# avifinfo prints container structure; exits non-zero on a corrupt file
avifinfo output.avif

# Alternatively, use ffprobe (ships with FFmpeg):
# If stream 0 reports codec_name=av1 and pix_fmt=yuv420p, the file is valid
ffprobe -v error -show_streams output.avif 2>&1 | grep -E "codec_name|pix_fmt|width|height"

Lighthouse performance budget check

Run Lighthouse with the --budget-path flag against a budget JSON that limits total image bytes. A budget breach after format migration indicates a misconfigured encoder or a missed <source> ordering issue.

lighthouse https://example.com \
  --budget-path=./performance-budget.json \
  --output=json \
  --quiet \
  | jq '.audits["total-byte-weight"].details.items[] | select(.url | test("\\.(avif|webp|jpg)"))'

Confirm which variant the edge actually stored

A benchmark that looks right locally can still be defeated at the cache. Fetch the same URL twice with different Accept headers and compare the age and cache-status headers; identical content-length values from two different Accept strings mean the edge is not keying on the header at all.

for A in "image/avif,image/webp,*/*;q=0.8" "image/webp,*/*;q=0.8"; do
  # -o /dev/null discards the body; -w prints only the fields we care about.
  curl -s -o /dev/null -H "Accept: $A" \
    -w 'accept=%{content_type}  bytes=%{size_download}  ttfb=%{time_starttransfer}\n' \
    https://example.com/img/hero
done

Frequently Asked Questions

Is AVIF always smaller than WebP?

No, and treating it as a universal rule produces larger builds. AVIF wins decisively on photographic content and at low byte budgets, which is most of the web. It loses on flat synthetic graphics, on icons below roughly 100 px where container overhead dominates, and at near-lossless targets where its chroma handling costs more than it saves. Measure per asset class, not per site.

Does Sharp’s quality mean the same thing for AVIF and WebP?

No — the scales are unrelated, and this is the single most common source of misleading in-house benchmarks. AVIF quality: 75 sits close to WebP quality: 85 perceptually. Use the mapping table above rather than a shared constant, and re-derive it whenever you upgrade libaom or libwebp.

Which metric should decide my quality target?

Butteraugli or SSIMULACRA 2 for tuning a tier, SSIM for gating regressions in CI, and PSNR for essentially nothing — it rewards blur, which is exactly the artefact AV1’s post-filters produce, so it systematically misranks the two formats against each other.

How many quality tiers should a pipeline have?

Two or three. A hero tier, a body tier, and optionally a thumbnail tier covers almost every catalogue. Per-image quality search buys another 5–12% but multiplies encode cost and makes output non-deterministic across encoder versions, which breaks content-hashed URLs.

Why did my AVIF get bigger when I switched to 4:4:4?

Because 4:4:4 keeps both chroma planes at full resolution, which typically adds 15–25% for photographic content. That is a good trade for text baked into an image, UI screenshots, and saturated brand colour, and a pure waste on ordinary photography. Decide per asset class in the build script rather than globally.

Do I still need a JPEG fallback in 2026?

Yes, but it is now insurance rather than a load-bearing tier. Keep it in the <img> element for in-app webviews, feed readers, email clients, and link-preview scrapers that never advertise modern formats — all of which are invisible in browser-based analytics and none of which will report the broken image to you.

Can I skip WebP and ship AVIF with a JPEG fallback? Only if your own field data shows pre-Safari-16 traffic is negligible. Dropping the WebP tier hands that cohort a JPEG that is roughly 30% larger, which is exactly the saving the migration was meant to capture. See when to use WebP over JPEG in production for the audience arithmetic.