# Storybook for Next.js ## Table of Contents - [Supported Features](#supported-features) - [Requirements](#requirements) - [Getting Started](#getting-started) - [In a project without Storybook](#in-a-project-without-storybook) - [In a project with Storybook](#in-a-project-with-storybook) - [Automatic migration](#automatic-migration) - [Manual migration](#manual-migration) - [Documentation](#documentation) - [Options](#options) - [Next.js's Image Component](#nextjss-image-component) - [Local Images](#local-images) - [Remote Images](#remote-images) - [AVIF](#avif) - [Next.js Font Optimization](#nextjs-font-optimization) - [@next/font/google](#nextfontgoogle) - [@next/font/local](#nextfontlocal) - [Not supported features of @next/font](#not-supported-features-of-nextfont) - [Next.js Routing](#nextjs-routing) - [Overriding defaults](#overriding-defaults) - [Global Defaults](#global-defaults) - [Default Router](#default-router) - [Actions Integration Caveats](#actions-integration-caveats) - [Next.js Navigation](#nextjs-navigation) - [Set `nextjs.appDirectory` to `true`](#set-nextjsappdirectory-to-true) - [Overriding defaults](#overriding-defaults-1) - [Global Defaults](#global-defaults-1) - [`useSelectedLayoutSegment` and `useSelectedLayoutSegments` hook](#useselectedlayoutsegment-and-useselectedlayoutsegments-hook) - [Default Navigation Context](#default-navigation-context) - [Actions Integration Caveats](#actions-integration-caveats-1) - [Next.js Head](#nextjs-head) - [Sass/Scss](#sassscss) - [Css/Sass/Scss Modules](#csssassscss-modules) - [Styled JSX](#styled-jsx) - [Postcss](#postcss) - [Absolute Imports](#absolute-imports) - [Runtime Config](#runtime-config) - [Custom Webpack Config](#custom-webpack-config) - [Typescript](#typescript) - [Notes for Yarn v2 and v3 users](#notes-for-yarn-v2-and-v3-users) - [FAQ](#faq) - [Stories for pages/components which fetch data](#stories-for-pagescomponents-which-fetch-data) - [Statically imported images won't load](#statically-imported-images-wont-load) - [Module not found: Error: Can't resolve \[package name\]](#module-not-found-error-cant-resolve-package-name) - [Acknowledgements](#acknowledgements) ## Supported Features πŸ‘‰ [Next.js's Image Component](#nextjss-image-component) πŸ‘‰ [Next.js Font Optimization](#nextjs-font-optimization) πŸ‘‰ [Next.js Routing (next/router)](#nextjs-routing) πŸ‘‰ [Next.js Head (next/head)](#nextjs-head) πŸ‘‰ [Next.js Navigation (next/navigation)](#nextjs-navigation) πŸ‘‰ [Sass/Scss](#sassscss) πŸ‘‰ [Css/Sass/Scss Modules](#csssassscss-modules) πŸ‘‰ [Styled JSX](#styled-jsx) πŸ‘‰ [Postcss](#postcss) πŸ‘‰ [Absolute Imports](#absolute-imports) πŸ‘‰ [Runtime Config](#runtime-config) πŸ‘‰ [Custom Webpack Config](#custom-webpack-config) πŸ‘‰ [Typescript](#typescript) (already supported out of the box by Storybook) ## Requirements - [Next.js](https://nextjs.org/) >= 12.x - [Storybook](https://storybook.js.org/) >= 7.x ## Getting Started ### In a project without Storybook Follow the prompts after running this command in your Next.js project's root directory: ```bash npx storybook@next init ``` [More on getting started with Storybook](https://storybook.js.org/docs/react/get-started/install) ### In a project with Storybook This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command: ```bash npx storybook@next upgrade --prerelease ``` #### Automatic migration When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/nextjs`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. #### Manual migration Install the framework: ```bash yarn add --dev @storybook/nextjs@next ``` Update your `main.js` to change the framework property: ```js // .storybook/main.js export default { // ... framework: { // name: '@storybook/react-webpack5', // Remove this name: '@storybook/nextjs', // Add this options: {}, }, }; ``` If you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: ```js // .storybook/main.js export default { // ... addons: [ // ... // These can both be removed // 'storybook-addon-next', // 'storybook-addon-next-router', ], }; ``` ## Documentation ### Options You can be pass an options object for addional configuration if needed. For example: ```js // .storybook/main.js import * as path from 'path'; export default { // ... 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](https://nextjs.org/docs/api-reference/next/image) is [notoriously difficult](https://github.com/vercel/next.js/issues/18393) to get working with Storybook. This framework allows you to use Next.js's `Image` component with no configuration! #### Local Images [Local images](https://nextjs.org/docs/basic-features/image-optimization#local-images) work just fine! Keep in mind that this feature was [only added in Next.js v11](https://nextjs.org/blog/next-11#automatic-size-detection-local-images). ```js import Image from 'next/image'; import profilePic from '../public/me.png'; function Home() { return ( <>

My Homepage

Picture of the author

Welcome to my homepage!

); } ``` #### Remote Images [Remote images](https://nextjs.org/docs/basic-features/image-optimization#remote-images) also work just fine! ```js import Image from 'next/image'; export default function Home() { return ( <>

My Homepage

Picture of the author

Welcome to my homepage!

); } ``` #### AVIF This format is not supported by this framework yet. Feel free to [open up an issue](https://github.com/storybookjs/storybook/issues) if this is something you want to see. ### Next.js Font Optimization [@next/font](https://nextjs.org/docs/basic-features/font-optimization) is partially supported in Storybook. The packages `@next/font/google` and `@next/font/local` are supported. #### @next/font/google You don't have to do anything. `@next/font/google` is supported out of the box. #### @next/font/local For local fonts you have to define the [src](https://nextjs.org/docs/api-reference/next/font#src) property. The path is relative to the directory where the font loader function is called. If the following component defines your localFont like this: ```js // src/components/MyComponent.js import localFont from '@next/font/local'; const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' }); ``` You have to tell Storybook where the `fonts` directory is located. The `from` value is relative to the `.storybook` directory. The `to` value is relative to the execution context of Storybook. Very likely it is the root of your project. ```js // .storybook/main.js export default { ... "staticDirs": [ { from: '../src/components/fonts', to: 'src/components/fonts' } ], } ``` #### Not supported features of @next/font The following features are not supported (yet). Support for these features might be planned for the future: - [Support font loaders configuration in next.config.js](https://nextjs.org/docs/basic-features/font-optimization#specifying-a-subset) - [fallback](https://nextjs.org/docs/api-reference/next/font#fallback) option - [adjustFontFallback](https://nextjs.org/docs/api-reference/next/font#adjustfontfallback) option - [declarations](https://nextjs.org/docs/api-reference/next/font#declarations) option - [preload](https://nextjs.org/docs/api-reference/next/font#preload) option gets ignored. Storybook handles Font loading its own way. - [display](https://nextjs.org/docs/api-reference/next/font#display) option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly. ### Next.js Routing [Next.js's router](https://nextjs.org/docs/routing/introduction) is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the Actions ctions panel if you have the [Storybook actions addon](https://storybook.js.org/docs/react/essentials/actions). > When using Next.js 13+, you should only use `next/router` in the `pages` directory. In the `app` directory, it is necessary to use `next/navigation`. #### Overriding defaults Per-story overrides can be done by adding a `nextjs.router` property onto the story [parameters](https://storybook.js.org/docs/react/writing-stories/parameters). The framework will shallowly merge whatever you put here into the router. ```js // SomeComponentThatUsesTheRouter.stories.js import SomeComponentThatUsesTheRouter from './SomeComponentThatUsesTheRouter'; export default { component: SomeComponentThatUsesTheRouter, }; // If you have the actions addon, // you can interact with the links and see the route change events there export const Example = { parameters: { nextjs: { router: { path: '/profile/[id]', asPath: '/profile/1', query: { id: '1', }, }, }, }, }; ``` #### Global Defaults Global defaults can be set in [preview.js](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) and will be shallowly merged with the default router. ```js // .storybook/preview.js export const parameters = { nextjs: { router: { path: '/some-default-path', asPath: '/some-default-path', query: {}, }, }, }; ``` #### Default Router The default values on the stubbed router are as follows (see [globals](https://storybook.js.org/docs/react/essentials/toolbars-and-globals#globals) for more details on how globals work) ```ts const defaultRouter = { push(...args) { action('nextRouter.push')(...args); return Promise.resolve(true); }, replace(...args) { action('nextRouter.replace')(...args); return Promise.resolve(true); }, reload(...args) { action('nextRouter.reload')(...args); }, back(...args) { action('nextRouter.back')(...args); }, forward() { action('nextRouter.forward')(); }, prefetch(...args) { action('nextRouter.prefetch')(...args); return Promise.resolve(); }, beforePopState(...args) { action('nextRouter.beforePopState')(...args); }, events: { on(...args) { action('nextRouter.events.on')(...args); }, off(...args) { action('nextRouter.events.off')(...args); }, emit(...args) { action('nextRouter.events.emit')(...args); }, }, // The locale should be configured [globally](https://storybook.js.org/docs/react/essentials/toolbars-and-globals#globals) locale: globals?.locale, asPath: '/', basePath: '/', isFallback: false, isLocaleDomain: false, isReady: true, isPreview: false, route: '/', pathname: '/', query: {}, }; ``` #### Actions Integration Caveats If you override a function, you lose the automatic actions integration and have to build it out yourself. ```js // .storybook/preview.js export const parameters = { nextjs: { router: { push() { // The default implementation that logs the action into the Actions panel is lost }, }, }, }; ``` Doing this yourself looks something like this (make sure you install the `@storybook/addon-actions` package): ```js // .storybook/preview.js import { action } from '@storybook/addon-actions'; export const parameters = { nextjs: { router: { push(...args) { // Custom logic can go here // This logs to the Actions panel action('nextRouter.push')(...args); // Return whatever you want here return Promise.resolve(true); }, }, }, }; ``` ### Next.js Navigation > Please note that [next/navigation](https://beta.nextjs.org/docs/upgrade-guide#step-5-migrating-routing-hooks) can only be used in components/pages in the `app` directory of Next.js 13+. #### Set `nextjs.appDirectory` to `true` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true` in your Story: ```js // SomeComponentThatUsesTheRouter.stories.js import SomeComponentThatUsesTheNavigation from './SomeComponentThatUsesTheNavigation'; export default { component: SomeComponentThatUsesTheNavigation, }; export const Example = { parameters: { nextjs: { appDirectory: true, }, }, }, ``` If your Next.js project uses the `app` directory for every page (in other words, it does not have a `pages` directory), you can set the parameter `nextjs.appDirectory` to `true` in the [preview.js](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) file to apply it to all stories. ```js // .storybook/preview.js export const parameters = { nextjs: { appDirectory: true, }, }; ``` The parameter `nextjs.appDirectory` defaults to `false` if not set. #### Overriding defaults Per-story overrides can be done by adding a `nextjs.navigation` property onto the story [parameters](https://storybook.js.org/docs/react/writing-stories/parameters). The framework will shallowly merge whatever you put here into the router. ```js // SomeComponentThatUsesTheNavigation.stories.js import SomeComponentThatUsesTheNavigation from './SomeComponentThatUsesTheNavigation'; export default { component: SomeComponentThatUsesTheNavigation, }; // If you have the actions addon, // you can interact with the links and see the route change events there export const Example = { parameters: { nextjs: { appDirectory: true, navigation: { pathname: '/profile', query: { user: '1', }, }, }, }, }; ``` #### Global Defaults Global defaults can be set in [preview.js](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) and will be shallowly merged with the default router. ```js // .storybook/preview.js export const parameters = { nextjs: { appDirectory: true, navigation: { pathname: '/some-default-path', }, }, }; ``` #### `useSelectedLayoutSegment` and `useSelectedLayoutSegments` hook The `useSelectedLayoutSegment` and `useSelectedLayoutSegments` hooks are supported in Storybook. You have to set the `nextjs.navigation.segments` parameter to return the segments you want to use. ```js // SomeComponentThatUsesTheNavigation.stories.js import SomeComponentThatUsesTheNavigation from './SomeComponentThatUsesTheNavigation'; export default { component: SomeComponentThatUsesTheNavigation, parameters: { nextjs: { appDirectory: true, navigation: { segments: ['dashboard', 'analytics'] }, }, }, }; export const Example = {}; // SomeComponentThatUsesTheNavigation.js import { useSelectedLayoutSegment, useSelectedLayoutSegments } from 'next/navigation'; export default function SomeComponentThatUsesTheNavigation() { const segment = useSelectedLayoutSegment(); // dashboard const segments = useSelectedLayoutSegments(); // ["dashboard", "analytics"] ... } ``` The default value of `nextjs.navigation.segments` is `[]` if not set. #### Default Navigation Context The default values on the stubbed navigation context are as follows: ```ts const defaultNavigationContext = { push(...args) { action('nextNavigation.push')(...args); }, replace(...args) { action('nextNavigation.replace')(...args); }, forward(...args) { action('nextNavigation.forward')(...args); }, back(...args) { action('nextNavigation.back')(...args); }, prefetch(...args) { action('nextNavigation.prefetch')(...args); }, refresh: () => { action('nextNavigation.refresh')(); }, pathname: '/', query: {}, }; ``` #### Actions Integration Caveats If you override a function, you lose the automatic action tab integration and have to build it out yourself. ```js // .storybook/preview.js export const parameters = { nextjs: { appDirectory: true, navigation: { push() { // The default implementation that logs the action into the Actions panel is lost }, }, }, }; ``` Doing this yourself looks something like this (make sure you install the `@storybook/addon-actions` package): ```js // .storybook/preview.js import { action } from '@storybook/addon-actions'; export const parameters = { nextjs: { appDirectory: true, navigation: { push(...args) { // Custom logic can go here // This logs to the Actions panel action('nextNavigation.push')(...args); // Return whatever you want here return Promise.resolve(true); }, }, }, }; ``` ### Next.js Head [next/head](https://nextjs.org/docs/api-reference/next/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head children are placed into the head element of the iframe that Storybook uses to render your stories. ### Sass/Scss [Global sass/scss stylesheets](https://nextjs.org/docs/basic-features/built-in-css-support#sass-support) are supported without any additional configuration as well. Just import them into [preview.js](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) ```js import '../styles/globals.scss'; ``` This will automatically include any of your [custom sass configurations](https://nextjs.org/docs/basic-features/built-in-css-support#customizing-sass-options) in your `next.config.js` file. ```js // next.config.js import * as path from 'path'; export default { // Any options here are included in Sass compilation for your stories sassOptions: { includePaths: [path.join(__dirname, 'styles')], }, }; ``` ### Css/Sass/Scss Modules [css modules](https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css) work as expected. ```js // 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 ( ); } ``` ### Styled JSX The built in CSS-in-JS solution for Next.js is [styled-jsx](https://nextjs.org/docs/basic-features/built-in-css-support#css-in-js), and this framework supports that out of the box too, zero config. ```js // This works just fine in Storybook now function HelloWorld() { return (
Hello world

scoped!

); } export default HelloWorld; ``` You can use your own babel config too. This is an example of how you can customize styled-jsx. ```json // .babelrc or whatever config file you use { "presets": [ [ "next/babel", { "styled-jsx": { "plugins": ["@styled-jsx/plugin-sass"] } } ] ] } ``` ### Postcss Next.js lets you [customize postcss config](https://nextjs.org/docs/advanced-features/customizing-postcss-config#default-behavior). Thus this framework will automatically handle your postcss config for you. This allows for cool things like zero config tailwindcss! (See [Next.js' example](https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss)) ### Absolute Imports Goodbye `../`! Absolute imports from the root directory work just fine. ```js // All good! import Button from 'components/button'; // Also good! import styles from 'styles/HomePage.module.css'; export default function HomePage() { return ( <>

Hello World