WebP Images in Next.js: Auto-Optimise With next/image

Next.js automatically serves WebP via the next/image component. Learn how to configure formats, quality, loaders, and priority for optimal performance.

Next.js includes built-in image optimisation through the next/image component. By default it automatically converts and serves images as WebP (or AVIF) based on the browser’s Accept header, with no additional configuration required. You write your imports and <Image> tags pointing to JPEG or PNG source files, and Next.js handles format conversion, resizing, and caching transparently.

Basic Usage #

Import Image from next/image and provide src, alt, width, and height. Next.js does the rest.

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/images/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority
    />
  );
}

The width and height props set the rendered size and aspect ratio. Next.js generates multiple responsive variants at build time (or on first request in production) and injects the correct srcset automatically.

Add the priority prop to any <Image> that appears above the fold. It disables lazy loading and injects a <link rel="preload"> tag in <head>, which directly improves your Largest Contentful Paint (LCP) score.

How Next.js Handles WebP #

The Image Optimisation API lives at /_next/image and acts as an on-demand image transform pipeline:

  1. Request interception — The browser requests an image through /_next/image?url=...&w=...&q=.... Next.js reads the browser’s Accept header to determine which format to serve.
  2. Format conversion — Next.js converts the source image to WebP (or AVIF if configured and supported) using the sharp library under the hood.
  3. Caching — The converted image is cached on disk. Subsequent requests for the same URL, width, and quality are served directly from cache without re-processing.
  4. Vary header — Responses include Vary: Accept so CDN edge nodes cache WebP and non-WebP responses separately for the same URL.

Configuring Image Formats #

Customise the format priority, quality, and device breakpoints in next.config.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'], // AVIF first, WebP fallback
    quality: 80,
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
};

module.exports = nextConfig;

formats controls which optimised formats are attempted in order. Browsers that support AVIF receive AVIF; browsers that support only WebP receive WebP; all others receive the original format. Remove 'image/avif' from the array if you want to serve WebP only — AVIF encoding is significantly slower and may increase cold-start times on large images.

Responsive Images With fill #

Use the fill prop when the image should expand to fill its parent container. Pair it with a positioned wrapper element and object-fit to control cropping behaviour.

<div style={{ position: 'relative', width: '100%', height: '400px' }}>
  <Image
    src="/images/photo.jpg"
    alt="Photo"
    fill
    style={{ objectFit: 'cover' }}
  />
</div>

When using fill, omit the width and height props — the component reads its dimensions from the CSS layout instead. The parent element must have position: relative, position: absolute, or position: fixed.

External Images #

To optimise images hosted on external domains, add the allowed hostnames to remotePatterns in next.config.js. Next.js rejects external URLs that aren’t explicitly listed as a security measure.

const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.example.com',
        port: '',
        pathname: '/uploads/**',
      },
      {
        protocol: 'https',
        hostname: '**.cdn-provider.com',
      },
    ],
  },
};

module.exports = nextConfig;

Once listed, external images are fetched, converted to WebP, cached, and served through /_next/image identically to local assets.

Custom Loaders #

If you use a third-party image CDN such as Cloudinary, Imgix, or Akamai, you can replace the default optimisation API with a custom loader that builds the CDN URL directly.

// lib/imageLoader.js
export default function cloudinaryLoader({ src, width, quality }) {
  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
}
import Image from 'next/image';
import cloudinaryLoader from '../lib/imageLoader';

<Image
  loader={cloudinaryLoader}
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
/>

Custom loaders bypass /_next/image entirely, so the CDN handles format negotiation and conversion instead of Next.js.

Next.js Image Optimisation requires a Node.js server at runtime. If you export your site as fully static using next export (or output: 'export' in Next.js 13+), the /_next/image endpoint is unavailable. For static exports, use a custom loader pointing to an external image CDN, or pre-convert your images to WebP at build time before deploying.

Per-Image Quality Override #

Override the global quality setting on individual <Image> components using the quality prop.

<Image
  src="/images/thumbnail.jpg"
  alt="Thumbnail"
  width={200}
  height={200}
  quality={65} // lower quality for small thumbnails
/>

<Image
  src="/images/portfolio-hero.jpg"
  alt="Portfolio hero"
  width={1600}
  height={900}
  quality={90} // higher quality for showcase images
  priority
/>
Was this page helpful?