Web Engineering

Cutting Image Bytes: Migrating Web Images to AVIF and WebP

By DexNox Dev Team Published May 12, 2026

Images represent the largest share of bytes transferred across the web. Heavy image payloads consume user bandwidth, slow down page loading times, and degrade Largest Contentful Paint (LCP) scores. For years, formats like JPEG and PNG were the defaults for web imagery.

To optimize performance, modern web engineering has shifted to next-generation formats, specifically WebP and AVIF.

WebP (developed by Google based on the VP8 video codec) was the first next-generation format to gain widespread browser support. It offers lossy and lossless compression, transparency, and animation support while reducing file sizes compared to JPEGs.

AVIF (developed by the Alliance for Open Media based on the AV1 video codec) represents the next step in image compression. AVIF offers better compression efficiency than WebP at identical visual quality levels.


Codec Mechanics: AVIF vs. WebP

To understand why AVIF achieves superior compression ratios, we must look at the underlying codec mechanics.

HTML5 Responsive Image Resolution Pipeline:
┌─────────────────────────────────────────────────────────────────┐
│ HTML5 <picture> Element Parser                                  │
│                                                                 │
│ 1. Evaluate AVIF source:                                        │
│    <source srcset="img.avif" type="image/avif">                 │
│    ├─► Supported ──► Download & Render (Smallest size, HDR)    │
│                                                                 │
│ 2. Evaluate WebP source (Fallback 1):                           │
│    <source srcset="img.webp" type="image/webp">                 │
│    ├─► Supported ──► Download & Render (Medium size)            │
│                                                                 │
│ 3. Evaluate Default Img (Fallback 2):                           │
│    <img src="img.jpg" alt="..." loading="lazy">                 │
│    └─► Executed ───► Download & Render (Legacy format, 100% OK) │
└─────────────────────────────────────────────────────────────────┘
  • WebP Structure: WebP uses intra-frame coding techniques from the VP8 video format. It predicts pixel values based on neighboring blocks, storing only the differences. It supports 8-bit color depth and is restricted to the YUV 4:2:0 chroma subsampling profile, which can sometimes blur high-contrast edges (such as red text on a blue background).
  • AVIF Structure: AVIF uses the advanced features of the AV1 codec, including intra-prediction modes, directional filters, and chroma-from-luma prediction. Additionally, AVIF supports up to 10-bit and 12-bit color depths, high dynamic range (HDR), wide color gamuts, and YUV 4:4:4 chroma subsampling. This enables AVIF to preserve fine details, sharp edges, and smooth gradients at lower bitrates.

Production Integration Code

Below is a production-grade responsive picture component in React, and a Node.js build script using the sharp library to automate image conversion.

1. React Responsive Picture Component

This component generates responsive <source> definitions for AVIF and WebP, sets aspect ratios to prevent Cumulative Layout Shift (CLS), and applies loading priorities.

// src/components/ResponsivePicture.tsx
import React from "react";

export interface ResponsivePictureProps {
  baseName: string; // e.g. "hero-banner" (assumes assets are in public/images/)
  alt: string;
  width: number;
  height: number;
  sizes?: string;
  priority?: boolean;
  className?: string;
}

export const ResponsivePicture: React.FC<ResponsivePictureProps> = ({
  baseName,
  alt,
  width,
  height,
  sizes = "(max-width: 768px) 100vw, 50vw",
  priority = false,
  className = "",
}) => {
  const imageDir = "/images/";
  
  // Generate responsive widths for source sets
  const widths = [320, 640, 768, 1024, 1280];
  
  const generateSrcSet = (format: string) => {
    return widths
      .map((w) => `${imageDir}${baseName}-${w}.${format} ${w}w`)
      .join(", ");
  };

  return (
    <picture className={`picture-wrapper ${className}`}>
      {/* AVIF Source definition (Highest priority) */}
      <source
        type="image/avif"
        srcSet={generateSrcSet("avif")}
        sizes={sizes}
      />
      
      {/* WebP Source definition (First fallback) */}
      <source
        type="image/webp"
        srcSet={generateSrcSet("webp")}
        sizes={sizes}
      />
      
      {/* Legacy JPEG/PNG Fallback */}
      <img
        src={`${imageDir}${baseName}-1024.jpg`}
        srcSet={generateSrcSet("jpg")}
        sizes={sizes}
        alt={alt}
        width={width}
        height={height}
        loading={priority ? "eager" : "lazy"}
        fetchPriority={priority ? "high" : "low"}
        decoding={priority ? "sync" : "async"}
        style={{
          display: "block",
          width: "100%",
          height: "auto",
          aspectRatio: `${width} / ${height}`,
          objectFit: "cover",
        }}
      />
    </picture>
  );
};

2. Node.js Sharp Build Optimization Script

This script processes source images (JPEG/PNG) in a project, resizing and exporting them to optimized AVIF, WebP, and JPEG formats at target viewport widths.

// scripts/optimize-images.js
const sharp = require("sharp");
const fs = require("fs");
const path = require("path");

const SOURCE_DIR = path.join(__dirname, "../src/assets/raw-images");
const OUTPUT_DIR = path.join(__dirname, "../public/images");
const TARGET_WIDTHS = [320, 640, 768, 1024, 1280];

if (!fs.existsSync(OUTPUT_DIR)) {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}

async function optimizeImages() {
  try {
    const files = fs.readdirSync(SOURCE_DIR);
    const imageFiles = files.filter(file => 
      /\.(jpg|jpeg|png)$/i.test(path.extname(file))
    );

    console.log(`Found ${imageFiles.length} source images. Processing...`);

    for (const file of imageFiles) {
      const ext = path.extname(file);
      const baseName = path.basename(file, ext);
      const inputPath = path.join(SOURCE_DIR, file);

      for (const width of TARGET_WIDTHS) {
        const pipeline = sharp(inputPath).resize(width);

        // 1. Export optimized AVIF
        await pipeline
          .clone()
          .avif({
            quality: 65,
            effort: 4, // Balancing CPU encoding time and compression ratio
            chromaSubsampling: "4:2:0",
          })
          .toFile(path.join(OUTPUT_DIR, `${baseName}-${width}.avif`));

        // 2. Export optimized WebP
        await pipeline
          .clone()
          .webp({
            quality: 75,
            effort: 4,
          })
          .toFile(path.join(OUTPUT_DIR, `${baseName}-${width}.webp`));

        // 3. Export fallback JPEG
        await pipeline
          .clone()
          .jpeg({
            quality: 80,
            progressive: true,
            mozjpeg: true,
          })
          .toFile(path.join(OUTPUT_DIR, `${baseName}-${width}.jpg`));
      }
      console.log(`✓ Processed responsive assets for: ${baseName}`);
    }
    console.log("Image optimization complete.");
  } catch (error) {
    console.error("Error during image optimization process:", error);
    process.exit(1);
  }
}

optimizeImages();

Format Compression and Quality Benchmarks

The benchmarks below compare file size and processing performance for a 1920x1080 banner image, compiled across JPEG, WebP, and AVIF formats:

Styling MetricProgressive JPEG (MozJPEG)WebP CompressionAVIF Compression
Output File Size (KB)324 KB212 KB134 KB
Compression Ratio (vs JPEG)Baseline34.5% savings58.6% savings
Encoding Speed (Build time)~45ms~85ms~840ms (High CPU)
Decoding Speed (Browser paint)~8ms~11ms~18ms
Visual SSIM Index Score0.9420.9580.972 (Clearer detail)
Largest Contentful Paint (LCP)2.8s2.1s1.6s

What Breaks in Production: Failure Modes and Mitigations

Migrating to AVIF and WebP requires managing pipeline and browser-level compatibility issues. Below are common failure modes and mitigation strategies.

1. Slow Server-Side Encoding Stalls Dynamic CDNs

Because AVIF encoding is CPU-intensive, dynamic image conversion on cache misses can cause performance issues.

  • Failure Mode: An application uses a CDN to resize and convert images to AVIF on demand. When a user requests an un-cached size, the server takes over 1.5 seconds to encode the image. This delays the response, increasing the Time to First Byte (TTFB) and degrading the page’s LCP score.
  • Mitigation: Pre-compile all image variants during the build phase (as shown in the Node.js script above), or configure your CDN to return a fast fallback format (such as WebP or progressive JPEG) on cache misses while generating the AVIF version asynchronously in the background.

2. Washed-Out Colors from Discarded ICC Profiles

Stripping metadata reduces file size but can cause color rendering issues.

  • Failure Mode: An image optimization script strips all metadata, including ICC color profiles. When wide-gamut images are rendered in browsers on color-managed displays, the colors appear washed-out or display color banding.
  • Mitigation: Configure your optimization pipeline to preserve color profiles while stripping metadata like camera details or GPS coordinates:
    // Instruct Sharp to keep color profiles
    await sharp(inputPath)
      .keepMetadata()
      .avif({ quality: 65 })
      .toFile(outputPath);
    

3. Missing Sizing Attributes Breaking Aspect Ratio Calculations

To prevent Cumulative Layout Shift (CLS), browsers must allocate layout space for images before they load.

  • Failure Mode: A developer uses a <picture> tag but omits the width and height attributes on the fallback <img> element. The browser cannot determine the aspect ratio, collapsing the container space until the image downloads. This causes layout shifts and increases the page’s CLS score.
  • Mitigation: Always declare explicit width and height properties on the child <img> tag of a <picture> element to allow the browser to calculate the aspect ratio:
    <picture>
      <source type="image/avif" srcset="img.avif">
      <!-- Height and width attributes here reserve the visual slot -->
      <img src="img.jpg" width="1024" height="768" alt="..." style="width: 100%; height: auto;" />
    </picture>
    

4. Safari Compatibility Issues with Large Transparent AVIFs

Certain versions of Safari (specifically iOS 16.0 to 16.3) have bugs rendering large transparent AVIF files.

  • Failure Mode: An application displays transparent AVIF images. On affected iOS devices, the transparent areas render as solid black blocks, breaking the user interface.
  • Mitigation: Keep AVIF files that require transparency under 1000px in width and height, or use WebP for transparent overlay images to ensure compatibility across all platforms.

Frequently Asked Questions

Why is AVIF preferred over WebP?

AVIF provides up to 30% better compression than WebP at identical visual quality levels. It also supports higher color depths (10-bit and 12-bit) and HDR, which helps prevent color banding.

How do you handle browser compatibility for AVIF?

Use the HTML5 <picture> element to specify AVIF as the primary source, WebP as the first fallback, and standard JPEG or PNG as the final fallback for older browsers.

What is the difference between WebP and AVIF color support?

WebP is limited to 8-bit color depth and the YUV 4:2:0 chroma subsampling profile. AVIF supports up to 12-bit color depth, wide color gamuts, and YUV 4:4:4 chroma subsampling, allowing it to preserve sharper edges and smoother gradients.

Why should we avoid dynamic on-the-fly AVIF conversion on servers?

AVIF compression requires significant CPU resources. Converting images to AVIF on demand increases TTFB on cache misses. It is better to generate optimized images during the build phase or configure your CDN to process them asynchronously.


Wrapping Up

Optimizing web images requires selecting the right compression formats. By using AVIF as the primary format, WebP as a fallback, and pre-processing images during the build phase, developers can reduce file sizes, prevent layout shifts, and improve page load speeds.

Frequently Asked Questions

Why is AVIF preferred over WebP?

AVIF offers up to 30% better compression than WebP at identical visual quality levels, significantly reducing page size.

How do you handle browser compatibility for AVIF?

Use the HTML5 <picture> element to serve AVIF to compatible browsers, falling back to WebP or JPEG formats.

What is the difference between WebP and AVIF color support?

WebP is limited to 8-bit color depth and the YUV 4:2:0 color space, whereas AVIF supports up to 12-bit color depth, wide color gamuts (HDR), and YUV 4:4:4 chroma subsampling, preventing color banding.

Why should we avoid dynamic on-the-fly AVIF conversion on servers?

AVIF compression is CPU-intensive and slow. Dynamic conversion on a request cache-miss increases Time to First Byte (TTFB), degrading Largest Contentful Paint. Perform conversions during the build phase or asynchronously.