next-runtime icon indicating copy to clipboard operation
next-runtime copied to clipboard

Next/Image component downloads larger image than required

Open eliorg opened this issue 1 year ago • 1 comments

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.

sreenshot

"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>
  );
}

eliorg avatar Oct 17 '24 13:10 eliorg

Hi @eliorg, thanks for writing in!

As far as we can tell, what's going on here is that next/image is rendering an <img> tag that looks like this:

<img
  alt=""
  loading="lazy"
  width="1920"
  height="1674"
  decoding="async"
  data-nimg="1"
  class="w-full h-auto"
  style="color:transparent"
  srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanimal.d9281202.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanimal.d9281202.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanimal.d9281202.jpg&amp;w=3840&amp;q=75"
>

The interesting bit is that srcset attribute on the last line. See here for more on srcset.

It seems that it's telling browsers that there's a 1x format available at https:[...snip...].jpg?w=1920 and a 2x format available at https:[...snip...].jpg?w=3840, and so if your browser decides to load the 2x format it ends up upscaled to 2x the original dimensions. There's a bit of Next.js specific documentation on this here, so it seems this is expected behaviour.

Netlify's image CDN allows upscaling, so it gladly returns a 3840 px wide upscaled image when requested (2x) and a 1920 px image when requested (1x). On the other hand, the built-in image optimization via Vercel ignores upscaling requests by rewriting requested widths greater than the original width on the fly. It looks like they even applied the same thing to the other built-in image CDN loaders. On Netlify this seems to go through another code path that doesn't have this workaround, so this doesn't happen.

The end result is that you see a "blurry" image on Netlify and a "normal" image on Vercel, unfortunately. Thanks for reporting this. We'll think about this a bit more on our end and see if we can come up with a fix. In the meantime, I believe you should be able to pass a single size to the sizes prop to work around this.

serhalp avatar Oct 28 '24 19:10 serhalp