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
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.
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