next.js: Image component `sizes` property does not work
Bug report
Describe the bug
When using the Image component, the sizes
property doesn’t work as described in the docs.
When using the Image component, the image is resized to match the container width which is size of the width
attributes that we passed, or fit 100% of the viewport if the image is larger than the screen, ignoring the sizes
attribute completely.
Example of code:
<Image
width="800px"
height="456.8px"
sizes="(max-width: 500px) 100px"
src="/next.png"
alt="Next.js"
/>
To Reproduce
I created a codesandbox example with the exact problem:
- Go to https://codesandbox.io/s/elated-neumann-u81mr?file=/pages/index.js
- Resize the browser window width to be bigger than 500px to see that the image is the size of
width 800px
andheight 456.8px
- Resize the browser window width to be smaller than 500px to see that the image will fit to 100% of the screen, not respecting the
sizes
prop that should tell the image to be100px
Expected behavior
I expect the image to have exactly a 100px when the view port is smaller then 500px, respecting the sizes attribute.
Screenshots
Bigger viewport width
Smaller viewport width
System information
- OS: Ubuntu 20.04
- Browser: Chrome
- Version of Next.js: 10.0.0
- Version of Node.js: v12.18.3
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 16
- Comments: 47 (8 by maintainers)
Commits related to this issue
- Add missing `sizes` prop on image component (#19128) - Fixes #19120 - Closes #18413 - Related to #19041 — committed to vercel/next.js by styfle 4 years ago
Can we get this re-opened? If I’ve understood correctly, I don’t believe this is resolved.
The image component is incredible and a brilliant idea, but it seems responsive images are not behaving as expected.
When comparing @josepholiveira’s example here -
To a pure html example, they should behave the same shouldn’t they?
Heres a codesandbox with both a next.js and html example side by side, trying to load the same size responsive images - https://codesandbox.io/s/empty-morning-4gdp9?file=/pages/index.js
The html example is inline with the way the expected behavior quoted above by @josepholiveira, in that it is supplying a 100px image.
The Next.js example loads a 320px image (as seen in network tab), and ignores the sizes request of a 100px image. (I’m also not sure why its 320, as that value is not present in deviceSizes or imageSizes).
I know when using layout=“responsive” next.js automatically supplies images that are sized based on the viewport, those images are at the following sizes: 640, 750, 828, 1080, 1200, 1920, 2048, 3840..
But is this ignoring of a requested size in the
sizes
prop intentional? Why could the image component not supply a 100px sized image? Loading a 320px image, that is 3.2x bigger than needed feels like a big hit to performance.Maybe I am misunderstanding how responsive images or how the image component works, apologies if so. Thanks again for the great work! 😃
Thanks to @josepholiveira @styfle @AshConnolly & @LauraBeatris for your contributions here.
I finally understand how to use the sizes prop with Next.js. Here’s the simple explanation.
Can I add this to the docs via a separate pull request?
---------------------- ---------------------- ----------------------
Need a smaller size? Just specify a smaller visual width. The default is 100vw, meaning 100% of the viewport. For a 3-column layout, try 33vw – the closest image to 33% of the viewport will get served. You can also use media queries to get specific with your responsive image sizes. Don’t forget to add additional deviceSizes in next.config.js if you need sizes <640px for mobile devices. Otherwise, 640px will be the smallest size Next.js can serve. For convenience, you can specify a custom deviceSizes by simply adding the default imageSizes array (which goes down to 16px) before the default deviceSizes.
Once you add that code, you can conveniently use any small vw value (like 10vw) and know that there will always be an appropriate version on tiny mobile devices. The smallest served will be 16px, and you’ll have a total of 17 responsive image sizes served automatically by Next.js v10 or later.
---------------------- ---------------------- ----------------------
And here’s a bunch more info in case anyone finds this issue while searching online like I did.
Here’s an example that can be used with this Next.js + Tailwind CSS starter blog https://jamstackthemes.dev/theme/nextjs-tailwind-starter-blog
That starter blog has a nice overview of using Images in Next.js 10 here: https://tailwind-nextjs-starter-blog.vercel.app/blog/guide-to-using-images-in-nextjs
Unfortunately it’s not clear what to do with a layout=“fill” image that won’t fill the entire viewport, without reading the MDN docs several dozen times.
(This obvious “use a sizes value of 33vw for an image in a 3-column grid” example does not appear in the MDN docs.)
Here’s a basic example of a small responsive image using 33vw (33% of the visual width of the device’s viewport):
index.js
Here’s a full 3-column gird layout example that also has a long description of how you can use media queries in the string you pass as the “sizes” prop:
index.js
And here’s my Next.js config file:
next.config.js
Hope this helps someone. And @styfle if you approve I’ll open a pull request to make the changes to the docs. I don’t want to do it right this second, because I could be entirely wrong here 😅
@AshConnolly I’ve been dealing with a similar issue and I think I’ve figured some of this out. It’s what @styfle said above.
Basically the smallest image that Next generates by default is 640px. To allow Next to generate smaller images than 640px you need to create a
next.config.js
file at the root of your project and specify smaller device sizes. The code in your next config file should look like this in your case:Everything after the specific sizes you are looking for are the Next defaults.
Now you will have an image generated for each of those device widths. The
sizes
prop on the<Image />
are where you can override which viewport width will use which image source size which you have done. I think your code should just work as intended with those new device sizes.Here is another example of the sizes attribute not being used as expected - https://codesandbox.io/s/restless-thunder-m8b9u?file=/pages/index.js
I am requesting an image that is
The biggest I would want is 140px wide image. Next.js image component is supplying a 320px (28kb) image, for all sizes. That is vastly bigger than needed.
It also supplies a 640px when requesting a 82px wide image if i use layout=“intrinsic”: https://codesandbox.io/s/sweet-sanderson-fu2p0?file=/pages/index.js
(sometimes its 320px instead of 640px, not sure why it seems to change)
Why is this happening? A 640px image is vastly larger than an 82px wide image and will massively degrade performance.
Again, apologies if I am misunderstanding how responsive images or how the image component works.
I thought of that too! But giving the fact that the Image component generates the
srcset
for you, and that the docs doesn’t provide any info on that attribute I preferred to open this issue, since it would make more sense to the Image component handle that too in that case.@AshConnolly Your version of Next.js is very old, prior to the
layout
prop.I tried your code and it works correctly with Next.js
10.0.5
Next.js doesn’t do any manipulation to the
sizes
property.It is passed directly to the underlying
img
element, so that’s why the docs link to MDN.The thing to remember is that
sizes
depends onsrcset
, andsrcset
is derived fromdeviceSizes
in yournext.config.js
file. So make sure the width exists there.Also, PR #19128 is going to autogenerate
sizes
when the prop is omitted to remain compliant.I think this is still not fixed. I have the same issue. I’m simply trying to achieve
max-width:100%
effect we used in normal CSS.From next.js perspective, I want something in the middle of
intrinsic
andresponsive
From the docs:
The problem is, while using
intrinsic
next/image does not provide multiple device sizes likeresponsive
(640, 720 etc) instead it just gives 1x & 2x only. When usingresponsive
, it takes the whole container to fill in large screens. Not the max-width of image loaded.So, how can I achieve something in-between?
Expected Result:
If it’s supported already, can someone guide me? If not, it’s better to open this issue?
See Issue live on codesandbox
This should render at 200px maximum.
Thanks for replying @styfle.
Cool! I thought that seemed odd.
Unfortunately my issue is still present.
Can you clarify how would I go about getting the image component to return a small, non responsive image, at (for example) 82px width?
As seen here - https://codesandbox.io/s/sweet-sanderson-fu2p0?file=/pages/index.js
The component is returning a 320px wide image when asking for a 82px wide image. generated html:
320px isn’t even listed in
imageSizes:[16, 32, 48, 64, 96, 128, 256, 384]
. It’s not in deviceSizes either, so I’m not sure where this value is coming from.I’d expect the component to return a 96px wide image as that is the closest larger value in imageSizes.
On mobile devices with a 2x pixel density, if the component takes that into account (does it?), I’d expect a 256px image (the closest larger value, when the width is doubled for 2x pixel density).
If you could clarify how to do this, I’d appreciate it! 😃
This is because
imageSizes
is only used when generating the 1x/2x/3x srcSet for layout=“fixed” or layout=“intrinsic”.The
deviceSizes
are used forlayout="responsive"
andlayout="fill"
which generates a srcSet with all the device sizes.https://nextjs.org/docs/api-reference/next/image#sizes
I had the same issue but for now, as a workaround, I’m using the layout
fill
property with a container and media queriesIn that way, the image won’t shift the content and always respect the size of the container. Therefore, I would prefer to use the
sizes
propertyThanks for getting back @styfle! 😃
Interesting… so adding more sizes to deviceSizes should solve my
layout="responsive"
issue. Ty & ty @cadekynaston!As for my 2nd issue above with
layout="intrinsic"
…Why is srcSet used for non-responsive images? Also imageSizes contains values much smaller than 320px, but I keep getting a 320px image returned when requesting a small image (sandbox example in my previous comment is 82px wide).
Am I to believe that if i want to load a small, non responsive, image that is 48px wide (smaller than 320px which is often returned), I need to use
layout="intrinsic"
, and pass it asizes
prop (which becomes srcSet for responsive images), even though the image does not need to be responsive?That seems confusing and it doesn’t seem to work - https://codesandbox.io/s/billowing-bush-n3m0n?file=/pages/index.js
It still loads a 320px image:
So how would I go about getting the image component to return a small, non responsive image, at (for example) 48px width? As currently it keeps returning a 320px image.
Thanks for replying @cadekynaston! Appreciate it mate! 😃
That does seem to work, but it shouldn’t be needed as next already has an imageSize array by default, and it contains images that are much smaller than the 320px that is being loaded -
this is taken from - https://nextjs.org/docs/basic-features/image-optimization#image-sizes
So it seems that next might not always be using this imageSizes array, and not be concatenating it with deviceSizes as explained.
@styfle are you aware of this issue?
Cheers! 👍
@josepholiveira Yep you’re right with an unstyled raw image tag the sizes attr does control the width. I agree the wrapper divs + default styles do put you in a situation where you can’t just drop in the new Image component without adding some extra styles.
@Mistwell The Image component generates a
srcset
based off the default device sizes. You can override the defaults if you want to allow the browser to choose images wider then 1200. (unrelated tosizes
)I mean, it’s the CSS implementation of object-fit / object-cover & the box model that’s “broken” because you have to play by specific rules to get CSS to figure out width & height in advance if they’re not specified directly on the image.
And since it’s incredibly easy to wrap a custom component around another React Component, you could implement that feature yourself if whatever way makes most sense for your use case without next.js have to force users into certain use cases.
I totally agree that it’s annoying to have to mess around & add a bunch of
<div>s just to have a proper "dynamic" image for the use cases I prefer, and some examples in the docs would be great.You shouldn’t use
sizes
forlayout=intrinsic
.The reason why
layout=intrinsic
has a srcset is for high DPI (retina) displays.If you inspect the DOM or html response, you can see the srcset output.
Read more about srcset and sizes on MDN.
You’re welcome @AshConnolly! Yea I thought the same thing. Couldn’t get it to work with the
imageSizes
array at all.As @mrmcc3 said
Next applies a list of device width breakpoints by default which is used to handle the cases where the images are wider than the viewport.
For instance:
The final length of this image will be smaller than 1200px in order to fit the viewport
Refer to the
intrinsic
image layout, which is the default one:But if you want it to be fixed, regardless of
device sizes
orimage sizes
, thefixed
property is the way to go:It’s the intended size of the image so the browser can pick the best image from
srcSet
. From what I can tell it won’t actually style the image to be that size you’ve got to do that manually