vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.88k stars 26.86k forks source link

next/image not properly sizing images #44244

Open pjaws opened 1 year ago

pjaws commented 1 year ago

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 21.6.0: Thu Sep 29 20:12:57 PDT 2022; root:xnu-8020.240.7~1/RELEASE_X86_64
Binaries:
  Node: 18.12.0
  npm: 8.19.2
  Yarn: 1.22.19
  pnpm: N/A
Relevant packages:
  next: 13.0.8-canary.2
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

Image optimization (next/image, next/legacy/image)

Link to the code that reproduces this issue

https://github.com/pjaws/next-image-example

To Reproduce

With fill prop

  1. Create div with desired aspect ratio padding, i.e. padding-top: calc(2/3 * 100%);
  2. Add next/image as child of that div
  3. Add sizes attribute with desired breakpoints/widths
  4. Images are not requested at these sizes/srcset is always the same regardless of what's in sizes

Without fill prop, external image

  1. Create next/image with remote image, width and height props specified
  2. Image is sized correctly at large screen sizes
  3. Image is actually much larger at smaller screen sizes

Without fill prop, local image

  1. Create next/image with local image, no width or height specified
  2. Image is always served at maximum resolution

Describe the Bug

In almost every case, the image requested by next/image is larger than expected, resulting in unnecessarily large file sizes.

For images with the fill prop, the sizes attribute doesn't seem to actually do anything. In the screenshot below, images that should be around 396px are loading at 640px (1200px screen, 33vw image).

Screen Shot 2022-12-21 at 2 01 32 PM

For images without fill, instead of generating smaller files for smaller devices, the requested image is usually much larger than the specified width and height. In the screenshot below, an image that should be a maximum of 800x533px is being loaded at a width of 1920px on a screen 375px wide.

Screen Shot 2022-12-21 at 2 09 56 PM

This can also be seen on your own example page where the local Vercel image is set to 1000x1000px, but actually loads the full-size 1600px image at a device width of 375px.

Expected Behavior

"Always serve correctly sized image for each device"

I'm not expecting magic, but currently next/image is doing the opposite of what I want/expect. It would be nice to have some way to load smaller images at smaller screen sizes.

Which browser are you using? (if relevant)

Chrome 108.0.5359.124

How are you deploying your application? (if relevant)

No response

kerns commented 1 year ago

Nice catch and detailed reporting. 🙏

Tobi-mmt commented 1 year ago

I am facing the same issue. Are there any updates ?

staaky commented 1 year ago

Are you considering the pixel density of your device? In your screenshot the srcset contains x modifiers. It's picking the 1920px 2x image because of pixel density.

If you use a sizes prop like 100vw it should give you a srcset with w modifiers for a responsive image. Even in that case you'd have to consider device pixel ratio. If the srcset is for example small.jpg 400w, large.jpg 700w on a 375px viewport with a pixel density of 2 it'll actually load the large image.

prncss-xyz commented 1 year ago

Same problem, with page router as well as app router (13.3.1).

romainbriand commented 1 year ago

Facing the same problem:

<Image src={/...} width={40} height={40} />

Source image is 200x200 pixels but generated srcset calls an image version sized 48x48 for 1x and 96x96 for 2x 🧐

wzpfish commented 1 year ago

@romainbriand +1 <Image src={iconSrc} alt='' width={16} height={16} /> source image is 32x32 px but rendered size is 16x20, where is 20 come from?

zakinadhif commented 1 year ago

Currently having the same problem, source image is 720px in width, but rendered size is 750px in width -- messing with aspect ratio thus displaying rather blurry image.

omergencoglu commented 1 year ago

Facing the same problem. I'm getting lower score in PageSpeed Insights for the mobile version due to "Properly size images" opportunity.

jmcoder1 commented 1 year ago

I currently have the same problem, too

williambout commented 1 year ago

I can repro as well. In my <Image> component I specify a width of 1500px and a height of 1000px but the 2x image is 3840px wide.

CleanShot 2023-10-02 at 23 05 49@2x

CleanShot 2023-10-02 at 23 03 29@2x

Pedromigacz commented 1 year ago

I'm still having this issue on 13.5.1. When the window is 2048px wide, it generates me a nice 2048px image, but if the window is 2049px, it will try to generate a 3840px wide image and timeout. Here is my component

<Image
  src={HeroBackground} // https://651f9af212fba00008bba0ed--extraordinary-cucurucho-baaf57.netlify.app/hero_background.png source image is 4096x1868
  fill
  alt='Aerial view of New York'
  className='absolute top-0 left-0 h-full object-cover'
  placeholder='blur'
  priority
  style={{ objectFit: 'cover', maxWidth: '2048px' }}
  sizes='(max-width: 2048px)'
/>

Here's a permalink with the issue: https://651f9af212fba00008bba0ed--extraordinary-cucurucho-baaf57.netlify.app/

cjimmy commented 1 year ago

@Pedromigacz You're using sizes incorrectly. Look at examples, and MDN docs. It should be a media query, and then a size for the image, and the last value of sizes must not have a media query. Assuming you want a full-width image at 2048px, I think this is what you mean

  sizes='(max-width: 2048px) 100vw, 100vw'

next/image will fetch a 3840px image because that's the next image breakpoint. See https://nextjs.org/docs/app/api-reference/components/image#devicesizes to custom define it in next.config.js

also, nit: src should be a string, and your naming of HeroBackground suggests that it's a component. also, style={{ maxWidth: '2048px' }} constrains how the image is shown, not the resolution that it's fetched at. also, if you use placeholder='blur' you need to include a blurDataURL for that to work (docs).

As to why it's timing out, that sounds like some other issue...

(sorry to spam everyone on this thread)

jrnkng commented 1 year ago

Same issue here...

Edit: after playing around with the Image component a little bit it seems that for me adding sizes="100vw" has the desired result. I had already specified a static width and height My image component now looks like this:

      <Image src={src} alt={alt} width={width} height={height} quality={95} sizes="100vw"
      />

In which width and height are integers and src refers to a local image. Finally got a 100 PageSpeed score :)

jrnkng commented 1 year ago

Are you considering the pixel density of your device? In your screenshot the srcset contains x modifiers. It's picking the 1920px 2x image because of pixel density.

If you use a sizes prop like 100vw it should give you a srcset with w modifiers for a responsive image. Even in that case you'd have to consider device pixel ratio. If the srcset is for example small.jpg 400w, large.jpg 700w on a 375px viewport with a pixel density of 2 it'll actually load the large image.

What actually happens when I pass a 100vw prop to sizes?

hc0503 commented 10 months ago

@pjaws , I have same issue, do you have any updates?

GregorTB commented 9 months ago

I have the same problem...

Edit by maintainers: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

giovaborgogno commented 3 months ago

setting style={{objectFit: "contain"}} works properly for me.

<Image src={src} width={500} height={500} style={{objectFit: "contain"}}></Image>
hayyaun commented 2 months ago

Have anyone managed to solve this problem?

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

Ma-Kas commented 2 months ago

Same issue still present in 14.2.4

e.g. an Image component with the following: sizes='(max-width: 1100px) 100vw, 1100px'

seems to correctly construct srcset when inspecting (albeit pointlessley up to the max breakpoint of 3840), but always loads the 1200 breakpoint version even on mobile.

evejk commented 1 month ago

I had the same issue and after two days of pulling my hair out, I concluded that this is a feature and not a bug… (Although I’m not sure all reported issues here are due to the same cause...)

As someone above already mentioned: The Image component takes DPR into account. So if your viewport is 200px width and DPR is 2x, it will try to serve the image closest to 400px. The browser will default to your machines' resolution, which in my case is 2x (MacBook).

You can easily test this in the dev tools when enabling the DPR dropdown and switching between 1/2/3 DPR settings. You may need to enable the DPR setting first to see it. In Chrome/Arc it is not visible by default. (After I tested the different settings and checked the network tab for the loaded image size, the world made sense again…). CleanShot 2024-08-22 at 22 08 51@2x

CleanShot 2024-08-22 at 21 51 08@2x


If you enable an emulated device in the browser, it will use the appropriate DPR for that device. --> Dimensions Samsung Galaxy S8+ y

I haven’t found a way to avoid this behavior besides just using a regular ol’ img-tag with x descriptors.

Another (overcomplicated) way might be to use the new-ish getImageProps() to create an image-set and tell it to use the same image size for all resolutions – but that's just an outlandish theory I haven't tested (yet).

Also: unless you set/override them manually in the .config, Next uses default deviceSizes and imageSizes to generate the srcSet which are: deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840] imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]

-> which might explain why @romainbriand is getting 48x48 and 96x96 instead of 40/80 if sizes prop is set
 -> which might explain why @zakinadhif is getting 750 instead of 720 -> which also explains the max breakpoint of 3840 @Ma-Kas

hope it helps :)

eliorg commented 1 day ago

I noticed next/image's size is set differently depending on where you host your Next.js project.

I uploaded a 1 page Next.js project displaying an image on Netlify and Vercel.

On Vercel, the image's intrinsic size is 1920x1674 - https://newapp-eight-tawny.vercel.app/ On Netlify the image, is intrinsic size is 3840x3348 - https://inquisitive-sunshine-89a4b0.netlify.app/

These values can be seen in the bottom right corner of each image below.

screnshot

"The Rendered size is the portion of the page that the image takes up. The Intrinsic size is the original size of the image."

Here is the code for both pages

import Image from "next/image";
import animal from "../public/images/animal.jpg";

export default function Home() {
  return (
    <div>
        <Image className="w-full h-auto" src={animal} alt=""></Image>
    </div>
  );
}