Best Way to Load Images in NextJs
project photo
author photo
Iftekhar AhmedJan 4, 2024

When I started using NextJs for the first time, I had such a hard time with the Next Image component. However, over time, I came to appreciate the sheer power and convenience it brings to the table. The Next Image component enforces a specific pattern that addresses a significant issue: "the layout shift".
It also provides Size Optimization, Responsive Images, Automatic WebP support, Lazy Loading, automatic caching, etc. out of the box. But the most underrated feature of it is 'placeholders', and I think this is the best way to load images for most of the cases.

In this blog, we are going to explore what are some techniques we can use on image placeholders to make our site more snappy and different use cases of placeholders.

Why Use Image Placeholders?

Image placeholder solves the website layout shift problem. Here is an example of how images can shift the layout of the website

project photo

Here you can see that the image shifted the layout of the page. However, if we use placeholders it can hold the spot for the images, keeping the layout steady while the actual image loads.

How to use placeholders inside Next <Image />

Static Images

For static images, it is pretty state-forward,

1import Image from "next/image";
2import img from "../../../public/image.jpg";
3
4export default function Static() {
5  return (
6    <>
7      <Image
8        src={img}
9        alt="with-blur"
10        width={300}
11        height={300}
12        placeholder="blur"
13      />
14    </>
15  );
16}
17

As you can see just by adding placeholder="blur" we can achieve the blur effect. Here NextJs / Vercel does all the heavy lifting for turning the static image into a blurred image and uses this blurred image as a placeholder. Here placeholder="blur" in action.

project photo

This can be used in both server-side rendered and client-side rendered components.

But there is a catch. For Next/Image to do its magic, we need to deploy our app in Vercel. Because under the hood it is using Vercel's edge function to turn the image into its base64 blur form.

Dynamic Images

As you can probably tell using placeholders for dynamic images can be tricky because we don't have an existing image to convert to its blur form. So alongside with placeholder, we need to provide a blurDataURL, which should be a base64 form of the image.

To address this we can generate the blur base64 image of the fetched image using a library called plaiceholder. Plaiceholder is a server-side function for creating low-quality image placeholders.
To use plaiceholder we need to install sharp and plaiceholder. Some frameworks include sharp by default so check before installing the library. Here is the straightforward code to turn the actual image into its base64 version

1import axios from "axios";
2import { getPlaiceholder } from "plaiceholder";
3
4async function generateBlurImg(image_url: string) {
5  try {
6    const fetchImgAsBuffer = await axios.get(image_url, {
7      responseType: "arraybuffer",
8    });
9
10    const { base64 } = await getPlaiceholder(fetchImgAsBuffer.data);
11
12    return base64;
13  } catch (e) {
14    if (e instanceof Error) console.log(e.stack);
15  }
16}
17
18export default generateBlurImg;

First, we need to fetch the image as an arrayBuffer . Then we pass the data to the getPlaiceholder function from the plaiceholder library and we get the base64 version of the image.

1import Image from "next/image";
2import generateBlurImg from "../utils/generateBlurImg";
3
4export default async function Dynamic() {
5  const img_url = "https://i.ibb.co/WKR52Db/image.jpg";
6  const blurImg = await generateBlurImg(img_url);
7  return (
8    <>
9      <Image
10        src={img_url}
11        alt="with-blur"
12        width={300}
13        height={300}
14        placeholder="blur"
15        blurDataURL={blurImg}
16      />
17    </>
18  );
19}
20

Then we pass the blurred image to blurDataURL , here passing the base64 version is necessary.

This code works because we are using the server component of NextJs. If the component was client-side rendered then it would not work. Because Plaiceholder uses `sharp` under the hood which is a server-side library.

Making your own custom Blur Image

We can turn any image to its base64 form and store it locally and use it as our blur image data. For example, take any image and turn it into a base64 version then use it in blurDataURL This will also work.

1import Image from "next/image";
2import blurImgJSON from "../customBase64Img.json";
3
4export default async function Dynamic() {
5  return (
6    <>
7      <Image
8        src={img_url}
9        alt="with-blur"
10        width={300}
11        height={300}
12        placeholder="blur"
13        blurDataURL={blurImgJSON.blurImgBase64}
14      />
15    </>
16  );
17}
18

Here I am storing my blur image's base64 form in a JSON and loading it from there. Making a generic blur base64 which can be used throughout the application can be useful if you don't want to generate any blur images.

Lazy 💤

And If you just don't care use a solid background as a placeholder. You can use any gradient also.

1import Image from "next/image";
2import generateBlurImg from "../utils/generateBlurImg";
3
4export default async function Dynamic() {
5  const img_url = "https://i.ibb.co/WKR52Db/image.jpg";
6  const blurImg = await generateBlurImg(img_url);
7  return (
8    <>
9      <Image
10        className="bg-slate-200"
11        src={img_url}
12        alt="with-blur"
13        width={300}
14        height={300}
15      />
16    </>
17  );
18}
19

Source Code