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>