How to Use the Next.js Image Component in Storybook

In this post, we will be setting up Next.js and Storybook in a way that makes it possible to use Next.js' Image component in components rendered inside Storybook.

Introduction

If you want to take a look at the final product first, or if you don't care about the step-by-step, here's the accompanying repo.

Rendering a component that uses the Next.js Image component inside Storybook might have you running into a few gotchas. I myself ran into two:

  • Storybook can't seem to find the image you statically import from your public directory, resulting in the following error:
Failed to parse src "static/media/public/<imageName>.jpg" on `next/image`, if using relative image it must start with a leading slash "/" or be an absolute URL (http:// or https://)
  • Storybook can't find the blur placeholder image that is automatically generated by Next.js and injected into the blurDataURL prop, resulting in the following error:
Image with src "static/media/public/<imageName>.jpg" has "placeholder='blur'" property but is missing the "blurDataURL" property.

The reasons for both of these are actually the same: when run inside Storybook, your code won't run through Next.js' build process, during which the correct URLs/Paths and alternative sizes for your hosted images as well as the placeholder images are created and injected into your code.

If you know how, both of these are straightforward to solve. But finding the solutions can be kind of an odyssey. So here they are in one place.

Note: in this post, I assume you have already set up both Next.js as well as Storybook. If not, do that and then come back here.

Storybook Can't Find Images Imported from Next.js' public Directory

Let's assume you have the following component:

// src/components/ImageTest.js

import Image from "next/image"

import testImage from "../public/testImage.jpg"

const ImageTest = () => (
  <Image src={testImage}
    alt="A stack of colorful cans"
    layout="fill" 
  />
)

export default ImageTest

And the following story for it:

// stories/ImageTest.stories.js

import React from 'react';

import ImageTest from '../components/ImageTest';

export default {
  title: 'Image/ImageTest',
  component: ImageTest,
};

const Template = (args) => <ImageTest {...args} />;

export const KitchenSink = Template.bind({});
KitchenSink.args = {};

If you were to run Storybook now, this would be what you saw:

When I first encountered this, I reasoned Storybook simply couldn't find the assets in Next.js' public/ directory. But running with the -s public/ command line option to tell it about the directory doesn't solve the problem.

After some digging, the issue here seems to be the things going on behind the scenes of the Image component. One of its most useful features is that it will automatically optimize the image you pass it and create and serve alternative sizings of it on demand. Next.js can't work its magic when the Image component is rendered inside Storybook though, and that's why the solution here is to simply turn these optimizations off in this context. To do this, we'll have to add the following to Storybook's setup code:

// .storybook/preview.js

import * as NextImage from "next/image";

const OriginalNextImage = NextImage.default;

Object.defineProperty(NextImage, "default", {
  configurable: true,
  value: (props) => (
    <OriginalNextImage
      {...props}
      unoptimized
    />
  ),
});

This code will replace the Image component's default export with our own version, which adds the unoptimized prop to every instance of it. With this, "the source image will be served as-is instead of changing quality, size, or format", according to the Next.js documentation. And since we added this to Storybook's setup code, this will only be done when our component is rendered inside Storybook.

Credit for this solution goes to Github user rajansolanki, who synthesized a few prior solution attempts in this comment on the relevant Github issue.

If you want to read more about Next.js' Image component and the props you can pass into it, take a look at the introduction to its features as well as its documentation.

Storybook Can't Find the Blur Placeholder Image That is Automatically Generated by Next.js and Injected Into the blurDataURL Prop

Another nice feature of the Image component is that it will automatically generate small, blurred placeholder images for display during the loading of the full image.

All we need to do to activate this feature is to pass the placeholder="blur" prop:

// src/components/ImageTest.js

import Image from "next/image"

import testImage from "../public/testImage.jpg"

const ImageTest = () => (
  <Image src={testImage}
    alt="A stack of colorful cans"
    layout="fill"
    placeholder="blur" // this is new!
  />
)

export default ImageTest

But this will immediately result in the following error when we run Storybook again:

The reason for this is basically the same as before. Next.js will generate a placeholder image and inject it into the component for us. So that one line of code we added actually does a whole lot in the background, all of which, again, is not automatically done when run inside Storybook. Luckily the solution is a one liner as well:

// .storybook/preview.js

import * as NextImage from "next/image";

const OriginalNextImage = NextImage.default;

Object.defineProperty(NextImage, "default", {
  configurable: true,
  value: (props) => (
    <OriginalNextImage
      {...props}
      unoptimized
      // this is new!
      blurDataURL="data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAADAAQDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAbEAADAAMBAQAAAAAAAAAAAAABAgMABAURUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAECEf/aAAwDAQACEQMRAD8Anz9voy1dCI2mectSE5ioFCqia+KCwJ8HzGMZPqJb1oPEf//Z"
    />
  ),
});

What we have done here is a bit of a hack, granted. But it's effective, as all good hacks are. With this, we're setting all placeholder images to be the same data, at least in the context of Storybook. The string above is actually the base64 representation of the placeholder for the plaiceholder example image on their homepage. But we could just as easily upload our own image there and use that.

And with this, you should see the following when running Storybook:

Note: our testImage for this is this image by Studio Blackthorns on Unsplash.

If you want to read more about the automatic placeholder generation of Next.js' Image component, take a look at the placeholder prop's documentation.

Don't forget that you can use or take a look at the accompanying repo.

💡 This post was published 2021/07/21 and last updated 2021/07/21. Since node libraries change and break all the time, there's a non-trivial chance that all of this is obsolete and none of it works when you read this in your flying taxi, gulpin' on insect protein shakes sometime in the distant future or, let's be honest here, next week. If so, tell me in the comments and I'll see if an update would still be relevant.

24