Serve AVIF in React in 2026: next/image negotiation, a native picture element for Vite, build-time sharp encoding, and responsive srcset.
Delivering AVIF to a React App That Already Works
React renders DOM, not images — so AVIF delivery is a build and markup decision, not a component you import. The right path depends entirely on whether a server sits in front of your images.
This guide gives one working answer per stack: Next.js, Vite or Create React App, and a static sharp pipeline. For the format itself, read AVIF; for the fallback chain, AVIF Browser Support.
Which approach fits your stack
Pick the row that matches your setup, then jump to its section.
- Next.js with a server — let
next/imagenegotiate AVIF per request. - Vite, CRA, or any SPA — encode at build time and ship a
<picture>element. - Static export or no build step — generate
.aviffiles withsharp, reference them by hand. - Already on an image CDN — pass
f_auto(or equivalent) and skip the pipeline.
The rule is simple: a runtime server can convert on demand; a static build cannot. Everything below follows from that one fact.
Next.js: next/image negotiates AVIF automatically
In Next.js, set images.formats and the <Image> component serves AVIF to every browser that accepts it. No <picture> markup, no manual variants.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
formats: ["image/avif", "image/webp"],
},
};
export default nextConfig;
Next.js reads the browser's Accept header and serves the first format in the array that the browser supports. AVIF-capable browsers get AVIF; the rest fall back to WebP, then the original. Order the array best-first.
As of Next.js 16, the formats field is required — the implicit default was removed to stop attackers requesting unbounded format variants. List the formats you want explicitly.
import Image from "next/image";
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Mountain ridge at dawn"
width={1600}
height={900}
priority
sizes="100vw"
/>
);
}
Four behaviours come free with that markup:
- AVIF or WebP is chosen per request from the
Acceptheader. - Resolution is selected from the device and the
sizesprop. widthandheightreserve space, preventing layout shift.prioritydisables lazy loading and preloads — set it on the LCP image only.
Next.js 16 ships with Turbopack as the default bundler for both next dev and next build, replacing Webpack. Image optimisation is unaffected by the bundler change; the config above is identical.
For a static export (output: "export"), the optimiser cannot run — there is no server. Treat that case like a Vite build and use the sharp pipeline below, or point a custom loader at a CDN.
Vite and CRA: the native picture element
Outside Next.js, AVIF delivery is plain HTML — a <picture> element with one <source> per format, ordered AVIF first, with a JPEG <img> as the universal fallback.
type Props = {
alt: string;
width: number;
height: number;
};
export function Hero({ alt, width, height }: Props) {
return (
<picture>
<source type="image/avif" srcSet="/hero.avif" />
<source type="image/webp" srcSet="/hero.webp" />
<img
src="/hero.jpg"
alt={alt}
width={width}
height={height}
decoding="async"
/>
</picture>
);
}
The browser walks the <source> list top to bottom and loads the first type it can decode. Non-AVIF browsers ignore the AVIF source silently and reach the <img>. No JavaScript and no feature detection are involved.
Always set width and height on the <img>. They give the browser an aspect ratio before bytes arrive, which prevents Cumulative Layout Shift — one of the Core Web Vitals.
The .avif and .webp files referenced here do not exist until you generate them. The next section builds them.
Generating AVIF at build time with sharp
Use sharp to encode source images to AVIF once, during the build. sharp wraps libvips and exposes an .avif() method.
npm install --save-dev sharp glob
// scripts/encode-avif.ts
import { glob } from "glob";
import path from "node:path";
import sharp from "sharp";
const sources = await glob("public/images/**/*.{jpg,jpeg,png}");
for (const file of sources) {
const base = file.replace(/\.(jpe?g|png)$/, "");
await sharp(file)
.avif({ quality: 50, effort: 4 })
.toFile(`${base}.avif`);
await sharp(file)
.webp({ quality: 75 })
.toFile(`${base}.webp`);
}
Two sharp AVIF parameters control the quality-versus-time trade-off:
quality— 1 to 100; 50 is a strong default for photos and visually matches JPEG quality 75 at far fewer bytes.effort— 0 to 9; higher means slower encoding for marginally smaller files.effort: 4balances build time against size; reserve 9 for a handful of heroes.
AVIF encoding is 5–20× slower than WebP at comparable effort, so cache the output and skip files whose .avif is newer than the source. Run the script before your build:
{
"scripts": {
"prebuild": "tsx scripts/encode-avif.ts",
"build": "vite build"
}
}
Prefer a plugin? vite-imagetools runs sharp through Vite and lets you request formats with an import query — import hero from "./hero.jpg?format=avif". The standalone script above stays framework-agnostic and works for static exports too.
Responsive images: srcset and sizes
A single AVIF width wastes bytes on phones and looks soft on desktops. Generate several widths and let the browser choose with srcset and sizes.
// scripts/encode-avif.ts (responsive widths)
const widths = [480, 768, 1200, 1800];
for (const w of widths) {
await sharp(file)
.resize({ width: w })
.avif({ quality: 50, effort: 4 })
.toFile(`${base}-${w}.avif`);
}
export function ResponsiveHero({ alt }: { alt: string }) {
return (
<picture>
<source
type="image/avif"
srcSet="/hero-480.avif 480w, /hero-768.avif 768w, /hero-1200.avif 1200w, /hero-1800.avif 1800w"
sizes="(max-width: 768px) 100vw, 1200px"
/>
<img
src="/hero-1200.jpg"
alt={alt}
width={1200}
height={675}
decoding="async"
/>
</picture>
);
}
Two attributes do the work:
srcsetlists each file with its intrinsic width descriptor, such as1200w.sizestells the browser how wide the image renders, so it picks the smallest sufficient file before layout.
Get sizes right and a phone downloads the 480w AVIF while a desktop fetches the 1800w — from the same markup.
Loading strategy: lazy below the fold, priority for the LCP
The slowest mistake in React image code is lazy-loading the hero. The LCP image must load eagerly; everything below the fold should defer.
{/* Below the fold: defer until near the viewport */}
<img src="/gallery-1.avif" alt="" width={600} height={400} loading="lazy" decoding="async" />
{/* LCP candidate: load now, hint high priority */}
<img src="/hero-1200.avif" alt="" width={1200} height={675} fetchPriority="high" decoding="async" />
Three attributes, three jobs:
loading="lazy"defers off-screen images natively — no IntersectionObserver needed.fetchPriority="high"raises the LCP image above other requests in the queue.decoding="async"keeps decode work off the main thread.
React expects camelCase here: write fetchPriority, not fetchpriority. React lowercases it in the rendered HTML. In Next.js, the equivalent is the priority prop on <Image>, which sets fetchpriority="high" and disables lazy loading for you.
Never put loading="lazy" on the LCP image — it delays the very paint Core Web Vitals measures. See AVIF Lazy Loading for the full pattern.
FAQ
Do I need a picture element with next/image?
No. next/image negotiates AVIF from the Accept header server-side, so a single <Image> covers every browser. Use <picture> only when no server optimises images — Vite, CRA, or a static export.
What AVIF quality should I set in sharp?
Use quality: 50 with effort: 4 for photos. That visually matches JPEG quality 75 at far fewer bytes. Raise effort to 9 only for a few hero images where build time does not matter. See AVIF Compression Settings.
Will AVIF break in older browsers?
No, if you keep a fallback. Both next/image and the <picture> element fall back to WebP or JPEG automatically. AVIF reaches roughly 94% of users in 2026; the fallback covers the rest. See AVIF Browser Support.
Should I convert PNG and JPG sources to AVIF?
Yes for photos; check transparency for graphics. AVIF supports full alpha, so it replaces both. Convert in-browser with JPG to AVIF and PNG to AVIF, or reverse with AVIF to JPG and AVIF to PNG.
Is AVIF worth it over WebP in a React app?
Yes for byte savings; weigh encode time. AVIF is 20–25% smaller than WebP but encodes far slower. Caching build output removes the cost. Compare both in AVIF vs WebP.
Where to go next
- AVIF Optimization — the full delivery checklist.
- AVIF Lazy Loading — eager versus deferred loading.
- AVIF Compression Settings — quality and effort tuning.
- Core Web Vitals & Images — measuring the payoff.
- Format references: WebP, JPG, PNG.