Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Next.js automatic image optimization with next/image

9 min read 2557

Next.js Automatic Image Optimization Next/Image

A picture is worth a thousand words, and images are an important part of the web to communicate with users. Defining them in their most basic form is straightforward and done with the humble <img> element:

<img src="image.jpg">

Ordinarily, the sourced image, image.jpg, is embedded into a web page, assuming the image is in the same directory as the HTML page.

You can — and you should — go further in this canonical form of image definition by adding an alternative text (alt) that describes an image if a browser or screen readers can’t load it:

<img src="image.jpg alt="describe the image here"/>

But with images on the web, the devil is in the detail. As the web evolved, so did the need for image optimization, whether it be for user or developer experiences.

To ensure users are served the most optimal image available, aspects like image size, web formats, and responsiveness must be addressed.

Solving for the user’s need is doable through the img element’s plethora of APIs for optimization. However, they can quickly become unwieldy to unpack. This is where automatic image optimization can benefit developers.

The case for automatic image optimization in Next.js

Frameworks like Next.js offer an abstraction that solves the most common, mundane, and complex tasks like routing, internalization, and image optimization.

According to the Next.js team, the goal with Next.js is to improve two things: developer and user experiences. And while most of the optimization focuses on reducing the amount of JavaScript shipped to users, there are other aspects like images that need optimization as well. Enter Next.js 10.

Next.js 10 welcomed a built-in image optimization API, next/image, as a canonical form for native automatic image optimization, providing five benefits.

Improved user experience

With optimized images that are loaded lazily by default, users can expect a performance boost in website load time, ultimately improving the overall user experience.

We made a custom demo for .
No really. Click here to check it out.

Good developer experience

With next/image’s simple-to-use API, developers have an improved experience themselves with the ability to define a basic image, tweak it to their heart’s content, or delve into advanced configuration options like caching and loaders.

Unaffected build times

Build times aren’t increased as a side-effect of optimization because Next.js optimizes images on-demand as users request them, instead of at build-time.

Modern image techniques and formats

Images are lazy-loaded by default and can be served in modern formats like WebP in supported browsers.

Future proof

Next.js can also automatically adopt future image formats and serve them to browsers that support those formats.

Using the next/image API

The next/image API is the sweet spot of image optimization in Next.js. It exposes an <Image/> component as a conventional single-source of truth. This means you only need to learn how to use one API to handle image optimization in Next.js.

By design, and in its most basic form, the <image/> component is fundamentally similar to the HTML img element because they both accept a src and alt attribute/property:

// 1. Import the `Image` component from the `next/image` API
import Image from 'next/image';

export default function CardImage({imageSrc, imageAltText}) {
    return (
        <div classname="cardImageWrapper">
            {/* 2. Use the `Image` component as you would any other component  */}
            <Image src={imageSrc} alt={imageAltText}/>
        </div>
    )
}

The functionality of the <Image/> component can extend with a number of props available in it. Let’s take a look at them.

src prop

The src is the single source of truth for the <Image/> component:

<Image src="image.webp"/>

To use the image src, it must be one of the following:

  1. A path string as an internal path, like the code snippet above, depending on the loader
  2. A path string as an absolute external URL with domain options set in next.config.js
  3. A statically imported image file

In this case, we’ll import a static image file:

import Image from 'next/image';
// Import a static image file
import defaultCardImage from '../public/defaultCardImage.webp';

export default function CardImage({imageSrc = defaultCardImage, imageAltText}) {
    return (
        <div classname="cardImageWrapper">
            <Image src={imageSrc} alt={imageAltText}/>
        </div>
    )
}

width and height props

The absolute width of an image is in pixels. This is required except for statically imported images or images with the layout prop set to fill:

<Image
    src="image-src"
    alt="image-alt-text"
    width={40}
    height={40}
/>

loader prop

loader is a custom function that resolves URLs. Given the src , width , and quality parameters, it returns a URL string:

import Image from 'next/image'

// You can add as many loader as you want, and use them conditionally.
// See https://nextjs.org/docs/basic-features/image-optimization#loader
const sanityIoImageLoader = ({ src, width, quality }) => {
  return `https://cdn.sanity.io/${src}?w=${width}&q=${quality || 75}`
}

function CardImage() {
  return (
    <Image
      loader={sanityIoImageLoader}
      src="image-src"
      alt="image-alt-text"
      width={500}
      height={500}
    />
  )
}

sizes prop

The sizes prop is akin to the HTML img element sizes attribute. With that, sizes are set to indicate a set of source sizes as described in the MDN docs:

<img src="image-src" alt="image-alt-text" srcset="(max-height: 500px) 1000px"/>

This means that if the viewport is less than 500px, use a source of 1000px width. With the <Image/> component, you can specify the sizes like the following:

<Image
    src="image-src"
    alt="image-alt-text"
    sizes="320 640 750"
    layout="responsive"
/>

Keep in mind that it is recommended to define sizes only when using a responsive or fill layout.

quality prop

The quality prop provides an integer between 1 and 100 that defines the quality of the optimized image; 1 being the worst quality and 100 being the best. It defaults to 75:

<Image
    src="image-src"
    alt="image-alt-text"
    quality={100}
    layout="fill"
/>

priority prop

By default, images are not prioritized (because they are lazy-loaded), meaning priority defaults to false . When true, the image is considered high-priority and preloaded.

This should only be used when the image is visible above the fold:

<Image
    src="image-src"
    alt="image-alt-text"
    width={500}
    height={300}
    priority
/>

layout prop

The layout prop controls the layout behavior of the image as the size of the viewport changes. The accepted value is a string of either intrinsic , fixed , responsive , or fill.

Each of the layout values has their nuances in:

  1. The generated srcSet and sizes
  2. Expected display or position value on the parent element for the corresponding <Image/> element on which they’re used
  3. Sometimes must pair with other props for proper behavior

Let’s take a look at the possible layout values

intrinsic layout

The intrinsic layout is the default of the four layout values. Using this layout for smaller viewports scales down image dimensions while using it for larger viewports maintains the original image dimensions. You can see an example of intrinsic layout here.

fixed layout

The fixed layout is similar in behavior to the native img element. With it, image dimensions remain fixed at their original dimensions regardless of viewport changes, meaning there is no response. You can see an example of a fixed layout here.

responsive layout

The responsive layout borrows from and overrides both the fixed and intrinsic layouts.

To override the fixed layout, it responds to viewport changes, but to override the intrinsic layout for larger viewports, where the image dimensions scale up, the parent element must be display: block. You can see an example of a responsive layout here.

fill layout

The fill layout responds to its parent dimensions. With it, the image dimensions are stretched to the parent’s dimensions, provided the parent is position: relative.

This is usually paired with the objectFit property. You can see an example of a responsive layout here.

placeholder prop

This placeholder property is used as a fallback image when an image is loading. Its possible values are blur or empty.

placeholder defaults to empty, and when it is empty, there is no placeholder while the image is loading, only empty space.

However, when it’s blur, the blurDataURL property is used as the placeholder. If the image src is a static import with the MIME-type jpg, png, or webp, the blurDataURL automatically populates.

For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation.

objectFit prop

The objectFit prop is similar to the object-fit CSS property that sets how an image should resize to fit its container. It is used with layout=fill or an image with a set width and height and has a possible value of contain, cover, fill, none, and scale-down:

<Image 
    src="image-src"
    alt="image-alt-text"
    layout="fill"
    objectFit="contain"
/>

objectPosition prop

This is similar to the object-position CSS property that specifies the position of an image in its container. It is used with layout=fill or an image with a set width and height and a position value:

<Image 
    src="image-src"
    alt="image-alt-text"
    layout="fill"
    objectPosition="top right"
/>

loading prop

The loading prop is similar to the HTML img element’s loading attribute used for lazy loading. The possible value is a string of lazy or eager:

<Image 
    src="image-src"
    alt="image-alt-text"
    layout="fill"
    loading="lazy"
/>

It is recommended that images above the fold be loaded with priority instead of loading=“eager” because this hurts performance.

onLoadingComplete prop

The onLoadingComplete prop is a callback function that executes immediately after the image completely loads and the placeholder is removed:

<Image
    src="image-src"
    alt="image-alt"
    layout="fill"
    // returns:
    // {naturalWidth: <imageNaturalWidth>, naturalHeight: <imageNaturalHeight>}
   onLoadingComplete={(imageDimension) => console.log(imageDimension)}
/>

blurDataURL prop

The blurDataURL prop is a placeholder image that loads before the src image successfully loads and must be a base64-encoded data URL image that is effectual only when used in combination with placeholder=“blur”:

import Image from 'next/image';
import cardImage from '../public/defaultCardImage.webp';

export default function CardImage() {
    return (
        <div classname="cardImageWrapper">
            <Image
                src={cardImage}
                alt="image-alt-text"
                placeholder="blur"
                // width, height, and blurDataURL are automatically provided
            />
        </div>
    )
}

This data is automatically provided for statically imported images. For dynamic or remote images, you must provide width, height, and blurDataURL manually.

You can see this example of the default blurDataURL prop as well as the shimmer effect with blurDataURL prop.

lazyBoundary prop

The lazyBoundary prop is similar to rootMargin in the Intersection Observer API, which acts as the marginal threshold for detecting intersections before triggering lazy loading. It defaults to 200px.

unoptimized prop

When the unoptimized prop is true, the src image will be served as-is instead of changing quality, size, or format:

<Image
    src="image-src"
    alt="image-alt-text"
    width={700}
    height={450}
    unoptimized
/>

This prop defaults to false.

Caveat with other properties

Apart from the properties listed above, other properties passed to the <Image/> component will relay to the underlying img element with the exception of the following:

  1. decoding is always "async"
  2. For ref, use onLoadingComplete instead
  3. For srcSet, use deviceSizes instead
  4. For style, use className instead

Styling the source image prop

To maintain the aspect ratio of an image and prevent the Core Web Vital and Cumulative Layout Shift, next/image wraps the img element with other div elements.

To style the source image, name it with the className prop then target it in your CSS:

<Image
    src="image-src"
    alt="image-alt-text"
    width={700}
    height={450}
    // You can style this image component with the `cardImage` class name
    className="cardImage"
/>

Configuration for next/image in next.config.js

next/image can be configured via next.config.js. Let’s take a look at some of the configurations.

domains

By default, Next.js only optimizes images hosted on the same domain as your Next.js app. If your images are hosted externally on your CMS, for example, you must specify which domains are allowed to be optimized. This level of specificity is needed to prevent the abuse of external URLs:

module.exports = {
    images: {
        // assuming you were using the Sanity.io image CDN
        // domains is an array of comma-separated strings
        // ['cdn.sanity.io', 'cdn.not-sanity.io', 'another domain']
        domains: ['cdn.sanity.io'],
    }
}

Keep in mind that when loader is set to an external image service, the domains config is ignored.

loader

By default, Next.js handles image optimization but you can hand that responsibility over to a cloud provider like Cloudinary or imgix that is more dedicated to images than just general optimization.

To do this set the loader and path to allow the use of relative URLs:

module.exports = {
    images: {
        loader: 'cloudinary',
        path: 'https://your-site.com/assets/images/'
    }
}

Caching

Caching is a rather intricate and, some say, one of the two hardest things in computer science.
In this context, caching speeds up image delivery to users because it avoids refetching over the network.

The Next.js docs have a sufficient explanation forcaching on the default loader. However, if you use a different loader, like Cloudinary, you must refer to that documentation to see how to enable caching.

Configuration for advanced use cases

While not always necessary, there are advanced use cases available for optimizing images. However, be aware that if configuring any properties in this section, it will override any change to the Next.js defaults in future updates.

Nevertheless, let’s see what they are.

deviceSizes and imageSizes

Both device and image sizes are similar with subtle differences. They are both used on the merit that the device widths from the users of your website are known in advance. And they represent a list of device width breakpoints:

module.exports = {
    images: {
        // the sizes define below are the defaults
        deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
        imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    }
}

deviceSizes is effectual when the <Image/> uses a responsive or fill layout and imageSizes is effectual when the <Image/> uses a fixed or intrinsic layout.

minimumCacheTTL

You can also configure how long or time-to-live (TTL) in seconds for cached optimized images with minimumCacheTTL:

module.exports = {
    image: {
        // This is 60 seconds
        minimumCacheTTL: 60,
    }
}

The docs advise that it is better to use a Static Image Import, which automatically handles hashing file contents and caching the file forever. But, keep in mind that you can disable static imports, so choose your battles wisely.

disableStaticImports

If you have your reasons (I haven’t personally come across one), it is possible to disable static imports:

module.exports = {
    images: {
        disableStaticImages: true
    }
}

Image formats like WebP and Core Web Vitals

To ensure your users get the most optimal image on their devices, images are served with formats like WebP in supported browsers, allowing developers to serve lighter images to users to increase speed while preserving as much fidelity and quality as possible.

Different image formats have their use-cases. WebP is well supported and is a great choice for animated images, offering better compression than PNG or JPEG.

Since Next.js optimizes images in this format, the visual stability of a Next.js site isn’t bogged down by the effects of Cumulative Layout Shift and you can score high on Core Web Vitals.

Conclusion

Image optimization in Next.js improves the user and developer experience with a native game-changing and powerful API that’s easy to work with and extend. This inadvertently solves a major Core Web Vitals need and helps websites achieve a higher SEO rank, all starting and ending with next/image.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Leave a Reply