Images are usually the heaviest part of a web page, and the hero image is often the element that decides your Largest Contentful Paint (LCP). Choosing the right format is one of the cheapest performance wins available, and in 2026 the choice for most teams comes down to WebP and AVIF.
Most comparisons online repeat the same headline figures without saying how they were produced. So instead of quoting someone else's numbers, we ran a small benchmark of our own with sharp, the Node.js image library many build pipelines already use. Below you will find the method, the results, the caveats, and the implementation patterns we use in React projects.
The two formats in brief
WebP was released by Google in 2010 and is based on the VP8 video codec. It supports lossy and lossless compression, transparency and animation. Encoding is fast, tooling is mature, and every current browser supports it.
AVIF stores AV1-compressed images in the HEIF container and was finalised in 2019. It also supports transparency, animation, wide colour gamut and HDR, and generally compresses better than WebP. The cost is encoding time: AVIF encoders do far more work, as the numbers below show.
Our benchmark
Method
- Tooling: sharp 0.35.5 (libvips 8.18.7, libwebp 1.6.0, libaom 3.15.1 via libheif) on Node.js 22, running on a 4-vCPU Intel Xeon (2.1 GHz) cloud VM.
- Images: 12 photographs (sports grounds, offices, a cityscape, hardware close-ups) resized to 1200 px wide, which works out to 0.6 to 1.8 megapixels each. We also tested a synthetic dashboard screenshot (flat colours, small text, a bar chart) and a logo on a white background.
- Encoders: JPEG via sharp's mozjpeg mode as the baseline, WebP, and AVIF. Each image was encoded across the full quality range (JPEG and WebP at quality 30 to 95, AVIF at 20 to 90, in steps of 5) with default effort settings.
- Quality metric: each output was decoded and compared with the original using SSIM (via ffmpeg's
ssimfilter). For each image we interpolated the file size each format needs to reach the same SSIM, then took the median across images.
Treat this as a small, honest experiment rather than a definitive study. The caveats are listed after the results.
File size at matched quality: photos
Median file size per photo (1200 px wide) needed to reach the same SSIM score. Higher SSIM means closer to the original:
| Target SSIM | JPEG (mozjpeg) | WebP | AVIF | AVIF vs WebP |
|---|---|---|---|---|
| 0.960 (aggressive) | 109 KB | 84 KB | 58 KB | 27% smaller |
| 0.975 (typical web) | 148 KB | 121 KB | 93 KB | 23% smaller |
| 0.985 (high quality) | 192 KB | 177 KB | 139 KB | 20% smaller |
Medians over 11 of the 12 photos (one image's quality curves did not span all three targets). The last column is the median of per-image ratios, so it does not exactly match the size columns.
What stood out:
- AVIF beat WebP on every photo, but the margin varied a lot from image to image: from about 13% to about 49% smaller depending on content and quality level.
- The AVIF advantage shrinks as quality rises. It is largest at the aggressive settings used for thumbnails and backgrounds.
- WebP's lead over a good JPEG encoder is modest. WebP came out roughly 17 to 22% smaller than mozjpeg at the same SSIM. Many published comparisons use a plain libjpeg baseline, which makes WebP look better.
Default settings are not equal quality
A common trap is comparing formats at their default quality settings. With sharp's defaults on the same photos:
| Default setting | Median size | Median SSIM |
|---|---|---|
JPEG quality: 80 (mozjpeg) | 114 KB | 0.965 |
WebP quality: 80 | 101 KB | 0.967 |
AVIF quality: 50 | 55 KB | 0.958 |
AVIF at its default looks about half the size of WebP, but part of that saving comes from slightly lower fidelity. The quality numbers are not comparable between encoders, so tune each format separately against your own images.
UI screenshots and logos
Flat colours and sharp text behave very differently from photos. At sharp's default settings:
| Image | JPEG q80 | WebP q80 | AVIF q50 |
|---|---|---|---|
| Dashboard screenshot | 49 KB (SSIM 0.984) | 33 KB (0.988) | 18 KB (0.9985) |
| Logo on white | 22 KB (0.982) | 10 KB (0.989) | 5 KB (0.989) |
On the screenshot, AVIF was both smaller and clearly more faithful. Part of the reason is chroma subsampling: sharp encodes AVIF at full colour resolution (4:4:4) by default, while lossy WebP always halves colour resolution (4:2:0), which smudges coloured text and thin lines. For UI images, also consider lossless WebP or a well-optimised PNG, and use SVG for logos and icons whenever you can.
Encoding speed
Median encode time per megapixel across the 12 photos, measured as the median of repeated runs after a warm-up:
| Setting | 1 thread | 4 threads |
|---|---|---|
| JPEG q80 (mozjpeg) | 110 ms | 111 ms |
| WebP q80, effort 4 (default) | 109 ms | 116 ms |
| WebP q80, effort 6 (max) | 283 ms | not measured |
| AVIF q50, effort 0 | 93 ms | 48 ms |
| AVIF q50, effort 2 | 541 ms | 192 ms |
| AVIF q50, effort 4 (default) | 3,830 ms | 1,246 ms |
| AVIF q50, effort 6 | 10,847 ms | not measured |
At default settings, AVIF took about 35 times longer than WebP on one thread and about 11 times longer with four threads. AVIF benefits from extra cores, while single-image WebP and JPEG encoding does not. The effort option makes a big difference: at the same quality setting, effort 2 produced files about 8% larger than effort 4 while encoding roughly 6 to 7 times faster. Effort 6 saved only about 1.5% more than effort 4 for nearly three times the work.
In practice, AVIF at default effort suits build-time or background processing. For on-demand encoding (user uploads, image CDNs that resize on request), either use WebP or drop AVIF's effort to 0 to 2.
Decoding
We did not measure browser decoding. As a rule, AVIF decodes somewhat more slowly than WebP per pixel, but for typical web images the difference is small next to network transfer time. If your audience uses low-end Android phones, test image-heavy pages on a real device.
Caveats
- SSIM is a limited metric. It is widely available and repeatable, but it does not match human perception closely. For serious tuning, use a perceptual metric such as SSIMULACRA 2 or Butteraugli, and look at the images yourself.
- The sources were not pristine. Our photos had already been compressed once (as high-quality WebP) before we resized them. Downscaling hides most of those artefacts, but a camera-original corpus could produce somewhat different ratios.
- Small sample, one machine, one encoder library. Other encoders (for example libavif with SVT-AV1 or rav1e, or Google's newer Jpegli for JPEG) will shift the numbers.
The scripts are short, and we recommend running something similar on your own images before committing to quality settings.
Browser support in 2026
Both formats now work in every current major browser:
- WebP: Chrome, Edge, Firefox and Opera for years; Safari since version 14 (2020).
- AVIF: Chrome since version 85 (2020), Firefox since 93 (2021), Safari since 16 (2022, iOS 16 and recent macOS), and Edge since version 121 (early 2024).
The only users who miss out on AVIF are those on old browsers, mainly iPhones and iPads that cannot update past iOS 15. That group is small and shrinking, but a fallback costs almost nothing, so keep one.
Implementation in React
The <picture> element
The browser uses the first <source> whose type it supports, so order matters: AVIF, then WebP, then the <img> fallback.
function OptimizedImage({ src, alt, width, height, priority = false }) {
const base = src.replace(/\.[^/.]+$/, "");
return (
<picture>
<source type="image/avif" srcSet={`${base}.avif`} />
<source type="image/webp" srcSet={`${base}.webp`} />
<img
src={`${base}.jpg`}
alt={alt}
width={width}
height={height}
loading={priority ? "eager" : "lazy"}
fetchPriority={priority ? "high" : "auto"}
decoding="async"
/>
</picture>
);
}
Always set width and height so the browser can reserve space and avoid layout shift. Do not lazy-load the LCP image (usually the hero). Pass priority for it so it loads eagerly with high fetch priority.
Responsive images in every format
Combine format switching with resolution switching so phones do not download desktop-sized files:
const WIDTHS = [320, 640, 1024, 1920];
function ResponsiveImage({ name, alt, sizes, width, height }) {
const srcSet = (ext) =>
WIDTHS.map((w) => `/images/optimized/${name}-${w}w.${ext} ${w}w`).join(", ");
return (
<picture>
<source type="image/avif" srcSet={srcSet("avif")} sizes={sizes} />
<source type="image/webp" srcSet={srcSet("webp")} sizes={sizes} />
<img
src={`/images/optimized/${name}-640w.jpg`}
srcSet={srcSet("jpg")}
sizes={sizes}
alt={alt}
width={width}
height={height}
loading="lazy"
decoding="async"
/>
</picture>
);
}
// Usage
<ResponsiveImage
name="hero-banner"
alt="Coach reviewing attendance on a tablet"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
width={1920}
height={1080}
/>;
Generating the files at build time with sharp
// scripts/optimize-images.mjs
import sharp from "sharp";
import { glob } from "glob";
import { mkdir } from "node:fs/promises";
import path from "node:path";
const WIDTHS = [320, 640, 1024, 1920];
const OUT_DIR = "public/images/optimized";
async function optimizeImage(inputPath) {
const { name } = path.parse(inputPath);
const source = sharp(inputPath).rotate(); // apply EXIF orientation
for (const width of WIDTHS) {
const resized = source.clone().resize({ width, withoutEnlargement: true });
const out = (ext) => path.join(OUT_DIR, `${name}-${width}w.${ext}`);
await Promise.all([
resized.clone().avif({ quality: 55, effort: 4 }).toFile(out("avif")),
resized.clone().webp({ quality: 80 }).toFile(out("webp")),
resized.clone().jpeg({ quality: 80, mozjpeg: true }).toFile(out("jpg")),
]);
}
}
await mkdir(OUT_DIR, { recursive: true });
const images = await glob("src/assets/images/**/*.{jpg,jpeg,png}");
// Process one source image at a time: AVIF encoding is CPU- and memory-heavy
for (const file of images) {
await optimizeImage(file);
}
console.log(`Optimized ${images.length} images`);
Use clone() whenever one sharp pipeline feeds several outputs; without it, each format call reconfigures the same instance. Processing files one at a time keeps memory use predictable, because sharp already parallelises work inside each image. The quality values are starting points; adjust them after looking at your own images.
Letting the framework or CDN do it
If you use Next.js, next/image can serve AVIF and WebP for you via images: { formats: ["image/avif", "image/webp"] } in next.config.js. Many image CDNs can also choose the format from the browser's Accept header. Either way, make sure responses carry Vary: Accept (or the CDN's equivalent) so a cache never sends AVIF to a browser that asked for JPEG.
Which format to use where
Static assets built ahead of time (marketing pages, hero images, product screenshots). Serve AVIF first, with WebP and JPEG fallbacks. Encoding time is paid once at build time and the savings apply to every visit.
User uploads and on-the-fly resizing. Encode WebP immediately, because it is fast and predictable. If the images are viewed often, generate AVIF versions in a background job, or use AVIF with a low effort setting.
Authenticated app UI (dashboards and admin panels like Track My Academy). Repeat visitors hit the browser cache, so format matters most on first load and on mobile data. Use SVG for icons and logos, AVIF or lossless WebP for screenshots and illustrations, and WebP for server-generated avatars and thumbnails.
Thumbnails and background images. This is where AVIF's lead was largest in our test. Use it.
Implementation checklist
- Audit: list your largest images and identify the LCP image on key pages (Lighthouse or PageSpeed Insights will show it).
- Automate: generate AVIF, WebP and JPEG at several widths in your build, or delegate this to your framework or CDN.
- Mark up correctly:
<picture>withtypeattributes,srcsetandsizes, explicitwidthandheight. - Prioritise the LCP image: no lazy loading,
fetchpriority="high", and consider a<link rel="preload">withimagesrcset. - Lazy-load the rest with
loading="lazy". - Configure caching: long
Cache-Controllifetimes for fingerprinted files,Vary: Acceptif you negotiate formats. - Tune quality by eye and by metric on a sample of your real images, per format.
- Measure in the field: track LCP from real users (for example with the
web-vitalslibrary or Chrome UX Report data), not just lab scores.
Conclusion
On our test set, AVIF produced files roughly 20 to 27% smaller than WebP at the same SSIM for photos, with a much larger advantage on a UI screenshot. Encoding at default settings was about 11 to 35 times slower, depending on threads. WebP remains the fast, dependable choice when images must be produced on demand.
For most sites, the answer is not one format. Generate AVIF and WebP ahead of time, keep a JPEG fallback, let <picture> (or your CDN) pick for each browser, and verify the result with real-user LCP data. If you would like help improving the performance of your own product, book a free consultation with TrackMy Tech.
