next/image

next/image and the Image Optimizer

next/image renders an <img>, but rewrites the src to point at /_next/image, a route that fetches the source file, resizes and re-encodes it with sharp, caches the result on disk and serves it with Vary: Accept. The component refuses to render without alt, and without either width and height or fill — those dimensions are not the display size but the aspect ratio, which the browser uses to reserve the right box before any image data arrives.

A fixed hero image and a fluid oneXML
<Image src="/hero.jpg" alt="Hopetoun Falls" width={640} height={427} preload />
<div style={{ position: "relative", aspectRatio: "3 / 2" }}>
  <Image src="/hero.jpg" alt="Hopetoun Falls" fill sizes="(max-width: 700px) 100vw, 50vw" />
</div>

The saving is not marginal. Serving a 2000x1333 photograph directly costs 466,866 bytes on every device; through the optimizer, at the width a 640-pixel slot needs, it costs a tenth of that in WebP and a twentieth in AVIF.

One source JPEG requested through /_next/image with different Accept headers
Request for the same 466,866-byte JPEG Bytes Served as
/hero.jpg, no optimizer 466,866 image/jpeg
w=640&q=75, Accept: */* 45,597 image/jpeg
w=640&q=75, Accept: image/webp 45,814 image/webp
w=640&q=75, Accept: image/avif 23,672 image/avif

The format is negotiated, not guessed: Next.js 10,514 reads the Accept header and picks the first configured format it matches, falling back to the original encoding otherwise. Headless Chrome 1 loading the page at device pixel ratio 1 downloaded the 45,814-byte WebP; at ratio 2 it took the w=1920 candidate from the same srcset, 347,066 bytes — still 26% less than the raw file, at four times the pixel count. The default quality is 75 and the default format list is ['image/webp']; AVIF costs one line of configuration and a slower encode.

Two props decide whether the optimizer helps or hurts. sizes tells the browser how wide the image will be at each breakpoint. Omit it and the browser assumes 100vw, and Next.js emits only a 1x/2x srcset instead of the full width-descriptor set built from deviceSizes and imageSizes — so a phone downloads a desktop-sized file. Next.js warns you in the browser console, in development only:

Output of 74
Image with src "/hero.jpg" has "fill" but is missing "sizes" prop. Please add it to improve
page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes
Image with src "/hero.jpg" was detected as the Largest Contentful Paint (LCP). Please add the
`loading="eager"` property if this image is above the fold.

The second message is the other half. Every next/image is loading="lazy" by default, which is wrong for the one image that is the Largest Contentful Paint: the browser will not discover it until layout runs. priority used to be the fix; as of Next.js 16 it is deprecated in favor of preload, which injects a <link rel="preload" as="image"> carrying the matching imageSrcSet. For most hero images loading="eager" or fetchPriority="high" is the better instrument.