A no config Storybook addon that makes Next.js features just work in Storybook

Storybook Addon Next

? No config support for Next.js: Tired of writing and debugging webpack config? What Next.js supports out of the box, this addon makes possible in Storybook

Supported Features

? Next.js’s Image Component

? Next.js Routing

? Sass/Scss

? Css/Sass/Scss Modules

? Styled JSX

? Postcss

? Absolute Imports

? Typescript (already supported out of the box by Storybook)

Required Versions

Examples

Getting Started

Installation

Install storybook-addon-next using yarn:

yarn add --dev storybook-addon-next

Or npm:

npm install --save-dev storybook-addon-next

Register the Addon in main.js

module.exports = {
  addons: ['storybook-addon-next']
}

Partay

?? Thats it! The supported features should work out of the box.

See Documentation for more details on how the supported features work in this addon.

If something doesn’t work as you would expect, feel free to open up an issue.

Documentation

Next.js’s Image Component

next/image is notoriously difficult to get working with storybook. This addon allows you to use Next.js’s Image component with no configuration!

Local Images

Local images work just fine with this addon! Keep in mind that this feature was only added in Next.js v11.

import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} automatically provided
        // height={500} automatically provided
        // blurDataURL="../public/me.png" set to equal the image itself (for this addon)
        // placeholder="blur" // Optional blur-up while loading
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

Remote Images

Remote images also work just fine!

import Image from 'next/image'

export default function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src="/me.png"
        alt="Picture of the author"
        width={500}
        height={500}
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

Optimization

All Next.js Images are automatically unoptimized for you.

If placeholder=”blur” is used, the blurDataURL used is the src of the image (thus effectively disabling the placeholder).

See this issue for more discussion on how Next.js Images are handled for Storybook.

AVIF

This format is not supported by this addon yet. Feel free to open up an issue if this is something you want to see.

Next.js Routing

This solution is heavily based on storybook-addon-next-router so a big thanks to lifeiscontent for providing a good solution that this addon could work off of.

Next.js’s router is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the Storybook actions tab if you have the actions addon.

Overriding defaults

Per-story overrides can be done by adding a nextRouter property onto the story parameters. The addon will shallowly merge whatever you put here into the router.

import SomeComponentThatUsesTheRouter from "./SomeComponentThatUsesTheRouter";

export default {
  title: "My Story",
};

// if you have the actions addon
// you can click the links and see the route change events there
export const Example = () => <SomeComponentThatUsesTheRouter />;

Example.parameters: {
  nextRouter: {
    path: "/profile/[id]",
    asPath: "/profile/ryanclementshax",
    query: {
      id: "ryanclementshax"
    }
  }
}

See this example for a reference.

Global Defaults

Global defaults can be set in preview.js and will be shallowly merged with the default router.

export const parameters = {
  nextRouter: {
    path: '/some-default-path',
    asPath: '/some-default-path',
    query: {}
  }
}

See this example for a reference.

Default Router

The default values on the stubbed router are as follows (see globals for more details on how globals work)

const defaultRouter = {
  locale: context?.globals?.locale,
  route: '/',
  pathname: '/',
  query: {},
  asPath: '/',
  push(...args: unknown[]) {
    action('nextRouter.push')(...args)
    return Promise.resolve(true)
  },
  replace(...args: unknown[]) {
    action('nextRouter.replace')(...args)
    return Promise.resolve(true)
  },
  reload(...args: unknown[]) {
    action('nextRouter.reload')(...args)
  },
  back(...args: unknown[]) {
    action('nextRouter.back')(...args)
  },
  prefetch(...args: unknown[]) {
    action('nextRouter.prefetch')(...args)
    return Promise.resolve()
  },
  beforePopState(...args: unknown[]) {
    action('nextRouter.beforePopState')(...args)
  },
  events: {
    on(...args: unknown[]) {
      action('nextRouter.events.on')(...args)
    },
    off(...args: unknown[]) {
      action('nextRouter.events.off')(...args)
    },
    emit(...args: unknown[]) {
      action('nextRouter.events.emit')(...args)
    }
  },
  isFallback: false
}

Actions Integration Caveats

If you override a function, you lose the automatic action tab integration and have to build it out yourself.

export const parameters = {
  nextRouter: {
    push() {
      // we lose the default implementation that logs the action into the action tab
    }
  }
}

Doing this yourself looks something like this (make sure you install the @storybook/addon-actions package):

import { action } from '@storybook/addon-actions'

export const parameters = {
  nextRouter: {
    push(...args) {
      // custom logic can go here
      // this logs to the actions tab
      action('nextRouter.push')(...args)
      // return whatever you want here
      return Promise.resolve(true)
    }
  }
}

Sass/Scss

Global sass/scss stylesheets are supported without any additional configuration as well. Just import them into preview.js

import '../styles/globals.scss'

This will automatically include any of your custom sass configurations in your next.config.js file.

const path = require('path')

module.exports = {
  // any options here are included in sass compilation for your stories
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')]
  }
}

Css/Sass/Scss Modules

Next.js supports css modules out of the box so this addon supports it too.

// this import works just fine in Storybook now
import styles from './Button.module.css'
// sass/scss is also supported
// import styles from './Button.module.scss'
// import styles from './Button.module.sass'

export function Button() {
  return (
    <button type="button" className={styles.error}>
      Destroy
    </button>
  )
}

Styled JSX

The built in CSS in JS solution for Next.js is styled-jsx, and this addon supports that out of the box too, zero config.

// This works just fine in Storybook with this addon
function HelloWorld() {
  return (
    <div>
      Hello world
      <p>scoped!</p>
      <style jsx>{`
        p {
          color: blue;
        }
        div {
          background: red;
        }
        @media (max-width: 600px) {
          div {
            background: blue;
          }
        }
      `}</style>
      <style global jsx>{`
        body {
          background: black;
        }
      `}</style>
    </div>
  )
}

export default HelloWorld

Postcss

Next.js lets you customize postcss config. Thus this addon will automatically handle your postcss config for you.

This allows for cool things like zero config tailwindcss! See the with-tailwindcss example for reference! Its a clone of Next.js’s tailwindcss example set up with storybook and this addon.

Absolute Imports

Goodbye ../! Absolute imports from the root directory work just fine with this addon.

// All good!
import Button from 'components/button'
// Also good!
import styles from 'styles/HomePage.module.css'

export default function HomePage() {
  return (
    <>
      <h1 className={styles.title}>Hello World</h1>
      <Button />
    </>
  )
}

// preview.js

// Also ok in preview.js!
import 'styles/globals.scss'

// ...

Typescript

There is no special thing this addon does to support Typescript because Storybook already supports it out of the box. I just listed it in the supported features for completeness and not to confuse anyone comparing the list of “out of the box” features Next.js has with this addon.

Similar Projects

Want to suggest additional features?

I’m open to discussion. Feel free to open up an issue.

Didn’t find what you were looking for?

Was this documentation insufficient for you?

Was it confusing?

Was it … dare I say … inaccurate?

If any of the above describes your feelings of this documentation. Feel free to open up an issue.

GitHub

View Github