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.
<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.
| 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:
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.