Next.js Image Optimization: A Guide for Web Developers
In the current digital world, the speed and performance of any website have become one of the most crucial factors determining the user experience. Google has already mentioned that speed is used as a ranking factor for mobile searches.
And for building production-ready, high-performance websites, Next.js stands out as the leading JavaScript framework. However, a fast website is not just about the code; it's also about optimizing assets like images to ensure a top-notch user experience. Unoptimized images have a massive impact on your LCP metrics, slowing websites down.
To solve this pain point, Next.js offers an <Image/>
component of its own that extends the <img>
element, adding several crucial features on top for automatic image optimization.
In this guide, you'll be exploring the next/image component in-depth and learn the benefits of using it. You’ll also understand the best practices to ensure you use the component well.
What is the Next.js Image Component?
The NextJS <Image>
component is similar to the native HTML <img />
tag but with superpowers, greatly improving both the user experience (UX) and the developer experience (DX) while working with images. Today's web applications demand a more flexible and performance-oriented solution—the Next.js image component is focused precisely on this.
The <Image>
component is designed to enhance image performance in web applications, managing tasks like responsive loading, adjusting image sizes based on the viewer's device, and lazy loading out-of-the-box. It also serves images in modern formats such as WebP and handles compression to strike the right balance between image quality and file size.
Before discussing its benefits, let’s briefly understand how it can be integrated into your Next.js application.
How to Use Next.js Image Component
Obviously, the first step is to initialize a Next.js application. However, because this article is explicitly focused on the Image component, the installation process will not be discussed here. If you are new to Next.js, you can refer to the official documentation for the installation process.
To use the Image component in your project, simply import the Image component from the 'next/image' package at the top of your file:
import Image from 'next/image'
When you use an image from the /public folder, Next.js can automatically infer the width and height to prevent Cumulative Layout Shift (CLS), a metric that measures visual stability. But for this, you’ll need to import the image first. Here’s an example:
import MyImage from '../../public/image.jpg';
function Component() {
return <Image src={MyImage} alt='Sample image' placeholder='blur' />;
}
In the above case, you don't have to explicitly specify the width and height because Next.js will automatically determine these dimensions from the image file.
But, if you're using a remote URL for the src, you'll need to explicitly pass the width and height as props. This is essential for Next.js to prevent layout shifts and properly size the image.
Here's a minimal example:
function Component() {
return (
<Image
src='https://ik.imagekit.io/your_imagekit_id/image_NDIXZTQle.jpg'
alt='Sample image'
width={400}
height={400}
/>
);
}
In this example, the width
and height
are explicitly set to 400 and 400 pixels, respectively. The width and height help Next.js to calculate the aspect ratio of the image and reserve space for it so that it doesn’t cause any layout shift to the application.
Now, let’s discuss a few features of the Next.js Image component.
Size Optimization
One of the standout features of the Next.js Image component is its ability to serve optimized images, tailored to each device, automatically. It detects the device's resolution and serves the image in the most appropriate size.
Moreover, it's proficient in delivering images in modern formats like WebP, which is around 25-30% smaller than JPEG, and AVIF if it detects the browser can support it. These formats are known for their superior compression and quality characteristics compared to traditional formats.
The above image shows how the Next.js Image component delivers a WebP image, even when the original image is in JPEG format.
Just how effective is this optimization? The screenshot below, comparing the same image in WebP vs. its JPEG source, speaks for itself.
The size of the optimized image is around 36KB, whereas the unoptimized source image is a whopping 4.6MB. The Next.js Image component is serving the same image compressed to less than 1% of the original size, cutting load times to less than a third, without any visual degradation.
Visual Stability
A common pain point in web design is the layout shift that occurs when images load. This can be disorienting for users and may even lead to accidental clicks on the wrong parts of the page. The CLS, or Cumulative Layout Shift is a metric that tracks this, serving as an indicator of overall page stability. CLS can harm your Core Web Vitals, and the Next.js Image component addresses this issue.
The Next.js Image component avoids layout shifts through the use of placeholders and intrinsic sizing. When you specify the dimensions of an image using the width and height props, the component reserves that space on the page even before the image has been loaded. This ensures that the rest of the page's elements know how much space the image will occupy, thereby preventing layout shifts as the image loads, offering a smoother user experience.
When you specify width and height props, you're telling the Next.js Image component what space to reserve for the image on the page layout.
<Image
src="/path-to-your-image"
alt="Sample image"
width={500}
height={500}
/>
Asset Flexibility
The Next.js Image component significantly enhances the traditional <img /> element's capabilities and offers asset flexibility, offering on-demand image resizing whether your images are stored locally, on remote servers, or on CDNs.
Some additional configuration is required to use images that are not stored locally. Specifically, you'll need to add the base URL of the image to the next.config.js file in your project's root directory. This is an essential step for optimizing images from a remote source. For example, to add AWS S3 as a remote URL, your next.config.js should look like this:
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/your-bucket-name/**',
},
],
},
}
With this configuration, you can use the image URL in the following format:
<Image
src="https://s3.amazonaws.com/your-bucket-name/image.png"
alt="Picture of the author"
width={500}
height={500}
/>
For relative URLs, the Next.js Image component also supports custom loaders, providing even more flexibility in how you manage your assets. Using a custom loader with ImageKit as an image source is discussed later in this article.
Faster Page Loads
Conversion rates drop by nearly 50% if your page load times are 5 seconds or more. The Next.js Image component addresses this issue with out-of-the-box Lazy Loading, ensuring that images are only loaded as they come into the viewport.
This means that images located further down the page (which the user hasn't reached yet) won't consume precious loading time or bandwidth unnecessarily. Out of sight, out of mind. Visitors can access your content faster, reducing the risk of abandonment due to long load times, and ultimately, you increase conversions and engagement.
However, keep in mind that while lazy loading is enabled by default, there might be scenarios where you'd actually want to disable it, such as critical above-the-fold images that need to be displayed immediately. Disabling lazy loading in this instance is as simple as setting the priority prop to true. Here’s the code for it:
import Image from 'next/image'
function Component() {
return (
<Image
src='/image.jpg'
alt='Sample image'
width={400}
height={400}
priority={true}
/>
);
}
Now that you have explored the features and benefits of the Next.js Image component, let’s explore the best practices for Image optimization using it.
Tips and Best Practices for Image Optimization in Next.js
Optimizing images in a Next.js application requires you to consider more than just using the next/image
component. To get the most out of this powerful tool, here are some tips and best practices to consider:
- Use Static Imports for Local Images: When you're using local images, static imports are your best friend. They automatically provide the
width
,height
, andblurDataURL
props, making it easier to manage image dimensions and placeholders. This not only simplifies your code but also enhances performance. - Set Custom Breakpoints for Specific Device Widths: If you know the device widths your audience primarily uses, specify custom breakpoints in your next.config.js to optimize image loading. This ensures that each device receives an image of just the right size, improving performance.
- Specify Width and Height for Remote Images: Always use the
width
andheight
props when dealing with images. This prevents layout shifts and ensures that the images maintain the correct aspect ratio, enhancing the visual stability of your webpage. - Always Use the Alt Prop: The alt prop serves two crucial functions: it improves accessibility by providing a text description of the image for screen readers, and it also offers SEO benefits by helping search engines understand the content of the image. It is a good practice always to provide the
alt
prop. - Use the Priority Prop for Above-the-Fold Images: Use the priority prop for images that appear above the fold and are critical to user experience. This will load these images eagerly, improving the perceived performance of your webpage.
- Utilize the Placeholder Prop: The placeholder prop can be set to either
blur
,empty
, ordata:image/....
Using blur, theblurDataURL
source will be used as a placeholder while the image is loading, andempty
will display an empty space. If the imported images are static imports, and their formats are either JPG, PNG, WebP, or AVIF, the placeholder images will be generated automatically. Both options improve user experience by preventing layout shifts. - Adjust Quality with the Quality Prop: The quality prop allows you to control the compression level of your images. It can be an integer between 1 to 100. By adjusting this value, you can find the right balance between file size and visual quality, optimizing both performance and user experience.
- Enable AVIF Support with the Formats Prop: If you want to take advantage of the latest image formats, use the formats option to enable AVIF support. AVIF offers superior compression and quality compared to older formats and is supported by many modern browsers. Though, it takes 20% longer time to encode. To enable the AVIF support, you can pass the
formats
key to thenext.config.js
file:
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
}
Following these tips and practices can ensure that your Next.js application loads faster and provides a better overall user experience.
But to take this to the next level, integrating the <Image> component with a third-party service like ImageKit will provide the best possible user experience and a website that loads images super-fast. Let’s discuss how this can be done.
How to Optimize Images with Next.js and ImageKit
ImageKit provides real-time image and video optimizations, transformations, and digital asset management solutions for delivering better visual experiences. We have already explored earlier in this article the importance of faster-loading websites, and the lousy effect when the images are not correctly optimized. Even though the Next.js Image component provides features to combat this, combining it with ImageKit can provide exceptional SEO and optimized user experiences.
To integrate ImageKit with Next.js, you’ll need to use the features of the custom loader. In Next.js, a loader function creates image URLs for different sizes, helping in automatic srcset generation for optimal viewport display. Next.js's default loader uses its own Image Optimization API to serve optimized images, but if you’re using a CDN or custom image server usage, you need to write your own loader.
A custom loader allows you to define your own URL generation logic, giving you the flexibility to integrate with any image service. To create a new custom loader, create a new file called imageKitLoader.ts
and paste the following code:
const imageKitLoader = ({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) => {
if (src[0] === '/') src = src.slice(1);
const params = [`w-${width}`];
if (quality) {
params.push(`q-${quality}`);
}
const paramsString = params.join(',');
var urlEndpoint = 'https://ik.imagekit.io/your_imagekit_id';
if (urlEndpoint[urlEndpoint.length - 1] === '/')
urlEndpoint = urlEndpoint.substring(0, urlEndpoint.length - 1);
return `${urlEndpoint}/${src}?tr=${paramsString}`;
};
export default imageKitLoader;
Here, a custom loader function called imageKitLoader
is created to use with the Next.js Image component, tailored explicitly for the ImageKit service. Let’s understand the code briefly:
- The function expects an object with properties
src
(a string representing the image source),width
(a number representing the width of the image), andquality
(an optional number representing the image quality). - The function first checks if the
src
starts with a forward slash/
. If it does, the leading slash is removed. - An array named params is initialized with the width parameter in the format
w-${width}
. - If a quality value is provided, it's added to the params array in the format
q-${quality}
. - The params array is then joined into a comma-separated string.
- The URL endpoint for ImageKit is defined. If it ends with a slash, the slash is removed to ensure the URL is correctly formatted.
- Finally, the function returns the complete URL for the image, appending the
src
and the transformed parameters. - The function
imageKitLoader
is then exported for use in other parts of the application.
You can pass the function to the loader prop to use this loader with any image component. Here’s an example:
'use client'; // if using the App router
import React from 'react';
import Image from 'next/image';
import imageKitLoader from '@/utils/imageKitLoader';
export default function MyImage() {
return (
<Image
loader={imageKitLoader}
src='default-image.jpg'
alt='Default image'
width={400}
height={400}
/>
);
}
The use is pretty straightforward. You pass the loader function to it and the required props like src, alt, width, and height. The Next.js image component will choose the optimal quality for the image.
To know more about the features and options of ImageKit, check their official documentation here.
Conclusion
The aim of the article was to introduce you to the Next.js Image component and how the image component helps you deliver images faster than the traditional img tag. You also explored the tips and best practices to follow when using the Image component. Finally, you explored how the custom image loader functionality can be extended to use with ImageKit.
With features like automatic format conversion, CDN delivery, image transformations on the fly, and more, ImageKit should be on top of your list when you are thinking of delivering images. To get the most from ImageKit, you can sign up for the free plan and upgrade only when satisfied.