Skip to main content
next/image in Next.js 16: What Is Not Automatic About Image Optimization

next/image in Next.js 16: What Is Not Automatic About Image Optimization

August 14, 2026
Frontend Engineering
7 min read

Key takeaways

next/image lazy-loads every image by default, including the one that is the Largest Contentful Paint element. Only the priority prop removes lazy loading and adds a high-priority preload hint.

The sizes prop tells the browser how wide an image will render before CSS is applied. An Image with the fill prop and no sizes is treated as 100vw, which selects the largest candidate in the srcset.

Next.js 16 removed the images.domains option, so images.remotePatterns is the only supported way to allow a remote host.

Next.js 16 restricts the quality prop to values listed in images.qualities, which defaults to [75].

An image transformation is a unique combination of source image, width, quality and output format, so a wide deviceSizes array multiplies both cost and cache misses.

Why is LCP still slow when the project already uses next/image?

Because next/image lazy-loads every image by default, including the Largest Contentful Paint element. A lazy image is not requested until the browser has run layout and decided the image is near the viewport, so the preload scanner never starts the download while HTML is still parsing.

The fix is the priority prop, applied to exactly one image per route. It removes loading=lazy, sets fetchpriority=high, and emits a preload link in the document head. Marking six images priority is equivalent to marking none, because six high-priority requests then compete for the same connection.

Two related traps cost our team time. The placeholder=blur option inlines a base64 data URI into the HTML, so a heavy blurDataURL grows the document on the critical path. And an image rendered by a client component that only mounts after hydration cannot be preloaded at all, whatever priority says, because the markup does not exist when the preload scanner runs.

What does the sizes prop actually do?

The sizes attribute tells the browser how wide the image will be rendered, so it can choose a srcset candidate before stylesheets are applied. Without sizes, Next.js emits a fixed 1x/2x srcset built from the width you passed. With sizes, it emits a full candidate list drawn from images.deviceSizes (default 640, 750, 828, 1080, 1200, 1920, 2048 and 3840) and images.imageSizes (default 16, 32, 48, 64, 96, 128, 256 and 384).

The fill prop with no sizes is treated as 100vw. On a 1920-pixel display at device pixel ratio 2 the browser then requests the 3840-pixel file for a card that renders at 400 pixels. Our review rule is simple: an Image with fill and no sizes is a defect, not a style preference. A correct value for a card capped at 400 pixels looks like (max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px.

Should we use fill or explicit width and height?

Use explicit width and height whenever the intrinsic dimensions are known, because Next.js converts them into a CSS aspect-ratio that reserves space and keeps Cumulative Layout Shift at zero. A static import of a file in the repository supplies both dimensions and a generated blurDataURL at build time.

Reach for fill only when the rendered box is decided by CSS and the source aspect ratio varies, such as user-uploaded avatars or CMS hero images. The fill prop absolutely positions the image, so the parent needs position relative and a non-zero height, and cropping becomes your job through object-fit.

What changed for images in Next.js 16?

Next.js 16 tightened image configuration in three ways that break existing config files. The images.domains option was removed in favour of images.remotePatterns. The quality prop is restricted to values listed in images.qualities, which defaults to [75]. And images.localPatterns restricts which local paths the optimizer accepts, which matters because /_next/image is a public endpoint anyone can call with arbitrary parameters.

SVG is still refused by the optimizer unless dangerouslyAllowSVG is set to true, and that default is correct. An SVG is an executable document, so optimizing one from an untrusted host turns your own origin into the delivery vehicle for its scripts. Serve SVG files as static assets with a restrictive Content-Security-Policy instead.

Should images.formats list AVIF or WebP first?

List AVIF first when bandwidth is the constraint, and WebP first when first-request latency is. The images.formats array is ordered by preference and the optimizer picks the first entry the requesting browser accepts.

AVIF produces smaller files than WebP at comparable visual quality but is noticeably slower to encode. WebP is larger than AVIF and far smaller than the JPEG source, and it encodes fast. The trade-off is uneven across a site: on a marketing page whose five hero images every visitor loads, AVIF encoding happens once and the smaller bytes win forever. On a catalogue of fifty thousand product photos where most are viewed once, the slower encode lands on a real user's first request.

How do we keep image transformation cost bounded?

An image transformation is a unique combination of source image, requested width, quality and output format, and each unique combination is computed once before it is cached. That definition is the entire cost model: anything that multiplies distinct combinations multiplies the bill.

Trim deviceSizes, because eight default widths times two formats is sixteen possible transformations per source image; if the layout has three real breakpoints, list three widths. Declare a single quality value. Raise minimumCacheTTL, since a short TTL recomputes transformations already paid for, and remember that Next.js honours an upstream Cache-Control max-age when it is longer, so a CMS sending no-store quietly defeats the cache.

Set the unoptimized prop on assets that are already optimized, such as sprite sheets and small PNG icons. Watch for cache-busting query strings too: a source URL with a changing token is a new source image on every request, and therefore a new transformation on every request.

When should we bypass next/image entirely?

Three cases justify leaving the component behind. For art direction, meaning a different crop on mobile than on desktop, call getImageProps() (stable since Next.js 15, previously unstable_getImgProps) to obtain the generated srcset and feed it into a picture element or a CSS background. For a static export there is no server to run the optimizer, so images.unoptimized must be true or a custom loader supplied. And when an image CDN such as Cloudinary or imgix is already paid for, point images.loaderFile at it rather than optimizing twice.

Self-hosting has one further requirement: the built-in optimizer needs the sharp package installed. The pure-JavaScript fallback was removed in earlier releases, so a self-hosted deployment without sharp fails to optimize rather than silently degrading.

FAQ

Q: Should priority be added to every above-the-fold image?

A: No. Add priority to the single image most likely to be the Largest Contentful Paint element. Multiple high-priority images compete for bandwidth and delay the one that determines the metric.

Q: Why does an optimized image look soft on a Retina screen?

A: Next.js never upscales beyond the intrinsic size of the source file. If the browser requests a 1600-pixel candidate and the source is 800 pixels wide, 800 pixels are rendered into a 1600-pixel box. Replace the source asset; no configuration fixes it.

Q: Is sharp required when self-hosting Next.js?

A: Yes. The built-in image optimizer requires the sharp package outside of Vercel, where the platform supplies its own optimization layer.

Q: Does next/image work with output: 'export'?

A: Not with the default loader, because a static export has no server. Set images.unoptimized to true to emit plain img tags, or configure images.loaderFile to point at an external image CDN.

Q: Is AVIF always the better choice?

A: No. AVIF produces smaller files than WebP at comparable quality but takes measurably longer to encode, so on images with low cache-hit rates that encode time lands on a real user's first request.