Direct answer: The
next/imagecomponent requires eitherwidthandheightprops or afillprop to function correctly. When usingfill, omitting thesizesprop causes the browser to default to100vw— treating the image as full-viewport-width and downloading the largest available version. On high-resolution desktop screens this means unnecessarily large image downloads. Providing accuratesizesvalues tells the browser the actual rendered size of the image, allowing it to download a much smaller, appropriately sized version.
We were facing slow image loading across parts of our application. My first instinct was CDN caching — we weren’t caching images through a CDN the way we were with other static assets, and due to cost constraints that wasn’t going to change immediately.
But it felt too slow to be explained by CDN caching alone. A missing cache header adds latency, but what I was seeing felt heavier than that. So I dug deeper.
What I found was that our next/image components had two distinct problems: some were missing width and height props entirely, and the ones using fill had no sizes prop on any of them. Once I understood what those props actually do under the hood, the slowness made complete sense.
What next/image Actually Does
Before getting into the props, it’s worth understanding what the next/image component gives you over a plain <img> tag — because this context makes the props make more sense.
next/image automatically:
- Converts images to modern formats like WebP (significantly smaller than JPEG or PNG)
- Generates multiple resized versions of the image at different widths
- Lazy loads images by default — only downloading when they approach the viewport
- Prevents Cumulative Layout Shift (CLS) by reserving space before the image loads
The key phrase is “generates multiple resized versions.” Next.js builds a srcset — a list of the same image at different widths — and the browser picks which one to download based on the device’s screen width and pixel density.
The props you pass directly control how intelligently that selection happens.
The First Problem: Missing width and height
Some of our images had neither width nor height nor fill. This is an error — next/image requires one or the other.
What width and height do:
These props define the intrinsic dimensions of the image in pixels. They serve two purposes:
- They prevent layout shift. The browser knows how much space to reserve before the image loads, so nothing jumps around. This directly improves your CLS score.
- They anchor the generated
srcset. Next.js uses thewidthvalue as the largest size to generate, and creates smaller variants from there.
// ✅ Correct — fixed dimensions
<Image src="/hero.jpg" alt="Hero image" width={800} height={450} />
Without these, Next.js doesn’t know the intended size of the image, can’t generate an appropriate srcset, and can’t reserve layout space. You get a console error and undefined behaviour.
The Second Problem: fill Without sizes
The rest of our images were using the fill prop — which is the right choice when an image needs to fill its parent container rather than have fixed pixel dimensions. Common use cases are hero sections, card thumbnails, and background images.
// fill — the image stretches to fill its parent
<div style=>
<Image src="/banner.jpg" alt="Banner" fill style= />
</div>
fill works correctly. But without a sizes prop alongside it, something problematic happens.
What sizes does and why it matters:
When Next.js generates a srcset for an image, it creates multiple versions at different widths. The browser then has to choose which one to download. To make that choice, it needs to know how wide the image will actually be rendered on screen.
Here’s the problem: the browser makes this decision before the page’s CSS has loaded and been applied. It doesn’t yet know that your image is inside a two-column grid, or that it’s constrained to a 400px sidebar. All it knows is the viewport width.
So without a sizes hint from you, it falls back to the safest assumption: the image will be as wide as the viewport — 100vw.
On a 1440px desktop screen with a 2x Retina display, 100vw means the browser targets a 2880px wide image. Even if the image is only rendered at 400px. The result is downloading an image up to 7x larger than necessary.
This is confirmed in Next.js’s own documentation — if you don’t specify a sizes value on an image with the fill property, a default value of 100vw is used, and the browser selects an image that is the same size or larger than the viewport.
How sizes fixes this:
sizes is a hint to the browser describing how wide the image will actually appear at different viewport widths. It uses the same media query syntax as CSS.
// Image that's full-width on mobile, half-width on desktop
<Image
src="/banner.jpg"
alt="Banner"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style=
/>
Now the browser knows: on screens narrower than 768px, the image is full width — download accordingly. On wider screens, the image is half the viewport — download a smaller version.
For a 1440px desktop screen with sizes="50vw", the browser targets a 720px image (or 1440px on Retina) instead of 2880px. That’s a dramatically smaller file.
Desktop vs Mobile: Where the Problem Hits Hardest
The missing sizes problem is worse on desktop than mobile — but for a reason that’s slightly counterintuitive.
On mobile, viewport widths are smaller (typically 375–430px). Even with the 100vw default, the browser is selecting images for a ~400px wide screen. The mismatch between “actual render size” and “assumed viewport size” is smaller, so the wasted bytes are less severe.
On desktop, viewports are 1280–1920px, and many devices are high-DPI (2x or 3x pixel ratio). The browser targets viewport width × pixel ratio — so a 1440px screen at 2x means targeting a 2880px image. If your image is actually rendered at 300px (say, a card thumbnail in a grid), you’re downloading nearly 10x more data than needed.
This is why the slowness was more noticeable in grid layouts and multi-column designs than on single-column mobile views.
The sizes Prop in Practice
Here are the patterns we used to fix our images:
Full-width hero image:
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="100vw"
style=
/>
Image in a two-column grid:
<Image
src="/card.jpg"
alt="Card"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style=
/>
Image in a three-column grid:
<Image
src="/thumbnail.jpg"
alt="Thumbnail"
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
style=
/>
Image in a fixed-width sidebar:
<Image
src="/profile.jpg"
alt="Profile"
fill
sizes="240px"
style=
/>
The values don’t need to be pixel-perfect. A reasonable approximation is enough — the browser uses them to select from a pre-generated srcset, and picking the next size up is always acceptable. What you’re avoiding is the browser treating a 240px sidebar image as a full-viewport image.
A Quick Reference for All the Props
| Prop | Required | Purpose |
|---|---|---|
src |
✅ Always | Image URL or imported file |
alt |
✅ Always | Accessibility and SEO |
width + height |
✅ Unless using fill |
Fixed dimensions in px — prevents layout shift |
fill |
✅ Unless using width/height |
Stretches image to fill parent container |
sizes |
⚠️ Always recommended | Tells browser actual render width — required with fill, strongly recommended with width/height for responsive images |
priority |
Optional | Disables lazy loading — use for above-the-fold images (LCP) |
quality |
Optional | JPEG/WebP compression quality, 1–100 (default 75) |
placeholder |
Optional | "blur" shows a blurred placeholder while loading |
What Actually Fixed the Slowness
After adding proper width and height to fixed images and sizes to all fill images, the improvement was noticeable — especially in grid layouts on desktop.
The CDN caching issue was real and still on the list to address. But it wasn’t the primary cause. The browser was downloading images 5–10x larger than necessary on every page load, on every device, on every visit — cached or not. Fixing the props reduced the image payload significantly before any caching optimisation was even applied.
The lesson: next/image gives you a lot for free, but it can only optimise based on what you tell it. Without width/height, it doesn’t know the image’s intended size. Without sizes, it assumes full-viewport. Both omissions lead to the same outcome — oversized images served to every user.
Frequently Asked Questions
Why are my Next.js images loading slowly despite using next/image?
The most common causes are missing width and height props on fixed images, or a missing sizes prop on images using fill. Without sizes, the browser defaults to assuming the image is full-viewport-width and downloads the largest available version — even if the image is only rendered at a fraction of that size.
What does the sizes prop do in next/image?
sizes tells the browser how wide the image will actually be rendered at different viewport widths, using media query syntax. The browser uses this to pick the most appropriate version from Next.js’s auto-generated srcset. Without sizes on a fill image, the browser defaults to 100vw and downloads the largest available version. With width and height, omitting sizes generates only a fixed 1x and 2x srcset — which works, but providing sizes unlocks a fuller responsive srcset for more precise optimisation.
What is the difference between fill and width/height in next/image?
width and height define fixed pixel dimensions for the image. fill makes the image stretch to fill its parent container — useful for responsive layouts where the image should cover a variable-sized area. When using fill, the parent must have position: relative set, and you should always add a sizes prop to prevent oversized image downloads.
Does missing sizes affect mobile or desktop more?
Desktop is affected more severely. Mobile viewports are narrower, so the gap between “assumed full-viewport size” and “actual render size” is smaller. On desktop, especially high-DPI screens where the browser targets viewport width × pixel ratio, the mismatch can result in images 5–10x larger than what’s actually rendered.
When should you use the priority prop on next/image?
Use priority on any image that is the Largest Contentful Paint (LCP) element — typically the main hero image or the first visible image above the fold. priority disables lazy loading and preloads the image, improving LCP scores. Avoid using it on below-the-fold images as it defeats the purpose of lazy loading.
Co-written with AI.