Introduction
Images can have a significant impact on website loading times, which can in turn affect user experience and website performance.
Let's look at the importance of image optimization and how Next.js provides us this capability out-of-the-box.
- Let's say you need to display an image on your web page. How would you traditionally do this?
By adding an image to your UI using the HTML
<img>
element. Something like this:
The img here is being loaded from an external server and let's examine the file size and download time.
Now, you may think 675ms is fairly quick, but if you had multiple images on your web page you would see it considerably slowing down your page load.
Next.js Image Component to the rescue
The Next.js Image component includes a variety of built-in performance optimizations and here are some of them:
Improved Performance - The Image component can automatically resize images to the correct size for the container they are displayed in. This helps to reduce the file size of images and improve loading times.
Lazy loading - The Image component uses lazy loading to defer the loading of images until they are needed. Images are only loaded when they enter the viewport.
Responsive images - The Image component supports responsive images, which allows different images to be displayed depending on the user's screen size. This helps to ensure that images are displayed at the correct size on different devices, while also reducing the file size of images.
Visual Stability - Prevent Cumulative Layout Shift (CLS) is a metric that measures the visual stability of a web page during the loading process. It measures how much the page layout changes as elements load and shift around on the screen.
You can read more about CLS here - https://nextjs.org/learn/seo/web-performance/cls
Let's see this in action
Next.js Image component can be used in different ways and let's compare it's performance with that of a generic html img in all of these cases.
- Scenario 1: Loading a Static Image using Next.js Image component
import Head from 'next/head'
import Image from 'next/image'
import SampleImg from '../../public/sampleImg.jpg'
export default function Home() {
return (
<>
<Head>
<title>Image Optimization Demo</title>
</Head>
<main>
<div>
<img src='https://m.media-amazon.com/images/I/71ZOjO-tDRL._SX3000_.jpg' alt='sample' width={960} height={400} />
</div>
<div>
<Image src={SampleImg} alt='Sample' height={400} width={960}></Image>
</div>
</main>
</>
)
}
In this Next.js application, I've loaded 2 images - one using the HTML img element and the other using Next.js Image component by using a local image.
Now, let's compare the loading times and file sizes of both the image files:
As you can see here, the Next.js Image loads much faster
and also it uses the webp format for the image which offers better compression and smaller file sizes than JPEG, without sacrificing image quality.
- Scenario 2: Loading a remote Image
import Head from 'next/head'
import Image from 'next/image'
import SampleImg from '../../public/sampleImg.jpg'
export default function Home() {
return (
<>
<Head>
<title>Image Optimization Demo</title>
</Head>
<main>
<div>
<img src='https://m.media-amazon.com/images/I/71ZOjO-tDRL._SX3000_.jpg' alt='sample' width={960} height={400} />
</div>
<div>
<Image src='https://m.media-amazon.com/images/I/71ZOjO-tDRL._SX3000_.jpg' alt='Sample' width={960} height={400}></Image>
</div>
</main>
</>
)
}
To use a remote image, the src property should be a URL string, which can be relative or absolute. Because Next.js does not have access to remote files during the build process, you'll need to provide the width and height.
However, if you do this, you will most likely get the following error:
The error indicates that in order to protect your application from malicious users, configuration is required in order to use external images. This ensures that only external images from your account can be served from the Next.js Image Optimization API. These external images can be configured with the remotePatterns property in your next.config.js file, as shown below:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'm.media-amazon.com',
port: '',
pathname: '/images/**',
},
],
},
}
module.exports = nextConfig
Adding this configuration should load the image successfully.
Here, again if we compare the results, you can see that the Next image loads much faster.
- Scenario 3: The size of the image is unknown in case of a remote Image
One of the ways that images most commonly hurt performance is through layout shift The way to avoid image-based layout shifts is to always size your images. This allows the browser to reserve precisely enough space for the image before it loads.
But, What if I don't know the size of my images?
- If you are accessing images from a source without knowledge of the images' sizes, you can use fill prop which allows your image to be sized by its parent element.
import Head from 'next/head'
import Image from 'next/image'
import SampleImg from '../../public/sampleImg.jpg'
export default function Home() {
return (
<>
<Head>
<title>Image Optimization Demo</title>
</Head>
<main>
<div>
<img src='https://m.media-amazon.com/images/I/71ZOjO-tDRL._SX3000_.jpg' alt='sample' width={960} height={400}></img>
</div>
<div style={{ position: 'relative', width: '960px', height: '400px' }}>
<Image src='https://m.media-amazon.com/images/I/71ZOjO-tDRL._SX3000_.jpg' alt='Sample' fill></Image>
</div>
</main>
</>
)
}
Here, when loading the Next Image, we have not specified the size, instead used the fill prop which will size the image based on it's parent div's dimensions.
- Lastly, consider a case where a image needs to be on the immediate viewport when a web page is loaded.
- In such case we can use the priority property on the image. Doing so, allows Next.js to specially prioritize the image for loading. When an image is marked with a priority, the browser will attempt to download and render that image before other images on the page. This way we can prevent any kind of flicker that may occur when loading a web page with an Image in the immediate viewport.
Responsive Image loading
- As we already discussed, the Next image component supports responsive images, which allows different images to be displayed depending on the user's screen size. Let's examine this in action.
If you resize the screen to different sizes, it can be seen that for each of the Next image requests, query parameters of the width and height are passed in depending on the screen size.
Here's an image request for screen size of a tablet.
This is another request for screen size of a mobile.
So to conclude, Next.js Image component provides us with various Image Optimization techniques which we can leverage to improve the page load and overall web page performance which otherwise would require us to manually implement these optimization techniques thereby saving us developers a ton of time! All we need to do is remember to use the Image component in a Next.js application.
However, I'd also like to point out that if you are pulling in the images dynamically from external sites that you do not trust, be cautious when using Next Image component as you need to white-list the domains.
Happy Coding! :)
Top comments (0)