next/experimental/image-optimizer-transform

API Reference for the next/experimental/image-optimizer-transform module, the standalone sharp-based image resizing and format conversion pipeline behind Next.js Image Optimization.

The next/experimental/image-optimizer-transform module exposes the transformation step of Next.js Image Optimization as a standalone Node.js module with a small dependency graph, so image resizing and format conversion (powered by sharp) can run outside the main server process.

import { imageOptimizerTransform } from 'next/experimental/image-optimizer-transform'
 
const result = await imageOptimizerTransform(
  {
    buffer: upstreamBuffer, // Buffer of the source image
    contentType: null,
    cacheControl: 'public, max-age=120',
    etag: 'source-etag',
  },
  { href: '/me.png', width: 64, quality: 75, mimeType: 'image/webp' },
  { minimumCacheTTL: 60 }
)
// result.contentType === 'image/webp'

Reference

imageOptimizerTransform

async function imageOptimizerTransform(
  imageUpstream: ImageUpstream,
  paramsResult: ImageOptimizerTransformParams,
  opts?: ImageOptimizerTransformOptions
): Promise<ImageOptimizerResult>

Validates an upstream image, optimizes it with sharp, and returns the result with caching metadata.

imageUpstream

FieldTypeDescription
bufferBufferThe source image bytes.
contentTypestring | null | undefinedThe content type reported upstream. Not trusted; the type is detected from the buffer.
cacheControlstring | null | undefinedThe upstream Cache-Control header. Used to compute maxAge.
etagstringThe upstream ETag. Returned as upstreamEtag, and as etag when optimization is skipped.

paramsResult

FieldTypeDescription
hrefstringThe source URL of the image. Used in log and error messages.
widthnumberThe target width in pixels.
qualitynumberThe target quality, an integer between 1 and 100.
mimeTypestringThe requested output MIME type. An empty string means no preference.

opts

All fields are optional.

FieldTypeDefaultDescription
dangerouslyAllowSVGbooleanfalseAllows SVG sources. When false, an SVG source throws an ImageError.
minimumCacheTTLnumber0The minimum maxAge of the result, in seconds.
concurrencynumber | nullundefinedThe sharp thread pool size. When not a number, a default based on NODE_ENV is used.
operationCacheboolean | nullundefinedEnables or disables the libvips operation cache. When not a boolean, sharp's default is used.
maxInputPixelsnumberundefinedPassed to sharp as limitInputPixels. When omitted, sharp's default of 268402689 is used.
sequentialReadboolean | nullundefinedPassed to sharp as sequentialRead.
timeoutInSecondsnumber7Per-image processing timeout.
mozjpegbooleantrueEncodes JPEG output with mozjpeg.
loggerobjectundefinedA logger with error(...args) and warnOnce(message). When omitted, no logging occurs.

These correspond to the images and experimental.imgOpt* options in next.config.js, but the defaults are not the same. For example, Next.js defaults images.minimumCacheTTL to 14400.

Returns

Promise<ImageOptimizerResult> with the following fields:

FieldTypeDescription
bufferBufferThe optimized image, or the upstream image when optimization is skipped or failed.
contentTypestringThe MIME type of buffer.
maxAgenumberThe cache lifetime in seconds.
etagstringThe ETag for the returned buffer.
upstreamEtagstringThe ETag of the upstream image.
optimizedbooleantrue when buffer was produced by sharp, false when the upstream image was returned.
errorunknownPresent only when optimization failed and the upstream image was returned instead.

The function runs the following steps in order:

  1. Computes maxAge as the greater of opts.minimumCacheTTL and the upstream Cache-Control s-maxage or max-age directive.
  2. Detects the content type from the magic bytes of imageUpstream.buffer.
  3. Throws an ImageError with statusCode 400 when the buffer is not a recognized image, or when the image is SVG and opts.dangerouslyAllowSVG is false.
  4. Returns animated WebP, PNG, and GIF sources unoptimized, and calls logger.warnOnce.
  5. Returns SVG, ICO, ICNS, BMP, JXL, and HEIC sources unmodified.
  6. Selects the output type: paramsResult.mimeType when non-empty, image/jpeg for WebP or AVIF sources when no type is requested, otherwise the detected source type.
  7. Resizes and re-encodes the image with sharp using the sharp-related opts values. The output is auto-rotated according to the EXIF orientation of the source image.
  8. On failure, returns the upstream buffer and content type with maxAge set to opts.minimumCacheTTL and the caught value on result.error. The failure does not throw.

getMaxAge

function getMaxAge(cacheControl: string | null | undefined): number

Parses a Cache-Control header value and returns the s-maxage directive, or the max-age directive when s-maxage is absent, as an integer number of seconds. Returns 0 when neither directive is present or its value is not a number. This is the same parsing the image optimizer applies to upstream responses when computing maxAge.

const maxAge = getMaxAge('public, max-age=120') // 120

getSharp

function getSharp(
  concurrency: number | null | undefined,
  operationCache: boolean | null | undefined
): typeof import('sharp').default

Loads the sharp module once, applies configuration, and returns the memoized instance.

ParameterTypeDescription
concurrencynumber | null | undefinedThe libvips thread pool size. When not a number, the default is the current concurrency divided by 4 in development or 2 in production, with a minimum of 1.
operationCacheboolean | null | undefinedWhen a boolean, passed to sharp.cache() to enable or disable the libvips operation cache.

On first load, getSharp restricts libvips image loaders to HEIF (AVIF), JPEG, GIF, PNG, SVG, TIFF, and WebP. If sharp is not installed, it throws an error that suggests running npm install --cpu=wasm32 sharp.

ImageError

class ImageError extends Error {
  statusCode: number
  constructor(statusCode: number, message: string)
}

The error type thrown by imageOptimizerTransform for invalid input. statusCode is preserved when it is 400 or greater. Any lower value is replaced with 500.

Good to know

  • sharp is required and is loaded lazily on the first call to getSharp or the first image that imageOptimizerTransform optimizes. The same instance is reused afterwards.
  • The module intentionally imports very little so it loads quickly in a separate process, such as a worker or child process. Its file location and exports are a stable public contract.
  • The contentType field of imageUpstream is not used for detection. The content type is detected from the first bytes of the buffer.
  • Optimization failures do not throw. imageOptimizerTransform returns the upstream buffer with error set on the result.
  • Animated WebP, PNG, and GIF images are returned unoptimized. SVG, ICO, ICNS, BMP, JXL, and HEIC images are always passed through without optimization.

Examples

Optimizing a fetched image

Fetch an upstream image and convert it to WebP, using the response headers as the upstream metadata.

import { imageOptimizerTransform } from 'next/experimental/image-optimizer-transform'
 
const logger = {
  error: (...args: unknown[]) => console.error(...args),
  warnOnce: (message: string) => console.warn(message),
}
 
const upstream = await fetch('https://example.com/me.png')
 
const result = await imageOptimizerTransform(
  {
    buffer: Buffer.from(await upstream.arrayBuffer()),
    contentType: upstream.headers.get('content-type'),
    cacheControl: upstream.headers.get('cache-control'),
    etag: upstream.headers.get('etag') ?? 'unknown',
  },
  {
    href: 'https://example.com/me.png',
    width: 64,
    quality: 75,
    mimeType: 'image/webp',
  },
  { minimumCacheTTL: 60, timeoutInSeconds: 10, logger }
)
 
if (result.error) {
  // Optimization failed. result.buffer contains the upstream image.
}

Configuring sharp with getSharp

Use getSharp to load sharp with the same loader restrictions and concurrency limits as the Next.js image optimizer.

import { getSharp } from 'next/experimental/image-optimizer-transform'
 
const sharp = getSharp(1, false)
const metadata = await sharp(buffer).metadata()

On this page