Blur Placeholder Images with Next.js and mdx-bundler
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.
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.