AVIFify

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/image negotiate AVIF per request.
  • Vite, CRA, or any SPA — encode at build time and ship a <picture> element.
  • Static export or no build step — generate .avif files with sharp, 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:

  1. AVIF or WebP is chosen per request from the Accept header.
  2. Resolution is selected from the device and the sizes prop.
  3. width and height reserve space, preventing layout shift.
  4. priority disables 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: 4 balances 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:

  • srcset lists each file with its intrinsic width descriptor, such as 1200w.
  • sizes tells 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

Sources

Lossy vs Lossless Compression: When to Use Each

Understand lossy vs lossless image compression, how each works, and which mode to pick for photos, line art, and masters — with AVIF numbers throughout.

Image SEO in 2026: Alt Text, Page Speed, and AVIF

Image SEO for 2026. Which signals move rankings, which are myths, and how AVIF's smaller files feed the page-experience signals Google measures.

Getting Started: Convert Your First Image to AVIF

A short guide to converting your first image to AVIF with AVIFify. Drag, drop, and download — no signup, no upload.

Core Web Vitals for Images: LCP, CLS, INP & AVIF

How images drive Core Web Vitals in 2026, and how AVIF cuts LCP. Covers the LCP sub-parts, CLS dimension fixes, INP decode cost, and measurement.