question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Image component `sizes` property does not work

See original GitHub issue

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:

  1. Go to https://codesandbox.io/s/elated-neumann-u81mr?file=/pages/index.js
  2. Resize the browser window width to be bigger than 500px to see that the image is the size of width 800px and height 456.8px
  3. 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 be 100px

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 bigger-viewport

Smaller viewport width smaller-viewport

System information

  • OS: Ubuntu 20.04
  • Browser: Chrome
  • Version of Next.js: 10.0.0
  • Version of Node.js: v12.18.3

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:16
  • Comments:47 (8 by maintainers)

github_iconTop GitHub Comments

16reactions
AshConnollycommented, Jan 10, 2021

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 -

https://codesandbox.io/s/elated-neumann-u81mr?file=/pages/index.js

Expected behavior: I expect the image to have exactly a 100px when the view port is smaller then 500px, respecting the sizes attribute.

Actual behavior: 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 be 100px

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

export default function IndexPage() {
  return (
    <div>
      <Image
        width="1000"
        height="571"
        sizes="(max-width: 500px) 100px, (max-width: 1023px) 400px, 1000px"
        src="https://i.imgur.com/3QPVI5K.png"
        alt="Next.js"
      />
      <img
        srcSet="https://i.imgur.com/CLjcs9D.png 100w,
        https://i.imgur.com/8Jqeo06.png 400w, 
        https://i.imgur.com/3QPVI5K.png 1000w"
        sizes="(max-width: 500px) 100px, (max-width: 1023px) 400px, 1000px"
        src="https://i.imgur.com/3QPVI5K.png"
        alt="Next.js"
      />
    </div>
  );
}

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! 😃

12reactions
DoctorDerekcommented, Feb 9, 2021

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.

                module.exports = {
                images: {
                  imageSizes: [],
                  deviceSizes: [16, 32, 48, 64, 96, 128, 256, 384, 512, 640, 750, 828, 1080, 1200, 1920, 2048, 3840],
                },}

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

          <div>
            <div className="relative h-36">
              <Image
                src="/static/images/ocean.jpeg"
                className="object-cover rounded-full"
                layout="fill"
                sizes="33vw"
              />
            </div>
          </div>

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

      <div className="max-w-3xl px-4 mx-auto sm:px-6 xl:max-w-5xl xl:px-0">
        <div className="grid grid-cols-3 gap-1 text-center md:gap-2 xl:gap-3">
          <div>
            <div className="relative h-36">
              <Image
                src="/static/images/ocean.jpeg"
                className="object-cover rounded-full"
                layout="fill"
                sizes="(min-width: 768px) 256px, (min-width: 1024px) 384px, 128px" // 128px used if width < 768px

                // Tailwind CSS explanation
                // max-w-3xl
                //   {max-width: 48rem/* 768 px */;}
                // xl:max-w-5xl
                //   @media (min-width: 1280px) {max-width: 64rem/* 1024px */; }
                // h-36
                //   {height: 9rem/* 144px */;}

                // These "sizes" result in almost the same behavior as 33vw.

                // A minimized Chrome window is 500px and will load the 128px
                // version. Later, when using 33vw, the 256px version will load.

                // That's because 33% (33vw) of 500px is 165px, so 256px loads.
                // (The resulting image is 148x144 when scaled with CSS.)

                // Note that this requires the following in next.config.js:
                /*
                module.exports = {
                images: {
                  imageSizes: [16, 32, 48, 64],
                  deviceSizes: [96, 128, 256, 384, 512, 640, 750, 828, 1080, 1200, 1920, 2048, 3840],
                },}
                */
                // You can inspect the image in the HTML code in Chrome in order
                // to find out its "intrinsic" size, as served by next.js.

                // Due to caching, you'll need to load a new private window at
                // the window size you want to test in Chrome.

                // You can also run Lighthouse in Google DevTools to test sizes.
              />
            </div>
          </div>
          <div>
            <div className="relative h-36">
              <Image
                src="/static/images/ocean.jpeg"
                className="object-cover rounded-full"
                layout="fill"
                sizes="33vw"
              />
            </div>
          </div>
          <div>
            <div className="relative h-36">
              <Image
                src="/static/images/ocean.jpeg"
                className="object-cover rounded-full"
                layout="fill"
                sizes="33vw"
              />
            </div>
          </div>
        </div>
      </div>

And here’s my Next.js config file:

next.config.js

module.exports = withBundleAnalyzer({
  images: {
    /* This is because imageSizes is only used when generating the 1x/2x/3x srcSet for layout="fixed" or layout="intrinsic".

    The deviceSizes are used for layout="responsive" and layout="fill" which generates a srcSet with all the device sizes. */
    imageSizes: [16, 32, 48, 64], // This array is concatenated to deviceSizes.
    // imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Next.js default
    deviceSizes: [96, 128, 256, 384, 512, 640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    // deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // default
  },
  // pageExtensions: etc.

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 😅

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to use Image component in Next.js with unknown width ...
The idea: get size of image when it's loaded, calc the ratio and use it to make the container fill the require space...
Read more >
Nextjs image optimization with examples - Refine Dev
The sizes prop only works for images with layout="responsive" or layout="fill" .
Read more >
next/image - Next.js
The sizes property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify...
Read more >
The Media or Image Source element - HTML - MDN Web Docs
A list of source sizes that describes the final rendered width of the image represented by the source. Each source size consists of...
Read more >
@astrojs/image Astro Documentation
This is the helper function used by the <Picture /> component to build multiple sizes and formats for responsive images. This helper can...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found