What you might not know about Next Image

If you've worked with Next.js, chances are you've come across the Next Image component. This hassle-free image optimization solution not only supports modern formats like webp and avif, but also generates several versions adapted to different screen sizes.
To take advantage of this magic, you just need to add the following code to your page:
import Image from 'next/image'; export default function Page() { return ( <Image src="/profile.png" width={500} height={500} alt="Picture of the author" /> );}
But, as with any magic trick, there's a lot of work behind the scenes that makes everything run smoothly. In this article we'll look at how Next Image works and clear up some widespread misconceptions about it.
Core architecture
The architecture of next/image relies mainly on three pieces:
- The Next Image React component
- The Image API
- The image optimizer
React component
The component's main job is to generate the correct image HTML from the props it receives and build the different URLs placed in the srcset and src attributes. Here's an example of what the Next Image component returns:
<img alt="Example" loading="lazy" width="500" height="500" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x" src="/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75">
Let's take a closer look at the generated URL:
/_next/image?url=/images/example.jpg&w=640&q=75
This encoded URL accepts two parameters, w (width) and q (quality), which are easier to read in the decoded version. Notice there's no h (height) parameter; we'll talk about that later.
Image API
Next's Image API works as an image proxy, similar to IPX. It's responsible for:
- Receiving an image URL, a width, and a quality
- Validating the parameters
- Determining cache policies
- Processing the image
- Serving the image in a format compatible with the user's browser
Now that things are starting to click into place, let's briefly go over the last piece of the puzzle before drawing conclusions.
Image optimizer
Next Image uses different image optimization libraries, Sharp or Squoosh, depending on certain conditions:
Sharp is a fast, efficient Node.js module for optimizing images that relies on the native libvips library.
Squoosh is an optimization solution built entirely on Node. It's slower, but doesn't require installing additional libraries on the machine. That's why Sharp is recommended in production, while Squoosh is used by default in local environments.
We recommend using Sharp locally as well. Although Sharp and Squoosh optimize images in fairly similar ways, Sharp's compression algorithms can slightly degrade colors compared to Squoosh. This can cause visual differences between production and local, especially when you're trying to match an image's background color to the page background.
Practical takeaways
Now that we understand the architecture of next/image, we can bust a few common myths and pull out some ideas for using it better.
next/image doesn't crop
A very common mistake among developers is thinking that next/image crops images. The confusion comes from being able to pass the component width, height, and fill props, which gives the impression that the image has been cropped. In reality, that's not the case. The Next Image component mainly needs the width and height to assign them to the img tag and avoid layout shifts.
As we've already seen, the Image API doesn't accept a height parameter, so today it isn't possible to change the aspect ratio of the original image. If you don't use the fill prop, when the width and height don't match, the image will simply stretch or shrink.
That said, if you use TailwindCSS the behavior is different because of its default global CSS rule:
img,video { max-width: 100%; height: auto;}
This makes layout shift issues harder to spot.
Displayed width ≠ loaded width
Another possible source of confusion is that the width prop you pass to next/image isn't the actual width the image is resized to. As we saw in the example at the start, passing width={500} to the component causes the image to be resized to a 640px width, as shown in the generated URL:
/_next/image?url=/images/example.jpg&w=640&q=75
If you expect the retina x2 version to use a 1000px or 1280px wide image, you're in for a surprise: the actual width will be 1080px. It's natural to wonder where these numbers come from.
Next.js resizes images to the closest size in the deviceSizes and imageSizes arrays, which you can define in next.config.js. By default they are:
module.exports = { images: { deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], },};
The important thing here is that the default configuration can hurt performance and lower your PageSpeed Insights score in Lighthouse. This is especially noticeable when you show large images on a page. For example, if you want to render an image 1250px wide, the image that gets loaded will be 1920px. The gap between the needed size and the loaded size is even bigger for retina x2 versions, which get resized to 3840px. Fortunately, you can fix this by adding more sizes to the deviceSizes or imageSizes arrays (documentation).
You can optimize images without the next/image component
Knowing the core architecture, it's easy to see that you can use the Image API without going through next/image. There are several cases where this comes in handy.
First, you can render optimized images inside a canvas. Whether you load images into the canvas from external sources or from local storage, you just need to pass the right URL to the API and it will work seamlessly.
You can also use it to optimize OG images or to build your own component based on the <picture> tag and get better control over art direction.
The Image API lives at the /_next/image route and only accepts three additional parameters: URL, width (w), and quality (q).
/_next/image?url=https://example.com/test.jpg&w=640&q=75
Keep in mind that the API validates the width parameter and it can only be a number included in the deviceSizes or imageSizes configuration.
Use import for local images
With next/image there are two ways to load local images:
import Image from 'next/image';import profileImg from './profile.jpg';export default function Page() { return ( <> {/* Using absolute path */} <Image src="/profile.png" width={500} height={500} alt="Picture of the author" /> {/* Using imported image via relative path */} <Image src={profileImg} alt="Picture of the author" /> </> );}
Using an absolute path is common with local images in examples, tutorials, and even open source projects. It's easy to assume there's no meaningful difference beyond the automatic width and height assignment. But there is. When you access images in the public folder via an absolute path, Next.js respects the destination server's cache policies, which by default means a 30-day cache instead of public,max-age=31536000,immutable. A 30-day cache policy for images can noticeably lower your Lighthouse score.
How sizes works and the 100vw trick
The next/image component accepts a prop called 'sizes', equivalent to the sizes attribute of the HTML img tag. But, as with other things we've seen, it also does some things of its own. The 'sizes' attribute works together with 'srcset' and accepts a list of browser conditions and the image widths that should apply in each case. If you're not familiar with it, we recommend checking out this documentation and this codesandbox example. Here's an example of an image using 'sizes':
<img srcset="/img/html/vangogh-sm.jpg 120w, /img/html/vangogh.jpg 193w, /img/html/vangogh-lg.jpg 278w" sizes="(max-width: 710px) 120px, (max-width: 991px) 193px, 278px">
Let's dig into this a bit more to understand it better. When you use Next Image without specifying the 'sizes' prop, your 'srcset' will include two URLs: one for the standard version (x1) and one for the Retina version (x2). With this setup, the browser will always pick the Retina version on a Retina device. That's because 'srcset' uses the 1x and 2x syntax.
<img srcset=" /_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x "/>
Basically, the browser reads it as: "Load this URL for a 2x pixel density and this other one for a 1x density." So if your design shows the image smaller on desktop than on mobile or tablet, with Next Image's default syntax the browser will always load the large version. Unfortunately, this can hurt performance and lower your Lighthouse score.
However, there's a way to tell the browser to load images based on the appropriate width. Instead of adding 1x and 2x parameters to the 'srcset' URLs, you specify the image width. Look at these instructions for the browser:
<img srcset=" /_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 640w, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 1080w "/>
In this case, the browser picks the most suitable image for the size it's displayed at on the page. If an image takes up 600px of width on mobile (1200px on Retina), it will choose the 1080w version. On the other hand, if it only takes up 300px on desktop (600px on Retina), the browser will opt for 640w.
The advantage of this approach is that the images loaded best match the screen size, which improves performance by reducing their weight. Now that we know the benefits, we can apply this strategy in Next Image with the 100vw trick. Although you can't directly tell Next Image to use width parameters (w) alongside the URL instead of the pixel density options (1x), you can rely on a workaround that comes from how Next Image is coded:
- If your 'sizes' attribute contains values in
vw, it will only keep the sizes larger than the smallestdeviceSize(640 by default) multiplied by the percentage (100vw= 1,50vw= 0.5). If you specify100vw, you'll get 8 URLs. - If your 'sizes' prop contains values that aren't
vw, your 'srcset' will include ALL THE SIZES (i.e., every possible combination ofdeviceSizesandimageSizes), for a total of 16 URLs.
To see this clearly, let's look at the code generated with 100vw:
<img sizes="100vw" srcset=" /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=640&q=75 640w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=750&q=75 750w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=828&q=75 828w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1080&q=75 1080w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1200&q=75 1200w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1920&q=75 1920w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=2048&q=75 2048w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75 3840w " src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75"/>
If you include a px value inside 'sizes' (e.g. (max-width: 1024px) 800px, 300px), the list of URLs grows even further, reaching 16 with the default configuration. Ideally you'd generate 4 URLs for a given image, as other frameworks do, instead of bloating the HTML with a lot of unnecessary options, none of which might have the exact size you need.
All of this makes one thing clear: to fill 'srcset' with more versions and improve performance across different resolutions, you just need to set 'sizes' to 100vw. This trick forces the creation of URLs for 8 sizes, starting at 640px.
That said, since this method can significantly increase HTML size, especially if you've added extra imageSizes or deviceSizes, it's worth applying carefully.
Although we can only speculate about the exact reason behind this architecture, we assume that in large projects, with images of different proportions used in many places, generating medium-sized versions can be useful. Those versions would cover most cases and would probably make better use of caching, without sacrificing ease of use.
Conclusions
Although Next Image simplifies image management and brings significant benefits, it could use additional features like advanced cropping and precise resizing offered by third-party solutions. A dedicated <picture> component for fine-tuning art direction would also be helpful. We especially miss an automatic way to generate four versions of an image at 0.25x, 0.5x, 1x, and 2x of the given width.
Even so, for most use cases, the developer experience and efficiency of Next Image are more than enough.