Storybook for Next.js with Vite
Storybook for Next.js (Vite) is the recommended framework for developing and testing UI components in isolation for Next.js applications. It uses Vite for faster builds and better performance. It includes:
- ๐ Routing
- ๐ผ Image optimization
- โคต๏ธ Absolute imports
- ๐จ Styling
- โก Vite-powered builds
- ๐ซ and more!
This Vite-based framework offers several advantages over the Webpack-based @storybook/nextjs framework:
- โก Faster builds - Vite's build system is significantly faster than Webpack
- ๐ง Modern tooling - Uses the latest build tools and optimizations
- ๐งช Better test support - Full support for the Vitest addon and other testing features
- ๐ฆ Simpler configuration - No need for Babel or complex Webpack configurations
- ๐ฏ Better development experience - Faster HMR (Hot Module Replacement) and dev server startup
Requirements
- Next.js โฅ 14.1
Getting started
In a project without Storybook
When you run storybook init in your Next.js project, Storybook will automatically detect your project and select the @storybook/nextjs-vite framework unless your project has custom Webpack or Babel configurations that may be incompatible with Vite.
Follow the prompts after running this command in your Next.js project's root directory:
npm create storybook@latestMore on getting started with Storybook.
If your project has a custom webpack.config.js or .babelrc file, storybook init will prompt you to choose between:
@storybook/nextjs-vite(recommended) - Faster, more modern, supports latest testing features@storybook/nextjs(Webpack 5) - Better compatibility with custom Webpack/Babel configurations
Choose nextjs-vite if you're willing to migrate your custom configurations to Vite. Choose nextjs (Webpack 5) if you need to keep your existing Webpack/Babel setup.
In a project with Storybook
This framework is designed to work with Storybook 10+. If you're not already using v10, upgrade with this command:
npx storybook@latest upgradeAutomatic migration
When running the upgrade command above, you should get a prompt asking you to migrate to @storybook/nextjs-vite, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below.
You can also use the nextjs-to-nextjs-vite automigration to migrate from the Webpack-based @storybook/nextjs framework to this Vite-based framework.
Manual migration
First, install the framework:
npm install --save-dev @storybook/nextjs-viteThen, update your .storybook/main.js|ts to change the framework property:
- import type { StorybookConfig } from '@storybook/your-previous-framework';
+ import type { StorybookConfig } from '@storybook/nextjs-vite';
const config: StorybookConfig = {
// ...
- framework: '@storybook/react-webpack5',
+ framework: '@storybook/nextjs-vite',
};
export default config;If your Storybook configuration contains custom Webpack operations in webpackFinal, you will likely need to create equivalents in viteFinal.
For more information, see the Vite builder documentation.
Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed:
// Replace your-framework with nextjs or nextjs-vite
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
// ...
addons: [
// ...
// ๐ These can both be removed
// 'storybook-addon-next',
// 'storybook-addon-next-router',
],
};
export default config;Migrating from Webpack
Storybook provides a migration tool for migrating to this framework from the Webpack-based Next.js framework, @storybook/nextjs. To migrate, run this command:
npx storybook automigrate nextjs-to-nextjs-viteThis automigration tool performs the following actions:
- Updates
package.jsonfiles to replace@storybook/nextjswith@storybook/nextjs-vite - Updates
.storybook/main.js|tsto change the framework property - Scans and updates import statements in your story files and configuration files
If your project has custom Webpack configurations in .storybook/main.js|ts (via webpackFinal), you'll need to migrate those to Vite configuration (via viteFinal) after running this automigration. See the Vite builder documentation for more information.
Run the Setup Wizard
If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing controls.

If you skipped the wizard, you can always run it again by adding the ?path=/onboarding query parameter to the URL of your Storybook instance, provided that the example stories are still available.
Next.js's Image component
This framework allows you to use Next.js's next/image with no configuration.
Local images
Local images are supported.
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 are also supported.
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>
</>
);
}Next.js font optimization
next/font 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 property. The path is relative to the directory where the font loader function is called.
If the following component defines your localFont like this:
import localFont from 'next/font/local';
const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' });The Vite-based framework automatically handles font path mapping, so you don't need to configure staticDirs for fonts like you would with the Webpack-based framework.
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
- fallback option
- adjustFontFallback option
- preload option gets ignored. Storybook handles Font loading its own way.
- display option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly.
Mocking fonts during testing
Occasionally fetching fonts from Google may fail as part of your Storybook build step. It is highly recommended to mock these requests, as those failures can cause your pipeline to fail as well. Next.js supports mocking fonts via a JavaScript module located where the env var NEXT_FONT_GOOGLE_MOCKED_RESPONSES references.
For example, using GitHub Actions:
- uses: chromaui/action@latest
env:
#๐ the location of mocked fonts to use
NEXT_FONT_GOOGLE_MOCKED_RESPONSES: ${{ github.workspace }}/mocked-google-fonts.js
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}Your mocked fonts will look something like this:
//๐ Mocked responses of google fonts with the URL as the key
module.exports = {
'https://fonts.googleapis.com/css?family=Inter:wght@400;500;600;800&display=block': `
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: block;
src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhiJ-Ek-_EeAmM.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* more font declarations go here */
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: block;
src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}`,
};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 Actions panel.
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. The framework will shallowly merge whatever you put here into the router.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import RouterBasedComponent from './RouterBasedComponent';
const meta = {
component: RouterBasedComponent,
} satisfies Meta<typeof RouterBasedComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
// Interact with the links to see the route change events in the Actions panel.
export const Example: Story = {
parameters: {
nextjs: {
router: {
pathname: '/profile/[id]',
asPath: '/profile/1',
query: {
id: '1',
},
},
},
},
};These overrides can also be applied to all stories for a component or all stories in your project. Standard parameter inheritance rules apply.
Default router
The default values on the stubbed router are as follows (see globals for more details on how globals work).
// Default router
const defaultRouter = {
// The locale should be configured globally: https://storybook.js.org/docs/essentials/toolbars-and-globals#globals
locale: globals?.locale,
asPath: '/',
basePath: '/',
isFallback: false,
isLocaleDomain: false,
isReady: true,
isPreview: false,
route: '/',
pathname: '/',
query: {},
};Additionally, the router object contains all of the original methods (such as push(), replace(), etc.) as mock functions that can be manipulated and asserted on using regular mock APIs.
To override these defaults, you can use parameters and beforeEach:
import type { Preview } from '@storybook/nextjs-vite';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from "@storybook/nextjs-vite/router.mock";
const preview: Preview = {
parameters: {
nextjs: {
// ๐ Override the default router properties
router: {
basePath: '/app/',
},
},
},
async beforeEach() {
// ๐ Manipulate the default router method mocks
getRouter().push.mockImplementation(() => {
/* ... */
});
},
};Next.js navigation
Please note that next/navigation can only be used in components/pages in the app directory.
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 for that component's stories:
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import NavigationBasedComponent from './NavigationBasedComponent';
const meta = {
component: NavigationBasedComponent,
parameters: {
nextjs: {
appDirectory: true, // ๐ Set this
},
},
} satisfies Meta<typeof NavigationBasedComponent>;
export default meta;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 .storybook/preview.js|ts file to apply it to all stories.
// Replace your-framework with nextjs or nextjs-vite
import type { Preview } from '@storybook/your-framework';
const preview: Preview = {
// ...
parameters: {
// ...
nextjs: {
appDirectory: true,
},
},
};
export default preview;Overriding defaults
Per-story overrides can be done by adding a nextjs.navigation property onto the story parameters. The framework will shallowly merge whatever you put here into the router.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import NavigationBasedComponent from './NavigationBasedComponent';
const meta = {
component: NavigationBasedComponent,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof NavigationBasedComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
// Interact with the links to see the route change events in the Actions panel.
export const Example: Story = {
parameters: {
nextjs: {
navigation: {
pathname: '/profile',
query: {
user: '1',
},
},
},
},
};These overrides can also be applied to all stories for a component or all stories in your project. Standard parameter inheritance rules apply.
useSelectedLayoutSegment, useSelectedLayoutSegments, and useParams hooks
The useSelectedLayoutSegment, useSelectedLayoutSegments, and useParams hooks are supported in Storybook. You have to set the nextjs.navigation.segments parameter to return the segments or the params you want to use.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import NavigationBasedComponent from './NavigationBasedComponent';
const meta = {
component: NavigationBasedComponent,
parameters: {
nextjs: {
appDirectory: true,
navigation: {
segments: ['dashboard', 'analytics'],
},
},
},
} satisfies Meta<typeof NavigationBasedComponent>;
export default meta;With the above configuration, the component rendered in the stories would receive the following values from the hooks:
import { useSelectedLayoutSegment, useSelectedLayoutSegments, useParams } from 'next/navigation';
export default function NavigationBasedComponent() {
const segment = useSelectedLayoutSegment(); // dashboard
const segments = useSelectedLayoutSegments(); // ["dashboard", "analytics"]
const params = useParams(); // {}
// ...
}To use useParams, you have to use a segments array where each element is an array containing two strings. The first string is the param key and the second string is the param value.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import NavigationBasedComponent from './NavigationBasedComponent';
const meta = {
component: NavigationBasedComponent,
parameters: {
nextjs: {
appDirectory: true,
navigation: {
segments: [
['slug', 'hello'],
['framework', 'nextjs'],
],
},
},
},
} satisfies Meta<typeof NavigationBasedComponent>;
export default meta;With the above configuration, the component rendered in the stories would receive the following values from the hooks:
import { useSelectedLayoutSegment, useSelectedLayoutSegments, useParams } from 'next/navigation';
export default function ParamsBasedComponent() {
const segment = useSelectedLayoutSegment(); // hello
const segments = useSelectedLayoutSegments(); // ["hello", "nextjs"]
const params = useParams(); // { slug: "hello", framework: "nextjs" }
...
}These overrides can also be applied to a single story or all stories in your project. Standard parameter inheritance rules apply.
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:
// Default navigation context
const defaultNavigationContext = {
pathname: '/',
query: {},
};Additionally, the router object contains all of the original methods (such as push(), replace(), etc.) as mock functions that can be manipulated and asserted on using regular mock APIs.
To override these defaults, you can use parameters and beforeEach:
import type { Preview } from '@storybook/nextjs-vite';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from '@storybook/nextjs-vite/navigation.mock';
const preview: Preview = {
parameters: {
nextjs: {
// ๐ Override the default navigation properties
navigation: {
pathname: '/app/',
},
},
},
async beforeEach() {
// ๐ Manipulate the default navigation method mocks
getRouter().push.mockImplementation(() => {
/* ... */
});
},
};Next.js Head
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 are supported without any additional configuration as well. Just import them into .storybook/preview.js|ts
import '../styles/globals.scss';This will automatically include any of your custom Sass configurations in your next.config.js file.
import * as path from 'path';
export default {
// Any options here are included in Sass compilation for your stories
sassOptions: {
includePaths: [path.join(process.cwd(), 'styles')],
},
};CSS/Sass/Scss Modules
CSS modules work as expected.
// This import will work in Storybook
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>
);
}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 Tailwind! (See Next.js' example)
Absolute imports
Absolute imports from the root directory are supported.
// 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 />
</>
);
}Also OK for global styles in .storybook/preview.js|ts!
import 'styles/globals.scss';
// ...Absolute imports cannot be mocked in stories/tests. See the Mocking modules section for more information.
Module aliases
Module aliases are also supported.
// 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 />
</>
);
}Subpath imports
As an alternative to module aliases, you can use subpath imports to import modules. This follows Node package standards and has benefits when mocking modules.
To configure subpath imports, you define the imports property in your project's package.json file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project:
{
"imports": {
"#*": ["./*", "./*.ts", "./*.tsx"]
}
}Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration.
Which can then be used like this:
import Button from '#components/button';
import styles from '#styles/HomePage.module.css';
export default function HomePage() {
return (
<>
<h1 className={styles.title}>Hello World</h1>
<Button />
</>
);
}Mocking modules
Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control and assert their behavior.
Built-in mocked modules
This framework provides mocks for many of Next.js' internal modules:
@storybook/nextjs-vite/cache.mock@storybook/nextjs-vite/headers.mock@storybook/nextjs-vite/navigation.mock@storybook/nextjs-vite/router.mock
Mocking other modules
To mock other modules, use automocking or one of the alternative methods documented in the mocking modules guide.
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:
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:
// Runtime config
{
"serverRuntimeConfig": {},
"publicRuntimeConfig": {
"staticFolder": "/static"
}
}Custom Vite configuration
You can customize the Vite configuration used by Storybook in your .storybook/main.js|ts file. By default, Storybook's configuration extends the Vite configuration used by your project, but you can configure it to not do so.
Not all Vite modifications are copy/paste-able between next.config.js and .storybook/main.js|ts. It is recommended to do your research on how to properly make your modification to Storybook's Vite config and on how Vite works.
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/*"]
}
}
}React Server Components (RSC)
(โ ๏ธ Experimental)
If your app uses React Server Components (RSC), Storybook can render them in stories in the browser.
To enable this set the experimentalRSC feature flag in your .storybook/main.js|ts config:
// Replace your-framework with nextjs or nextjs-vite
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
// ...
features: {
experimentalRSC: true,
},
};
export default config;Setting this flag automatically wraps your story in a Suspense wrapper, which is able to render asynchronous components in NextJS's version of React.
If this wrapper causes problems in any of your existing stories, you can selectively disable it using the react.rsc parameter at the global/component/story level:
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import MyServerComponent from './MyServerComponent';
const meta = {
component: MyServerComponent,
parameters: {
react: { rsc: false },
},
} satisfies Meta<typeof MyServerComponent>;
export default meta;Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To work around this, you'll need to mock out your data access layer using Vite aliases or an addon like storybook-addon-module-mock.
If your server components access data via the network, we recommend using the MSW Storybook Addon to mock network requests.
In the future we will provide better mocking support in Storybook and support for Server Actions.
FAQ
Stories for pages/components which fetch data
Next.js pages can fetch data directly within server components in the app directory, which often include module imports that only run in a node environment. This does not (currently) work within Storybook, because if you import from a Next.js page file containing those node module imports in your stories, your Storybook's Vite build 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 pure component in your stories. Or, if that's not feasible for some reason, you can configure Vite to handle those modules in your Storybook's viteFinal configuration.
Before
async function getData() {
const res = await fetch(...);
// ...
}
// Using this component in your stories will break the Storybook build
export default async function Page() {
const data = await getData();
return // ...
}After
// Use this component in your stories
import MyPage from './components/MyPage';
async function getData() {
const res = await fetch(...);
// ...
}
export default async function Page() {
const data = await getData();
return <MyPage {...data} />;
}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 would import 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:
// Image import object
{
"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 only the asset path.
See local images for more detail on how Next.js treats static image imports.
Error: You are importing avif images, but you don't have sharp installed. You have to install sharp in order to use image optimization features in Next.js.
sharp is a dependency of Next.js's image optimization feature. If you see this error, you need to install sharp in your project.
npm install sharpyarn add sharppnpm add sharpYou can refer to the Install sharp to Use Built-In Image Optimization in the Next.js documentation for more information.
Should I use the Vite or Webpack version?
We recommend using @storybook/nextjs-vite (this framework) for most projects because it offers:
- Faster builds and development server startup
- Better support for modern testing features like the Vitest addon
- Simpler configuration without Babel
- Better developer experience with faster HMR
However, if your project has custom Webpack configurations that are incompatible with Vite, or you need specific Webpack features, you should use @storybook/nextjs (Webpack 5) instead.
API
Modules
The @storybook/nextjs-vite package exports several modules that enable you to mock Next.js's internal behavior.
@storybook/nextjs-vite/cache.mock
Type: typeof import('next/cache')
This module exports mocked implementations of the next/cache module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's play function.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import { expect } from 'storybook/test';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { revalidatePath } from '@storybook/your-framework/cache.mock';
import MyForm from './my-form';
const meta = {
component: MyForm,
} satisfies Meta<typeof MyForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Submitted: Story = {
async play({ canvas, userEvent }) {
const submitButton = canvas.getByRole('button', { name: /submit/i });
await userEvent.click(saveButton);
// ๐ Use any mock assertions on the function
await expect(revalidatePath).toHaveBeenCalledWith('/');
},
};@storybook/nextjs-vite/headers.mock
Type: cookies, headers and draftMode from Next.js
This module exports writable mocked implementations of the next/headers module's exports. You can use it to set up cookies or headers that are read in your story, and to later assert that they have been called.
Next.js's default headers() export is read-only, but this module exposes methods allowing you to write to the headers:
headers().append(name: string, value: string): Appends the value to the header if it exists already.headers().delete(name: string): Deletes the headerheaders().set(name: string, value: string): Sets the header to the value provided.
For cookies, you can use the existing API to write them. E.g., cookies().set('firstName', 'Jane').
Because headers(), cookies() and their sub-functions are all mocks you can use any mock utilities in your stories, like headers().getAll.mock.calls.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import { expect } from 'storybook/test';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { cookies, headers } from '@storybook/your-framework/headers.mock';
import MyForm from './my-form';
const meta = {
component: MyForm,
} satisfies Meta<typeof MyForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedInEurope: Story = {
async beforeEach() {
// ๐ Set mock cookies and headers ahead of rendering
cookies().set('username', 'Sol');
headers().set('timezone', 'Central European Summer Time');
},
async play() {
// ๐ Assert that your component called the mocks
await expect(cookies().get).toHaveBeenCalledOnce();
await expect(cookies().get).toHaveBeenCalledWith('username');
await expect(headers().get).toHaveBeenCalledOnce();
await expect(cookies().get).toHaveBeenCalledWith('timezone');
},
};@storybook/nextjs-vite/navigation.mock
Type: typeof import('next/navigation') & getRouter: () => ReturnType<typeof import('next/navigation')['useRouter']>
This module exports mocked implementations of the next/navigation module's exports. It also exports a getRouter function that returns a mocked version of Next.js's router object from useRouter, allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's play function.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import { expect } from 'storybook/test';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { redirect, getRouter } from '@storybook/your-framework/navigation.mock';
import MyForm from './my-form';
const meta = {
component: MyForm,
parameters: {
nextjs: {
// ๐ As in the Next.js application, next/navigation only works using App Router
appDirectory: true,
},
},
} satisfies Meta<typeof MyForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Unauthenticated: Story = {
async play() {
// ๐ Assert that your component called redirect()
await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
},
};
export const GoBack: Story = {
async play({ canvas, userEvent }) {
const backBtn = await canvas.findByText('Go back');
await userEvent.click(backBtn);
// ๐ Assert that your component called back()
await expect(getRouter().back).toHaveBeenCalled();
},
};@storybook/nextjs-vite/router.mock
Type: typeof import('next/router') & getRouter: () => ReturnType<typeof import('next/router')['useRouter']>
This module exports mocked implementations of the next/router module's exports. It also exports a getRouter function that returns a mocked version of Next.js's router object from useRouter, allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's play function.
// Replace your-framework with nextjs or nextjs-vite
import type { Meta, StoryObj } from '@storybook/your-framework';
import { expect } from 'storybook/test';
// ๐ Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from '@storybook/your-framework/router.mock';
import MyForm from './my-form';
const meta = {
component: MyForm,
} satisfies Meta<typeof MyForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const GoBack: Story = {
async play({ canvas, userEvent }) {
const backBtn = await canvas.findByText('Go back');
await userEvent.click(backBtn);
// ๐ Assert that your component called back()
await expect(getRouter().back).toHaveBeenCalled();
},
};Options
You can pass an options object for additional configuration if needed:
import * as path from 'path';
export default {
// ...
framework: {
name: '@storybook/nextjs-vite',
options: {
image: {
loading: 'eager',
},
nextConfigPath: path.resolve(process.cwd(), 'next.config.js'),
},
},
};The available options are:
builder
Type: Record<string, any>
Configure options for the framework's builder. For Next.js with Vite, available options can be found in the Vite builder docs.
image
Type: object
Props to pass to every instance of next/image. See next/image docs for more details.
nextConfigPath
Type: string
The absolute path to the next.config.js file. This is necessary if you have a custom next.config.js file that is not in the root directory of your project.
Parameters
This framework contributes the following parameters to Storybook, under the nextjs namespace:
appDirectory
Type: boolean
Default: false
If your story imports components that use next/navigation, you need to set the parameter nextjs.appDirectory to true. Because this is a parameter, you can apply it to a single story, all stories for a component, or every story in your Storybook. See Next.js Navigation for more details.
navigation
Type:
{
asPath?: string;
pathname?: string;
query?: Record<string, string>;
segments?: (string | [string, string])[];
}Default value:
{
segments: [];
}The router object that is passed to the next/navigation context. See Next.js's navigation docs for more details.
router
Type:
{
asPath?: string;
pathname?: string;
query?: Record<string, string>;
}The router object that is passed to the next/router context. See Next.js's router docs for more details.
