How to: Blurred images on load in Next.js
Next.js' <Image>
component is a really impressive piece of engineering. It offers automatic image optimisation, all the SEO features you'd want from html image tags and some more arcane properties like blurDataURL
, which is supposed to help Next.js display a low-res blurred image before loading its full-res version.
If that sounds too good to be true, well that's probably because it is. The Next.js documentation is very vague about the blurDataURL
property.
A Data URI to be used as a placeholder image before the
src
image successfully loads. Only takes effect when combined withplaceholder="blur"
.Must be a base64-encoded image. It will be enlarged and blurred, so a very small image (10px or less) is recommended. Including larger images as placeholders may harm your application performance
...Thanks?
I don't know about you but the above means absolutely nothing to me in terms of actually using it. It took me a while in fact to figure out how to implement it, so here's everything I've learned about how to blur images in Next.js.
What is the blurDataURL property for the Image component?
The Next.js docs give us a little information, despite being a bit confusing:
- Must be a base64-encoded image
- a very small image (10px or less)
This means we need to generate a base64-encoded version of our image that is 10px or less. Pretty easy right? No, not really, but luckily there's a package for that!
Generating a base64 encoded image for blurDataURL
Plaiceholder
is going to be our package of choice for this. It's a nifty utility that can be used with Next.js or Node to generate the information we need. On top of blurDataURL
we'll also need to know the image's size, which we can get with image-size
npm i sharp plaiceholder image-size
# or
yarn add sharp plaiceholder image-size
For this article we'll use a lightly altered (and non-typescript) way of getting the information we need originally written by Nikolov Lazar in his blog Generating blurDataURL for remote images in Next.js.
The code we use here is mostly the same, but I'd like to show off a couple of different ways to use it in case you're not working with MDX.
/**
* utils/imageMetadata.js
* Code written by Nikolov Lazar
* https://nikolovlazar.com/blog/generating-blur-for-dynamic-images-nextjs
*/
import imageSize from 'image-size'
import path from 'path'
import { getPlaiceholder } from 'plaiceholder'
import visit from 'unist-util-visit'
import { promisify } from 'util'
// Convert the imageSize method from callback-based to a Promise-based
// promisify is a built-in nodejs utility function btw
const sizeOf = promisify(imageSize)
// Just to check if the node is an image node
function isImageNode(node) {
const img = node
return (
img.type === 'element' &&
img.tagName === 'img' &&
img.properties &&
typeof img.properties.src === 'string'
)
}
// Returns the props of given `src` to use for blurred images
export async function returnProps(src) {
// Calculate image resolution (width, height)
const res = await sizeOf(path.join(process.cwd(), 'public', src))
// Calculate base64 for the blur
const { base64: blurDataURL, img } = await getPlaiceholder(src)
// If an error happened calculating the resolution, throw an error
if (!res) throw Error(`Invalid image with src "${node.properties.src}"`)
const { width, height } = res
return {
...img,
width,
height,
blurDataURL,
}
}
async function addProps(node) {
// return the new props we'll need for our image
const { width, height, blurDataURL } = await returnProps(
node.properties.src
)
// add the props in the properties object of the node
// the properties object later gets transformed as props
node.properties.width = width
node.properties.height = height
node.properties.blurDataURL = blurDataURL
node.properties.placeholder = 'blur'
}
const imageMetadata = () => {
return async function transformer(tree) {
// Create an array to hold all of the images from the markdown file
const images = []
visit(tree, 'element', (node) => {
// Visit every node in the tree, check if it's an image and push it in the images array
if (isImageNode(node)) {
images.push(node)
}
})
for (const image of images) {
// Loop through all of the images and add their props
await addProps(image)
}
return tree
}
}
export default imageMetadata
For this I've specifically separated some of the logic into it's own function returnProps
, which we'll use when working with more standard APIs.
How to blur images in Next.js from a Node.js API
This is probably the easiest way to return the information you need to blur images. Whenever an image needs to be returned by an endpoint, you'll also have to make sure to use plaiceholder
to return its data. It should look something like this:
import { returnProps } from 'utils/imageMetaData'
/**
* Random example using Next.js API functions, use whatever Node backend you
* prefer
*/
export default function handler(req, res) {
// Get your data from a database or any other source
const data = getPostsFromDatabase();
// Pass the image to plaiceholder
const imageProps = await returnProps(data.thumbnailSrc);
// Or maybe you have a gallery
data.gallery = await Promise.all(
data.gallery.map(async item => {
const imageProps = await returnProps(item.imageSrc);
// This will return the image a well as the needed plaiceholder
// info in the same object within the array 🤯
return { ...item, imageProps };
})
);
res.status(200).json({
...data,
// This data will then be used by <Image> in our frontEnd
thumbnail: imageProps
})
}
How to blur images in a statically exported Next.js site
Using plaiceholder
in a statically generated Next.js site is also pretty easy.
export const getStaticProps = async context => {
// Get your data from a database or any other source
const data = getPostsFromDatabase();
// Pass the image to plaiceholder
const imageProps = await returnProps(data.thumbnailSrc);
// Or maybe you have a gallery
data.gallery = await Promise.all(
data.gallery.map(async item => {
const imageProps = await returnProps(item.imageSrc);
// This will return the image a well as the needed plaiceholder
// info in the same object within the array 🤯
return { ...item, imageProps };
})
);
return {
props: {
data,
// This data will then be used by <Image> in our frontEnd
thumbnail: imageProps
},
};
}
How to blur images in Next.js from a markdown file
Blurring images within Markdown or MDX is thankfully a lot easier thanks to the rest of Nikolov's functions. We just need to pass the plugin to our serialize function.
import imageMetaData from 'utils/imageMetadata'
import { serialize } from 'next-mdx-remote/serialize'
const components = {
img: (props: any): React.ReactNode => (
<Image {...props} layout="responsive" loading="lazy" />
),
}
function Post({ mdx }) {
return (
<>
<MDXRemote
{...mdx}
components={components}
/>
</>
}
export const getStaticProps = async context => {
// Get your data from a database or any other source
const markdown = getPostsFromDatabase();
// convert your markdown to html with unified, rehype or remark
const mdx = await serialize(markdown, {
mdxOptions: {
rehypePlugins: [imageMetaData],
},
})
return {
props: {
mdx,
},
};
}
That's all! Try to load your page. Your images should have a beautiful blur before they lazy-load into view.