Storybook Addon Next
Table of Contents
- Supported Features
- Requirements
- Examples
- Getting Started
- Documentation
- Similar Projects
- Want to suggest additional features?
- Didn't find what you were looking for?
Supported Features
Requirements
- Next.js >= 9.x
- Storybook >= 6.x
- Storybook webpack 5 builder
- Intro
- Installation guide
- It's not that this plugin can't support the webpack 4 builder, it's just that there hasn't been much of a need to and this is what Storybook recommends for nextjs apps. If you feel that you have a good use case, feel free to open up an issue.
- Storybook webpack 5 builder
- Your Next.js config file uses the
.js
extension and not the.mjs
extension (i.e.next.config.js
notnext.config.mjs
)- See next.config.js for more details
Examples
- Nextjs v12 - Source
- Tailwindcss - Source
- Nextjs v11.1 - Source
- Nextjs v11.0 - Source
- Nextjs v10 - Source
- Nextjs v9 - Source
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
// .storybook/main.js
module.exports = {
// other config ommited for brevity
addons: [
// ...
'storybook-addon-next'
// ...
]
}
Partay
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
Options
This addon can be passed an options object for addional configuration if needed.
For example:
// .storybook/main.js
const path = require('path')
module.exports = {
// other config ommited for brevity
addons: [
// ...
{
name: 'storybook-addon-next',
options: {
nextConfigPath: path.resolve(__dirname, '../next.config.js')
}
}
// ...
]
}
nextConfigPath
: The absolute path to thenext.config.js
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 Image
s 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 Image
s 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.
Right now only the
.js
extension of the Next.js config is supported, not.mjs
. See next.config.js for more details.
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'
// ...
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 addon, 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"
}
}
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.
next.config.js
ESM
Right now the only supported config format for Next.js that this plugin supports is the commonjs version of the config (i.e. next.config.js
). This is mostly because I haven't figured out how to require a .mjs
file from a storybook addon (which is bound to commonjs modules as far as I know right now). If you are able to help, I'd love it if you could contribute to this discussion to get support for the .mjs
version if such support is even possible.
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
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 storybook-addon-next
, image imports just imported the raw path to the image (e.g. 'static/media/stories/assets/plugin.svg'
). When using storybook-addon-next
image imports work the "Next.js way" meaning that we now get an object when we import an image. For example:
{
"src": "static/media/stories/assets/plugin.svg",
"height": 48,
"width": 48,
"blurDataURL": "static/media/stories/assets/plugin.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.
This addon breaks when the .mjs extension for the next config is used
Right now using next.config.mjs
isn't supported by this addon. See next.config.js for more details. Right now, it is required for you to use the .js
extension instead. Feel free to help out on this discussion to get this supported.
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.
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.