Image Optimization with NextJS

Image Optimization with NextJS

How does NextJS Image work? How do I optimize my Nextjs build? Lazy Loading Images? - Right article to find out :)

NextJS is fast becoming my favorite frontend framework because of the endless advantages over a basic React application, one of those said benefits would be the Built-in Image Component.

In this article, we would take a look at the Image component from NextJS and learn how to use it to optimize an image in our web app.

By the end of this article, you should understand the following concepts:

  • Image Optimization

  • Using next/image

  • Image Props

  • Configuring next.config.js

  • Usage of the native < img > tag in NextJS

Image Optimization

Ordinarily, if you were going to make use of an image in your website/app you would do this ( assuming the image is in the same directory as the webpage it is accessing ):

<img src="./apple.jpg">

You can go further by adding an alternative text (for screen readers or when the image cannot be loaded) by doing this:

<img src="./apple.jpg" alt="Image of an apple"/>

However, this format doesn't solve image optimization aspects like image size, web formats, and responsiveness with this single usage.

NextJS offers automatic image optimization which solves all of the above as well as common tasks like internalization and routing.

The golden rule for any performance optimization simply put is giving users what they want in the shortest possible time or providing a fall-back if need be.

Hence NextJS provides us with a built-in image optimization API, next/image, a canonical form for native automatic image optimization.

Using next/image

The Image component in NextJS is quite similar to the native html <img>, it's an extension of this element and can be used by importing it from next/image and using it like you'd use a component with props.

import Image from 'next/image';

export default function SampleImage({source}) {
    return (
        <div>
            <Image src={source} alt='Image alt text'/>
        </div>
    )
}

The Image tag has a couple of props available to it for use asides the src and alt prop, we'd take a look at some of them

  • width and height prop

The width and height of the image is in pixels , when adding the width and height be sure to add the correct dimension. If a different aspect ratio is added, the image would adjust accordingly. For example if the width and height of a (1400 x 700) image gets changed to (400 x400) as shown below, it could result in a skewed image.

import Image from 'next/image';

export default function SampleImage({source}) {
    return (
        <div>
            <Image 
               src={source} 
               alt='Image alt text'
               height={400}
               width={400}
             />
        </div>
    )
}
  • layout prop

There may be times you do not know the width and height of an image, but still want it to fill the entire space while keeping its aspect ratio. In this situation, you can leave out the width and height prop on the Image component. Instead, add a prop of layout="fill". This will stretch the image to the width and the height of the parent element. When using the layout="fill" prop, it is often best to pair it with objectFit="cover". This will allow the image to maintain its aspect ratio while filling the element’s entire content box. To achieve this, wrap the Image component as a child of a <div> element. Then add a width and height to the parent <div> element, along with giving it a position="relative".

import Image from 'next/image';

export default function SampleImage({source}) {
    const myStyle = {
       height: '400px',
       width: '400px',
       position: 'relative' 
   }
    return (
        <div style={myStyle}>
            <Image 
               src={source} 
               alt='Image alt text'
               layout='fill'
               objectFit='cover'
             />
        </div>
    )
}

This way, we can see that the image is taking up the 400-pixel square that we wanted, but the aspect ratio of the image is still in place. The parts of the image that do not fit within the parent element are clipped.

Other layout values are intrinsic, fixed, and responsive.

  • loader prop

A loader is a function returning a URL string for the image, given the following parameters (src, width, quality). Setting the loader as a prop on the Image component overrides the default loader defined in the images section of next.config.js.

import Image from 'next/image'

const sampleLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

const MyImage = (props) => {
  return (
    <Image
      loader={sampleLoader}
      src="me.png"
      alt="My Picture"
      width={500}
      height={500}
    />
  )
}
  • sizes prop

You can specify a list of image widths using the images.imageSizes property in your next.config.js file. These widths are concatenated with the array of device sizes to form the full array of sizes used to generate image srcsets.

module.exports = {
  images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

Or by defining it in your component like,

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

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

  • quality prop

The quality of the optimized image, is an integer between 1 and 100 where 100 is the best quality. Defaults to 75.

<Image
    src={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. You should use the priority property on any image detected as the Largest Contentful Paint (LCP) element. Should only be used when the image is visible above the fold. Defaults to false.

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

This placeholder property is used as a fallback image when an image is loading. Its possible values are blur or empty. When empty, there will be no placeholder while the image is loading, only empty space. When blur, the blurDataURL property will be used as the placeholder. If src is an object from a static import and the imported image is .jpg, .png, .webp, or .avif, then blurDataURL will be automatically populated.

<Image
    src={src}
    alt="image-alt-text"
    width={500}
    height={300}
    placeholder="blur"
/>
  • 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”.

<Image
  src={src}
  alt="image-alt-text"
  width={600}
  height={450}
  placeholder="blur"
  blurDataURL=”data:image/png;base64,[IMAGE_CODE_FROM_PNG_PIXEL]”
/>
  • objectFit prop

The objectFit prop defines how the image will fit into the container of it's parent, quite similar to the object-fit CSS property. It is used with layout=fill or an image with a set width and height.

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

It has a possible value of: contain, cover, fill, none, and scale-down.

  • unoptimized prop

When true, the source image will be served as-is instead of changing quality, size, or format. Defaults to false.

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

Configuring next.config.js

You can configure your NextJS image through the next.config.js file

  • domains

When using an external URL to load images, you must add it to domains in next.config.js

module.exports = {
    images: {
        domains: ['example.com'],
    }
}
  • loader

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

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

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

For more advanced cases of props in NextJS, there are other props that you can add to the Image component as well as configurations. Check out the full documentation here.

Conclusion

Image optimization in Next.js improves the user and developer experience but just like every other thing in programming, the Image component has some limitations one of which is its inability to adjust CSS directly. Unlike the native <img> element whereby you can pass a style prop to override its CSS. The NextJS image component doesn't support the style property at all. Hence to style the source image, name it with a className then target it with your CSS.

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

P.S: Next.js forces using their

component instead of the native <img> tag by including the corresponding linter check to the app build process. So if you're going to make use of the <img> tag in a NextJS application you'd add the following to disable the check

// eslint-disable-next-line @next/next/no-img-element
 <img
     src={src}
     alt='myImg'
     className='myImage'
 />

Or by adding "@next/next/no-img-element": "off" in the .eslintrcconfig file.

I hope you enjoyed reading this as much as I enjoyed writing it and would be building your NextJS app as soon as possible.

Resources used:

👉🏾 Learn more about me

👉🏾 Connect on LinkedIn

👉🏾 Subscribe to my blog, let's feast