The Nextjs Image Component is awesome. It does a lot of magic. To leverage the full magic of the image component it is required to import
the image that needs to be enchanted.
import PictureOfMeOnTheBeach from "public/PictureOfMeOnTheBeach.png"
Now Nextjs will do the following:
- Get the actual
width
andheight
of the image and provide it to theimg
element. - Generate a
blurDataURL
and show it until the actual image is loaded.
As stated above this only works if you import
the image. I know two cases where you aren't able to do this:
- Dynamically loaded: If you don't know the exact images at build time.
- Remote image: If you retrieve the image from a remote location.
I recently experienced the first problem and here is how I dealt with it.
Part One: Dynamically load images
Let's start by loading all images within a folder:
const imagesDirectory = path.join(
process.cwd(),
`public/products/${product.id}`
)
// Wrap the readdir in a try-catch block, otherwise the build will fail because of a unhandled exception.
try {
const productImagePaths = await fs.readdir(imagesDirectory)
return {
props: {
images: productImagePaths.map((path, index) => ({
path: `/products/${product.id}/${path}`,
})),
},
}
} catch (error) {
console.warn(
`Image ${product.name} has no images under /public/product/[id]!`
)
return {
props: {
images: [],
}
}
Part Two: Generate a BlueDataURL
The Nextjs Image Component provides a prop called blurDataURL
to show a very blurry version of the image while the actual image is loading. To generate this URL I use Plaiceholder:
const imagesDirectory = path.join(
process.cwd(),
`public/products/${product.id}`
)
// Wrap the readdir in a try-catch block, otherwise the build will fail because of a unhandled exception.
try {
const productImagePaths = await fs.readdir(imagesDirectory)
/**
* Create blurDataURLs (base64) as image placeholders
*/
const blurDataURLs = await Promise.all(
productImagePaths.map(async src => {
const { base64 } = await getPlaiceholder(`/products/${product.id}/${src}`)
return base64
})
).then(values => values)
return {
props: {
images: productImagePaths.map((path, index) => ({
path: `/products/${product.id}/${path}`,
blurDataURL: blurDataURLs[index],
})),
},
}
} catch (error) {
console.warn(
`Image ${product.name} has no images under /public/product/[id]!`
)
return {
props: {
images: [],
},
}
}
Part Three: Make it look sexy
Now it's time to use the image source and blurDataURL with the Nextjs image component.
// Use whichever styling solution you want. But it is important to add 'transition' to the Image component
const AnimatedImage = styled(Image, {
transition: ".3s",
});
const ProductPage = ({ images }) => {
<ImageContainer>
{images.length ? (
<AnimatedImage
src={images[0].path}
layout="fill"
objectFit="cover"
alt={images[0].path}
placeholder="blur"
blurDataURL={images[0].blurDataURL}
/>
) : (
<Image
src={PlaceholderImage}
layout="fill"
objectFit="cover"
alt="placeholder"
/>
)}
</ImageContainer>
);
};
The important parts are:
placeholder
need to beblur
blurDataURL
needs to be the base64 string generated by plaiceholder
I use layout="fill"
, that's why the actual image size is not that important to me. If you need this data you can use sharp's metadata for this.