skies.dev

Blur Placeholder Images with Next.js and mdx-bundler

3 min read

Let's look at adding blur to your images using Next.js and mdx-bundler.

To do this, we need to create a custom rehype plugin to get necessary props for next/image. We need:

  • width
  • height
  • placeholder='blur'
  • blurDataURL

First, Lazar Nikolov shows us how to generate blurDataURL for remote images in Next.js with his rehypeImage function. His function can easily be adapted for mdx-bundler.

Note: rehypeImage will require some dev dependencies.

yarn add -D plaiceholder util unist-util-visit image-size

When you prepare your MDX, add the custom rehype plugin.

import {bundleMDX} from 'mdx-bundler';
import rehypeImage from '@lib/imageMetadata';

export async function prepareMDX(source: string) {
  return bundleMDX({
    source,
    cwd: process.cwd(),
    esbuildOptions(options) {
      options.target = 'esnext';
      return options;
    },
    xdmOptions(options) {
      options.remarkPlugins = [...(options.remarkPlugins ?? [])];
      options.rehypePlugins = [...(options.rehypePlugins ?? []), rehypeImage];
      return options;
    },
  });
}

mdx-bundler will gather the src and alt props from the markdown itself.

![Blue Ridge Mountains](/images/mountains.jpg)

The rehypeImage function (found on Lazar's post) will get the placeholder, blurDataURL, width, and height props.

Now that mdx-bundler is getting the props we need, we can spread them onto our custom MDX image component.

import {HTMLProps} from 'react';
import {getMDXComponent} from 'mdx-bundler/client';
import Image, {ImageProps} from 'next/image';

function Img(props: HTMLProps<HTMLImageElement>) {
  return <Image {...(props as ImageProps)} layout={'responsive'} />;
}

export default function BlogPost({code}: {code: string}) {
  const Component = React.useMemo(() => getMDXComponent(code), [code]);
  return (
    <article>
      <Component components={{img: Img}} />
    </article>
  );
}

All together, you'll get the image with the blur effect.

A picture of mountains to show an example of the blur-up effect in action.
A picture of mountains to show an example of the blur-up effect in action.

How to get a smooth blur up effect with Next.js images

One feature I liked with gatsby-plugin-image was how the image would transition from blurred to high resolution.

Currently, next/image goes from 100% blur to 0% with no transition time, which looks a bit sloppy in my opinion.

I found a Codepen that shows how you can use JavaScript and CSS to smoothly transition from fully blurred to high-resolution. We'll adapt this to our use-case.

First, if you look at the source code for next/image, we can see they're using the CSS property filter for the blur effect.

In our CSS, we'll create the class img-blur, which replicates the blur created by next/image.

.img-blur {
  filter: blur(20px);
}

Then, we'll make an animation to transition smoothly to the high quality image.

.unblur {
  animation: unblur 0.3s linear;
}

@keyframes unblur {
  from {
    filter: blur(20px);
  }
  to {
    filter: blur(0);
  }
}

Now, we want to trigger the unblur animation after the image is fully loaded. We'll use the onLoadingComplete prop for this purpose.

import {HTMLProps, useState} from 'react';
import Image, {ImageProps} from 'next/image';

function Img(props: HTMLProps<HTMLImageElement>) {
  const [blur, setBlur] = useState(true);
  return (
    <Image
      {...(props as ImageProps)}
      layout={'responsive'}
      className={blur ? 'img-blur' : 'unblur'}
      onLoadingComplete={() => setBlur(false)}
    />
  );
}

Now, we get the nice blur transition that is sure to delight our users.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like