Blurred image placeholder with Next.js image and Cloudinary

Most of the time, we don't want to manage image optimizations ourselves. Libraries such as Next.js/image are handy to deal with responsive images. In addition to Next.js image, I often use Cloudinary, which is a media management service.
Two key benefits:

  • Use a CDN dedicated to our images
  • Easy to apply transformations to an image: a URL-based image API

Next.js provides excellent integration with Cloudinary. For example, we can use the following next.config.js file.

const cloudinaryBaseUrl = `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/`;
module.exports = {  
  images: {
    loader: "cloudinary",
    path: cloudinaryBaseUrl,
  },
};

where the CLOUDINARY_CLOUD_NAME env variable contains our cloudinary cloud name.

We're ready to build a basic app to display an image using next/image and Cloudinary. Here's what a React code would be like:

function Home({ exampleImage }) {
    return (
        <div className={styles.container}>            
            <main className={styles.main}>
                <h1 className={styles.title}>Blurred image placeholder</h1>
                <h2 className={styles.subtitle}>with Next.js image and cloudinary</h2>
                <div className={styles.imagewrapper}>
                    <Image
                        src={exampleImage.src}
                        alt="Example"
                        width="1920"
                        height="1280"
                        layout="responsive"
                        quality="75"
                        sizes="60vw"                        
                    />
                </div>
            </main>
        </div>
    );
}

It would be nice to display a blurry image while the browser loads the real one.
Blurred image placeholder
But, unfortunately, next/image does not generate the blurred placeholder automatically when we use the Cloudinary loader.

Let's try to add a blurred image placeholder. Next/image provides two properties: placeholder and blurDataURL. We will rely on Cloudinary to get a low-quality, blurred picture. It leads to the following function to generate a base64 encoded data URL:

export async function getBase64ImageUrl(imageId: string): Promise<string | undefined> {
    const response = await fetch(`${process.env.CLOUDINARY_BASE_URL}w_100/e_blur:1000,q_auto,f_webp${imageId}`);
    const buffer = await response.arrayBuffer();
    const data = Buffer.from(buffer).toString('base64');
    return `data:image/webp;base64,${data}`;
}

Finally, we have to generate the data URL at compile-time. With Next.js, it's pretty straightforward by implementing the getStaticProps function:

export async function getStaticProps() {
    const imageSrc = process.env.CLOUDINARY_EXAMPLE_IMAGE_SRC;
    if (!imageSrc) {
        throw new Error('Missing CLOUDINARY_EXAMPLE_IMAGE_SRC env variable');
    }

    const blurDataUrl = await getBase64ImageUrl(imageSrc);
    return {
        props: {
            exampleImage: {
                src: imageSrc,
                blurDataUrl: blurDataUrl,
            },
        },
    };
}

where the CLOUDINARY_EXAMPLE_IMAGE_SRC env variable contains our cloudinary image id.

So here's the final version of the React code:

function Home({ exampleImage }: InferGetStaticPropsType<typeof getStaticProps>) {
    return (
        <div className={styles.container}>            
            <main className={styles.main}>
                <h1 className={styles.title}>Blurred image placeholder</h1>
                <h2 className={styles.subtitle}>with Next.js image and cloudinary</h2>
                <div className={styles.imagewrapper}>
                    <Image
                        src={exampleImage.src}
                        alt="Example"
                        width="1920"
                        height="1280"
                        layout="responsive"
                        quality="75"
                        sizes="60vw"
                        placeholder="blur"
                        blurDataURL={exampleImage.blurDataUrl}
                    />
                </div>
            </main>
        </div>
    );
}

You can find the source code on Github.
This has been helpful for me in my projects. Hopefully it helps you as well.

47