The src emitted by Image did not, by itself, reveal the width actually downloaded. In this test, an image whose src pointed to width 3840 had a browser currentSrc pointing to width 640.
Inspect generated img attributes, the URL chosen by the browser, and that URL's response separately. This comparison uses the same synthetic image for ordinary img and Next.js Image.
Give the images the same CSS display width
The source is a 1600×1000 PNG. Identical pixels were saved under different filenames to avoid sharing the same URL cache between images. It is a regular color pattern for testing, not an input representative of photographic compression.
The page was:
import Image from 'next/image'; const responsiveStyle = { display: 'block', width: '50vw', height: 'auto' }; export default function Page() { return <main> <img id="plain" src="/plain.png" alt="Plain test pattern" width={320} height={200} style={responsiveStyle} /> <Image id="fixed" src="/fixed.png" alt="Fixed candidates" width={320} height={200} style={responsiveStyle} /> <Image id="responsive" src="/responsive.png" alt="Responsive candidates" width={320} height={200} sizes="50vw" style={responsiveStyle} /> <div style={{ height: 10000 }} /> <section id="lazy-pair"> <img id="lazy-plain" src="/lazy-plain.png" alt="Native lazy test" width={320} height={200} loading="lazy" /> <Image id="lazy-next" src="/lazy-next.png" alt="Next lazy test" width={320} height={200} /> </section> </main>; } The first 3 images have a CSS width of 50vw and height auto. Their width attribute is 320, but CSS changes their display width with the viewport. The other 2 images sit farther down the page for a lazy-loading comparison.
The tested viewport was 1280×720 with devicePixelRatio 1. All 3 upper images had a measured display width of 640.
src and currentSrc can point to different candidates
| Image | sizes | srcset form | Width selected through currentSrc |
|---|---|---|---|
| Ordinary img | Absent | Absent | Original 1600 |
| Image without sizes | Absent | 384 as 1x, 640 as 2x | 384 |
| Image with sizes | 50vw | Candidates from 384w through 3840w | 640 |
Without sizes, Image generated density candidates based on the supplied width. Even though CSS expanded the display width to 640, this input selected the 384-wide candidate.
With sizes, Image generated width candidates that the browser could choose from using the viewport and 50vw. The selected URL was /_next/image?url=%2Fresponsive.png&w=640&q=75, while that same img's src attribute used w=3840.
The Image sizes reference explains the different candidate forms and specifies sizes for images made responsive through CSS. Check the relationship between the JSX width, the CSS display width, and the sizes supplied to the browser.
This test covers DPR 1 and one viewport size. It does not establish that other widths, DPR values, or browsers choose the same candidates.
Inspect the response from the selected URL
The currentSrc chosen by the browser was fetched again with an Accept header listing image formats. The record contains response Content-Type and body bytes, using default image-optimization settings.
| Image | Content-Type | Response body bytes |
|---|---|---|
| Ordinary img, source PNG | image/png | 494961 |
| No sizes, width 384 | image/webp | 926 |
| With sizes, width 640 | image/webp | 1842 |
These are HTTP response-body sizes, not total traffic including request/response headers or perceived loading speed. Browser Resource Timing also recorded encodedBodySize values of 926 and 1842 for the two optimized images.
This synthetic PNG compresses easily, so the differences are not photographic compression ratios. Image quality, LCP, and optimization-server CPU time were not measured. A smaller 384-wide response is not automatically the appropriate choice; its relationship to display width matters too.
Lazy loading is available to ordinary img too
A blank block with height 10000 separated the lower pair from the upper images. The ordinary img explicitly used loading="lazy"; Image had no loading prop.
Initially, both had naturalWidth 0 and an empty currentSrc. After scrolling to their section and waiting, both loaded. The img generated by Image also had loading="lazy".
The loading reference specifies lazy as Image's default. Lazy loading alone therefore does not distinguish Image from an ordinary img configured for it. The upper ordinary img without loading and the lower one with an explicit loading attribute are different conditions.
The test did not measure the pixel distance that triggers fetching. It established only that these images were unloaded at the initial position and loaded after scrolling to them.
The tests ran on September 11, 2026, with Next.js 16.3.4, installed React and React DOM 19.3.0, Node.js 24.15.0, macOS arm64, and Headless Chrome 152. They used a webpack production build and local next start. External images, custom loaders, unoptimized, preload, and CDNs were not compared. The pattern generator and runner and attribute, URL, and response records are available.
