8.0.9 ā€¢ Published 16 hours ago

@storybook/nextjs v8.0.9

Weekly downloads
-
License
MIT
Repository
github
Last release
16 hours ago

Storybook for Next.js

Table of Contents

Supported Features

šŸ‘‰ Next.js's Image Component

šŸ‘‰ Next.js Routing

šŸ‘‰ Sass/Scss

šŸ‘‰ Css/Sass/Scss Modules

šŸ‘‰ Styled JSX

šŸ‘‰ Postcss

šŸ‘‰ Absolute Imports

šŸ‘‰ Runtime Config

šŸ‘‰ Custom Webpack Config

šŸ‘‰ Typescript (already supported out of the box by Storybook)

Requirements

Getting Started

In a project without Storybook

Follow the prompts after running this command in your Next.js project's root directory:

npx storybook init

More on getting started with Storybook

In a project with Storybook

Update your main.js to look something like this:

Install the framework:

yarn install @storybook/nextjs
// .storybook/main.js
module.exports = {
  framework: {
    name: '@storybook/nextjs',
    options: {};
  }
}

Documentation

Options

You can be pass an options object for addional configuration if needed.

For example:

// .storybook/main.js
const path = require('path');

module.exports = {
  // other config ommited for brevity
  framework: {
    name: '@storybook/nextjs',
    options: {
      nextConfigPath: path.resolve(__dirname, '../next.config.js'),
    },
  },
  // ...
};
  • nextConfigPath: The absolute path to the next.config.js

Next.js's Image Component

next/image is notoriously difficult to get working with Storybook. This framework allows you to use Next.js's Image component with no configuration!

Local Images

Local images work just fine! 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 framework)
        // 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 framework yet. Feel free to open up an issue if this is something you want to see.

Next.js Routing

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 framework will shallowly merge whatever you put here into the router.

// SomeComponentThatUsesTheRouter.stories.js
import SomeComponentThatUsesTheRouter from './SomeComponentThatUsesTheRouter';

export default {
  component: SomeComponentThatUsesTheRouter,
};

// if you have the actions addon
// you can click the links and see the route change events there
export const Example = {
  parameters: {
    nextRouter: {
      path: '/profile/[id]',
      asPath: '/profile/ryanclementshax',
      query: {
        id: 'ryanclementshax',
      },
    },
  },
};

Global Defaults

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

// .storybook/main.js

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

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.

// .storybook/main.js

export const parameters = {
  nextRouter: {
    push() {
      // The default implementation that logs the action into the action tab is lost
    },
  },
};

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

// .storybook/main.js
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.

// next.config.js
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

css modules work as expected.

// 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 framework supports that out of the box too, zero config.

// This works just fine in Storybook now
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;

You can use your own babel config too. This is an example of how you can customize styled-jsx.

// .babelrc or whatever config file you use
{
  "presets": [
    [
      "next/babel",
      {
        "styled-jsx": {
          "plugins": ["@styled-jsx/plugin-sass"]
        }
      }
    ]
  ]
}

If you use a monorepo, you may need to add the babel config yourself to your storybook project. Just add a babel config to your storybook project with the following contents to get started.

{
  "presets": ["next/babel"]
}

Postcss

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

This allows for cool things like zero config tailwindcss! (See Next.js' example)

Absolute Imports

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

// 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';

// ...

Runtime Config

Next.js allows for Runtime Configuration which lets you import a handy getConfig function to get certain configuration defined in your next.config.js file at runtime.

In the context of Storybook with this framework, you can expect Next.js's Runtime Configuration feature to work just fine.

Note, because Storybook doesn't server render your components, your components will only see what they normally see on the client side (i.e. they won't see serverRuntimeConfig but will see publicRuntimeConfig).

For example, consider the following Next.js config:

// next.config.js
module.exports = {
  serverRuntimeConfig: {
    mySecret: 'secret',
    secondSecret: process.env.SECOND_SECRET, // Pass through env variables
  },
  publicRuntimeConfig: {
    staticFolder: '/static',
  },
};

Calls to getConfig would return the following object when called within Storybook:

{
  "serverRuntimeConfig": {},
  "publicRuntimeConfig": {
    "staticFolder": "/static"
  }
}

Custom Webpack Config

Next.js comes with a lot of things for free out of the box like sass support, but sometimes you add custom webpack config modifications to Next.js. This framework takes care of most of the webpack modifications you would want to add. If Next.js supports a feature out of the box, then that feature will work out of the box in Storybook. If Next.js doesn't support something out of the box, but makes it easy to configure, then this framework will do the same for that thing for Storybook.

Any webpack modifications desired for Storybook should be made in .storybook/main.js.

Note: Not all webpack modifications are copy/paste-able between next.config.js and .storybook/main.js. It is recommended to do your reasearch on how to properly make your modifcation to Storybook's webpack config and on how webpack works.

Below is an example of how to add svgr support to Storybook with this framework.

// .storybook/main.js
module.exports = {
  // other config omitted for brevity
  webpackFinal: async (config) => {
    // this modifies the existing image rule to exclude .svg files
    // since you want to handle those files with @svgr/webpack
    const imageRule = config.module.rules.find((rule) => rule.test.test('.svg'));
    imageRule.exclude = /\.svg$/;

    // configure .svg files to be loaded with @svgr/webpack
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });

    return config;
  },
};

Typescript

Storybook handles most Typescript configurations, but this framework adds additional support for Next.js's support for Absolute Imports and Module path aliases. In short, it takes into account your tsconfig.json's baseUrl and paths. Thus, a tsconfig.json like the one below would work out of the box.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}

Notes for Yarn v2 and v3 users

If you're using Yarn v2 or v3, you may run into issues where Storybook can't resolve style-loader or css-loader. For example, you might get errors like:

Module not found: Error: Can't resolve 'css-loader'\ Module not found: Error: Can't resolve 'style-loader'

This is because those versions of Yarn have different package resolution rules than Yarn v1.x. If this is the case for you, just install the package directly.

FAQ

Stories for pages

Next.js page files can contain imports to modules meant to run in a node environment (for use in data fetching functions). If you import from a Next.js page file containing those node module imports in your stories, your Storybook's Webpack will crash because those modules will not run in a browser. To get around this, you can extract the component in your page file into a separate file and import that component in your stories. Or, if that's not feasible for some reason, you can polyfill those modules in your Storybook's webpackFinal configuration.

Before

// ./pages/my-page.jsx
import fs from 'fs';

export default MyPage = (props) => (
  // ...
);

export const getStaticProps = async () => {
  // Logic that uses `fs`
};

After

// ./pages/my-page.jsx
import fs from 'fs';

import MyPage from 'components/MyPage';

export default MyPage;

export const getStaticProps = async () => {
  // Logic that uses `fs`
};

Statically imported images won't load

Make sure you are treating image imports the same way you treat them when using next/image in normal development.

Before using this framework, image imports just imported the raw path to the image (e.g. 'static/media/stories/assets/logo.svg'). Now image imports work the "Next.js way", meaning that you now get an object when importing an image. For example:

{
  "src": "static/media/stories/assets/logo.svg",
  "height": 48,
  "width": 48,
  "blurDataURL": "static/media/stories/assets/logo.svg"
}

Therefore, if something in storybook isn't showing the image properly, make sure you expect the object to be returned from an import instead of just the asset path.

See local images for more detail on how Next.js treats static image imports.

Module not found: Error: Can't resolve package name

You might get this if you're using Yarn v2 or v3. See Notes for Yarn v2 and v3 users for more details.

Acknowledgements

This framework borrows heavily from these Storybook addons:

7.6.19

1 day ago

7.6.18

10 days ago

8.0.9

11 days ago

8.0.8

22 days ago

8.0.7

22 days ago

8.1.0-alpha.7

22 days ago

8.0.6

28 days ago

8.1.0-alpha.6

28 days ago

8.0.5

1 month ago

8.1.0-alpha.5

1 month ago

8.0.4

1 month ago

8.0.3

1 month ago

8.1.0-alpha.4

1 month ago

8.0.2

1 month ago

8.1.0-alpha.3

1 month ago

8.0.1

2 months ago

8.1.0-alpha.2

2 months ago

8.1.0-alpha.1

2 months ago

8.1.0-alpha.0

2 months ago

8.0.0-rc.4

2 months ago

8.0.0-rc.5

2 months ago

8.0.0

2 months ago

8.0.0-rc.3

2 months ago

8.0.0-rc.2

2 months ago

8.0.0-rc.1

2 months ago

8.0.0-rc.0

2 months ago

8.0.0-beta.6

2 months ago

8.0.0-beta.5

2 months ago

8.0.0-beta.4

2 months ago

7.6.17

2 months ago

8.0.0-beta.3

3 months ago

7.6.16

3 months ago

7.6.15

3 months ago

8.0.0-beta.2

3 months ago

7.6.14

3 months ago

8.0.0-beta.1

3 months ago

7.6.13

3 months ago

8.0.0-beta.0

3 months ago

8.0.0-alpha.17

3 months ago

7.6.12

3 months ago

8.0.0-alpha.16

3 months ago

7.6.11

3 months ago

8.0.0-alpha.15

3 months ago

8.0.0-alpha.14

3 months ago

8.0.0-alpha.13

3 months ago

8.0.0-alpha.12

3 months ago

7.6.10

4 months ago

8.0.0-alpha.11

4 months ago

7.6.9

4 months ago

8.0.0-alpha.10

4 months ago

7.6.8

4 months ago

8.0.0-alpha.9

4 months ago

8.0.0-alpha.8

4 months ago

8.0.0-alpha.7

4 months ago

7.6.7

4 months ago

8.0.0-alpha.6

4 months ago

8.0.0-alpha.5

4 months ago

8.0.0-alpha.4

5 months ago

7.6.6

5 months ago

7.6.5

5 months ago

8.0.0-alpha.3

5 months ago

8.0.0-alpha.2

5 months ago

7.6.4

5 months ago

8.0.0-alpha.1

5 months ago

8.0.0-alpha.0

5 months ago

7.3.0-alpha.0

9 months ago

7.3.1

9 months ago

7.3.0

9 months ago

7.4.4

7 months ago

7.4.3

8 months ago

7.4.2

8 months ago

7.4.1

8 months ago

7.4.6

7 months ago

7.4.5

7 months ago

7.4.0

8 months ago

7.5.3

6 months ago

7.5.2

6 months ago

7.5.1

7 months ago

7.5.0

7 months ago

7.4.0-alpha.0

9 months ago

7.4.0-alpha.1

8 months ago

7.4.0-alpha.2

8 months ago

7.6.0-beta.2

5 months ago

7.1.0-beta.0

10 months ago

7.1.0-beta.2

10 months ago

7.1.0-beta.1

10 months ago

7.1.0-beta.3

10 months ago

7.6.2

5 months ago

7.6.1

5 months ago

7.6.0

5 months ago

7.6.3

5 months ago

7.1.0-rc.0

10 months ago

7.1.0-rc.1

10 months ago

7.1.0-rc.2

10 months ago

7.6.0-beta.0

5 months ago

7.6.0-beta.1

5 months ago

7.6.0-alpha.1

7 months ago

7.6.0-alpha.0

7 months ago

7.6.0-alpha.3

6 months ago

7.6.0-alpha.2

6 months ago

7.6.0-alpha.5

6 months ago

7.2.2-alpha.1

9 months ago

7.6.0-alpha.4

6 months ago

7.2.2-alpha.0

9 months ago

7.6.0-alpha.7

5 months ago

7.6.0-alpha.6

6 months ago

7.1.0-alpha.43

10 months ago

7.1.0-alpha.44

10 months ago

7.5.0-alpha.3

7 months ago

7.5.0-alpha.4

7 months ago

7.5.0-alpha.1

8 months ago

7.5.0-alpha.2

8 months ago

7.5.0-alpha.0

8 months ago

7.5.0-alpha.7

7 months ago

7.5.0-alpha.5

7 months ago

7.5.0-alpha.6

7 months ago

7.1.0-alpha.42

10 months ago

7.0.27

10 months ago

7.1.1

9 months ago

7.0.25

10 months ago

7.1.0

10 months ago

7.0.26

10 months ago

7.2.3

9 months ago

7.2.0-rc.0

9 months ago

7.2.0-alpha.0

10 months ago

7.2.2

9 months ago

7.2.1

9 months ago

7.2.0

9 months ago

7.3.2

9 months ago

7.1.0-alpha.41

10 months ago

7.1.0-alpha.40

10 months ago

7.0.24

10 months ago

7.1.0-alpha.32

11 months ago

7.1.0-alpha.33

11 months ago

7.1.0-alpha.34

11 months ago

7.1.0-alpha.35

11 months ago

7.1.0-alpha.36

11 months ago

7.1.0-alpha.37

11 months ago

7.1.0-alpha.38

11 months ago

7.1.0-alpha.39

11 months ago

7.1.0-alpha.30

11 months ago

7.1.0-alpha.31

11 months ago

7.1.0-alpha.28

11 months ago

7.1.0-alpha.29

11 months ago

7.0.19

11 months ago

7.0.23

11 months ago

7.0.21

11 months ago

7.0.22

11 months ago

7.0.20

11 months ago

7.0.8

1 year ago

7.0.9

12 months ago

7.1.0-alpha.21

12 months ago

7.0.12

12 months ago

7.1.0-alpha.22

12 months ago

7.0.13

12 months ago

7.1.0-alpha.23

11 months ago

7.0.10

12 months ago

7.1.0-alpha.24

11 months ago

7.0.11

12 months ago

7.1.0-alpha.25

11 months ago

7.1.0-alpha.26

11 months ago

7.1.0-alpha.27

11 months ago

7.0.18

11 months ago

7.0.16

11 months ago

7.0.17

11 months ago

7.0.14

12 months ago

7.0.15

12 months ago

7.1.0-alpha.20

12 months ago

7.1.0-alpha.12

1 year ago

7.1.0-alpha.13

12 months ago

7.1.0-alpha.14

12 months ago

7.1.0-alpha.15

12 months ago

7.1.0-alpha.16

12 months ago

7.1.0-alpha.17

12 months ago

7.1.0-alpha.18

12 months ago

7.1.0-alpha.19

12 months ago

7.0.0-beta.62

1 year ago

7.0.0-beta.63

1 year ago

7.0.0-beta.64

1 year ago

7.0.7

1 year ago

7.0.6

1 year ago

7.0.5

1 year ago

7.0.0-rc.8

1 year ago

7.0.0-rc.7

1 year ago

7.0.0-rc.9

1 year ago

7.0.0-rc.4

1 year ago

7.0.0-rc.3

1 year ago

7.0.0-rc.6

1 year ago

7.0.0-rc.5

1 year ago

7.0.0-rc.0

1 year ago

7.0.0-rc.2

1 year ago

7.0.0-rc.1

1 year ago

7.0.0

1 year ago

7.0.4

1 year ago

7.0.3

1 year ago

7.0.2

1 year ago

7.0.1

1 year ago

7.1.0-alpha.10

1 year ago

7.1.0-alpha.11

1 year ago

7.0.0-rc.11

1 year ago

7.0.0-rc.10

1 year ago

7.1.0-alpha.0

1 year ago

7.1.0-alpha.9

1 year ago

7.1.0-alpha.1

1 year ago

7.1.0-alpha.2

1 year ago

7.1.0-alpha.3

1 year ago

7.1.0-alpha.4

1 year ago

7.1.0-alpha.5

1 year ago

7.1.0-alpha.6

1 year ago

7.1.0-alpha.7

1 year ago

7.1.0-alpha.8

1 year ago

7.0.0-beta.60

1 year ago

7.0.0-beta.61

1 year ago

7.0.0-beta.55

1 year ago

7.0.0-beta.56

1 year ago

7.0.0-beta.57

1 year ago

7.0.0-beta.58

1 year ago

7.0.0-beta.59

1 year ago

7.0.0-beta.50

1 year ago

7.0.0-beta.51

1 year ago

7.0.0-beta.52

1 year ago

7.0.0-beta.53

1 year ago

7.0.0-beta.54

1 year ago

7.0.0-beta.47

1 year ago

7.0.0-beta.48

1 year ago

7.0.0-beta.49

1 year ago

7.0.0-beta.44

1 year ago

7.0.0-beta.45

1 year ago

7.0.0-beta.46

1 year ago

7.0.0-beta.40

1 year ago

7.0.0-beta.41

1 year ago

7.0.0-beta.42

1 year ago

7.0.0-beta.43

1 year ago

7.0.0-beta.38

1 year ago

7.0.0-beta.39

1 year ago

7.0.0-beta.23

1 year ago

7.0.0-beta.24

1 year ago

7.0.0-beta.25

1 year ago

7.0.0-beta.20

1 year ago

7.0.0-beta.21

1 year ago

7.0.0-beta.26

1 year ago

7.0.0-beta.27

1 year ago

7.0.0-beta.28

1 year ago

7.0.0-beta.29

1 year ago

7.0.0-alpha.59

1 year ago

7.0.0-alpha.58

1 year ago

7.0.0-beta.11

1 year ago

7.0.0-beta.12

1 year ago

7.0.0-beta.13

1 year ago

7.0.0-beta.14

1 year ago

7.0.0-beta.10

1 year ago

7.0.0-beta.19

1 year ago

7.0.0-beta.15

1 year ago

7.0.0-beta.16

1 year ago

7.0.0-beta.17

1 year ago

7.0.0-alpha.62

1 year ago

7.0.0-alpha.60

1 year ago

7.0.0-alpha.61

1 year ago

7.0.0-beta.33

1 year ago

7.0.0-beta.34

1 year ago

7.0.0-beta.35

1 year ago

7.0.0-beta.36

1 year ago

7.0.0-beta.30

1 year ago

7.0.0-beta.31

1 year ago

7.0.0-beta.32

1 year ago

7.0.0-beta.6

1 year ago

7.0.0-beta.7

1 year ago

7.0.0-beta.4

1 year ago

7.0.0-beta.5

1 year ago

7.0.0-beta.8

1 year ago

7.0.0-beta.9

1 year ago

7.0.0-beta.2

1 year ago

7.0.0-beta.3

1 year ago

7.0.0-beta.0

1 year ago

7.0.0-beta.1

1 year ago

7.0.0-alpha.57

1 year ago

7.0.0-alpha.56

1 year ago

7.0.0-alpha.55

1 year ago

7.0.0-alpha.54

1 year ago

7.0.0-alpha.53

1 year ago

7.0.0-alpha.52

1 year ago

7.0.0-alpha.51

1 year ago

7.0.0-alpha.50

1 year ago

7.0.0-alpha.49

1 year ago

7.0.0-alpha.48

1 year ago

7.0.0-alpha.47

2 years ago

7.0.0-alpha.46

2 years ago

7.0.0-alpha.45

2 years ago

7.0.0-alpha.44

2 years ago

7.0.0-alpha.43

2 years ago

7.0.0-alpha.42

2 years ago

7.0.0-alpha.41

2 years ago