PixConvertPixConvertBlog

PixConvertBlog

Next.jsViteWebpackBuild tools6 min read

Serve WebP Images in Next.js, Vite, and Webpack

Automatic WebP conversion at build time or request time. No manual conversion needed — your build pipeline handles it for you.

Next.js — built-in WebP via next/image

Next.js next/image automatically converts images to WebP (or AVIF) at request time via its built-in Image Optimization API. No configuration needed for basic usage.

// Just use next/image — it auto-converts to WebP in modern browsers
import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"        // Can be JPG or PNG — served as WebP automatically
      alt="Hero image"
      width={1200}
      height={600}
      priority              // Use for above-the-fold images (improves LCP)
    />
  );
}

To also enable AVIF (even smaller files), configure next.config.ts:

// next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],   // Try AVIF first, WebP fallback
    minimumCacheTTL: 31536000,               // Cache for 1 year
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
};

export default config;
Note: next/image optimization requires a Node.js server or a provider with image optimization support (Vercel, Cloudflare, etc.). For static exports (output: 'export'), use the Vite or sharp approaches below.

Vite — vite-imagetools

vite-imagetools transforms images at build time with query parameters.

npm install --save-dev vite-imagetools
// vite.config.ts
import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
  plugins: [imagetools()],
});
<!-- In your HTML/JSX — add ?format=webp to convert at build time -->
<picture>
  <source srcset="./hero.jpg?format=webp" type="image/webp">
  <img src="./hero.jpg" alt="Hero">
</picture>

// Or in React/Vue with import
import heroWebp from './hero.jpg?format=webp';
import heroJpg from './hero.jpg';

// Responsive with multiple sizes
import { src, srcset } from './hero.jpg?w=800;1200;1600&format=webp&as=srcset';

Vite — @imagetools/core with rollup plugin

For generating WebP alongside the original format (for <picture> fallbacks):

// vite.config.ts — generate both WebP and original
import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
  plugins: [
    imagetools({
      defaultDirectives: (url) => {
        // Auto-generate WebP for all images
        if (/.(jpg|jpeg|png)$/.test(url.pathname)) {
          return new URLSearchParams({ format: 'webp' });
        }
        return new URLSearchParams();
      },
    }),
  ],
});

Webpack — imagemin-webp

npm install --save-dev imagemin-webp imagemin-webpack-plugin
// webpack.config.js
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const imageminWebp = require('imagemin-webp');

module.exports = {
  plugins: [
    new ImageminPlugin({
      plugins: [
        imageminWebp({ quality: 85 })
      ],
      test: /.(jpe?g|png)$/i,
    }),
  ],
};

Node.js build script with sharp (framework-agnostic)

Works with any framework. Run as a pre-build step or in CI/CD.

// scripts/generate-webp.mjs
import sharp from 'sharp';
import { readdir, mkdir } from 'fs/promises';
import path from 'path';

const SRC = './public/images';
const DST = './public/images/webp';

await mkdir(DST, { recursive: true });

const files = await readdir(SRC);
await Promise.all(
  files
    .filter(f => /\.(jpg|jpeg|png)$/i.test(f))
    .map(async (file) => {
      const src = path.join(SRC, file);
      const dst = path.join(DST, file.replace(/\.[^.]+$/, '.webp'));
      await sharp(src).webp({ quality: 85 }).toFile(dst);
      console.log(`✓ ${file}`);
    })
);
// package.json
{
  "scripts": {
    "generate-webp": "node scripts/generate-webp.mjs",
    "build": "npm run generate-webp && next build"
  }
}

Using <picture> for maximum compatibility

Regardless of build tool, serve WebP with a fallback for older browsers:

<!-- HTML: serve WebP, fallback to JPG -->
<picture>
  <source srcset="/images/webp/hero.webp" type="image/webp">
  <source srcset="/images/hero.jpg" type="image/jpeg">
  <img src="/images/hero.jpg" alt="Hero" width="1200" height="600">
</picture>

<!-- With AVIF + WebP + JPG fallback chain -->
<picture>
  <source srcset="/images/avif/hero.avif" type="image/avif">
  <source srcset="/images/webp/hero.webp" type="image/webp">
  <img src="/images/hero.jpg" alt="Hero" width="1200" height="600">
</picture>