Docs
Storybook Docs

适用于 Next.js 的 Storybook

Storybook for Next.js 是一个 framework,它可以轻松地为 Next.js 应用单独开发和测试 UI 组件。它包括:

¥Storybook for Next.js is a framework that makes it easy to develop and test UI components in isolation for Next.js applications. It includes:

  • 🔀 路由

    ¥🔀 Routing

  • 🖼 图片优化

    ¥🖼 Image optimization

  • ⤵️ 绝对导入

    ¥⤵️ Absolute imports

  • 🎨 样式

    ¥🎨 Styling

  • 🎛 Webpack 和 Babel 配置

    ¥🎛 Webpack & Babel config

  • 💫 还有更多!

    ¥💫 and more!

要求

¥Requirements

  • Next.js ≥ 13.5

  • Storybook ≥ 7.0

入门

¥Getting started

在没有 Storybook 的项目中

¥In a project without Storybook

在 Next.js 项目的根目录中运行此命令后,按照提示进行操作:

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

npx storybook@latest init

有关开始使用 Storybook 的更多信息。

¥More on getting started with Storybook.

在带有 Storybook 的项目中

¥In a project with Storybook

此框架旨在与 Storybook 7+ 配合使用。如果你尚未使用 v7,请使用以下命令升级:

¥This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command:

npx storybook@latest upgrade

自动迁移

¥Automatic migration

运行上面的 upgrade 命令时,你应该会收到一个提示,要求你迁移到 @storybook/nextjs,它应该为你处理所有事情。如果自动迁移不适用于你的项目,请参考下面的手动迁移。

¥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

首先,安装框架:

¥First, install the framework:

npm install --save-dev @storybook/nextjs

然后,更新你的 .storybook/main.js|ts 以更改框架属性:

¥Then, update your .storybook/main.js|ts to change the framework property:

.storybook/main.ts
import { StorybookConfig } from '@storybook/nextjs';
 
const config: StorybookConfig = {
  // ...
  // framework: '@storybook/react-webpack5', 👈 Remove this
  framework: '@storybook/nextjs', // 👈 Add this
};
 
export default config;

最后,如果你使用 Storybook 插件与 Next.js 集成,则使用此框架时不再需要这些插件,可以将其删除:

¥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:

.storybook/main.ts
import { StorybookConfig } from '@storybook/nextjs';
 
const config: StorybookConfig = {
  // ...
  addons: [
    // ...
    // 👇 These can both be removed
    // 'storybook-addon-next',
    // 'storybook-addon-next-router',
  ],
};
 
export default config;

使用 Vite

¥With Vite

(⚠️实验性)

¥(⚠️ Experimental)

你可以使用我们刚刚出炉的实验性 @storybook/experimental-nextjs-vite 框架,该框架基于 Vite,无需 Webpack 和 Babel。它支持此处记录的所有功能。

¥You can use our freshly baked, experimental @storybook/experimental-nextjs-vite framework, which is based on Vite and removes the need for Webpack and Babel. It supports all of the features documented here.

将 Next.js 框架与 Vite 一起使用需要 Next.js 14.1.0 或更高版本。

¥Using the Next.js framework with Vite requires Next.js 14.1.0 or later.

npm install --save-dev @storybook/experimental-nextjs-vite

然后,更新你的 .storybook/main.js|ts 以更改框架属性:

¥Then, update your .storybook/main.js|ts to change the framework property:

.storybook/main.ts
import { StorybookConfig } from '@storybook/experimental-nextjs-vite';
 
const config: StorybookConfig = {
  // ...
  // framework: '@storybook/react-webpack5', 👈 Remove this
  framework: '@storybook/experimental-nextjs-vite', // 👈 Add this
};
 
export default config;

如果你的 Storybook 配置在 webpackFinal 中包含自定义 Webpack 操作,你可能需要在 viteFinal 中创建等效操作。

¥If your Storybook configuration contains custom Webpack operations in webpackFinal, you will likely need to create equivalents in viteFinal.

最后,如果你使用 Storybook 插件与 Next.js 集成,则使用此框架时不再需要这些插件,可以将其删除:

¥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:

.storybook/main.ts
import { StorybookConfig } from '@storybook/experimental-nextjs-vite';
 
const config: StorybookConfig = {
  // ...
  addons: [
    // ...
    // 👇 These can both be removed
    // 'storybook-addon-next',
    // 'storybook-addon-next-router',
  ],
};
 
export default config;

运行安装向导

¥Run the Setup Wizard

如果一切顺利,你应该会看到一个安装向导,它将帮助你开始使用 Storybook,向你介绍主要概念和功能,包括 UI 的组织方式、如何编写你的第一个故事以及如何利用 controls 测试组件对各种输入的响应。

¥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.

Storybook onboarding

如果你跳过了向导,你可以随时通过将 ?path=/onboarding 查询参数添加到 Storybook 实例的 URL 来再次运行它,前提是示例故事仍然可用。

¥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 的图片组件

¥Next.js's Image component

此框架允许你无需配置即可使用 Next.js 的 next/image

¥This framework allows you to use Next.js's next/image with no configuration.

本地图片

¥Local images

本地图片 受支持。

¥Local images are supported.

// index.jsx
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.

// index.jsx
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 字体优化

¥Next.js font optimization

next/font 在 Storybook 中得到部分支持。支持 next/font/googlenext/font/local 包。

¥next/font is partially supported in Storybook. The packages next/font/google and next/font/local are supported.

next/font/google

你不需要做任何事情。next/font/google 开箱即用。

¥You don't have to do anything. next/font/google is supported out of the box.

next/font/local

对于本地字体,你必须定义 src 属性。路径相对于调用字体加载器函数的目录。

¥For local fonts you have to define the src property. The path is relative to the directory where the font loader function is called.

如果以下组件像这样定义你的 localFont:

¥If the following component defines your localFont like this:

// src/components/MyComponent.js
import localFont from 'next/font/local';
 
const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' });

staticDir 映射

¥staticDir mapping

如果你使用的是 @storybook/experimental-nextjs-vite 而不是 @storybook/nextjs,则可以放心地跳过此部分。基于 Vite 的框架会自动处理映射。

¥You can safely skip this section if you are using @storybook/experimental-nextjs-vite instead of @storybook/nextjs. The Vite-based framework takes care of the mapping automatically.

你必须通过 staticDirs 配置 告诉 Storybook fonts 目录的位置。from 值与 .storybook 目录相关。to 值与 Storybook 的执行上下文相关。很可能它是项目的根。

¥You have to tell Storybook where the fonts directory is located, via the staticDirs configuration. 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.

.storybook/main.ts
import { StorybookConfig } from '@storybook/nextjs';
 
const config: StorybookConfig = {
  // ...
  staticDirs: [
    {
      from: '../src/components/fonts',
      to: 'src/components/fonts',
    },
  ],
};
 
export default config;

不支持 next/font 的功能

¥Not supported features of next/font

以下功能尚不支持。未来可能会计划支持这些功能:

¥The following features are not supported (yet). Support for these features might be planned for the future:

测试期间模拟字体

¥Mocking fonts during testing

偶尔,从 Google 获取字体可能会作为 Storybook 构建步骤的一部分失败。强烈建议模拟这些请求,因为这些失败也会导致你的管道失败。Next.js 支持模拟字体 通过位于环境变量 NEXT_FONT_GOOGLE_MOCKED_RESPONSES 引用的 JavaScript 模块。

¥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.

例如,使用 GitHub Actions

¥For example, using GitHub Actions:

# .github/workflows/ci.yml
- uses: chromaui/action@v1
  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-google-fonts.js
//👇 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 路由

¥Next.js routing

Next.js 的路由 会自动为你存根,以便在与路由交互时,如果你拥有 Storybook 操作插件,则其所有交互都会自动记录到“操作”面板中。

¥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 if you have the Storybook actions addon.

你只应在 pages 目录中使用 next/router。在 app 目录中,必须使用 next/navigation

¥You should only use next/router in the pages directory. In the app directory, it is necessary to use next/navigation.

覆盖默认值

¥Overriding defaults

可以通过将 nextjs.router 属性添加到故事 参数 上来完成每个故事的覆盖。框架将浅层合并你在此处放入路由的任何内容。

¥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.

RouterBasedComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import RouterBasedComponent from './RouterBasedComponent';
 
const meta: Meta<typeof RouterBasedComponent> = {
  component: RouterBasedComponent,
};
export default meta;
 
type Story = StoryObj<typeof RouterBasedComponent>;
 
// If you have the actions addon,
// you can interact with the links and see the route change events there
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

存根路由上的默认值如下(有关全局变量如何工作的更多详细信息,请参阅 globals)。

¥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.nodejs.cn/docs/essentials/toolbars-and-globals#globals
  locale: globals?.locale,
  asPath: '/',
  basePath: '/',
  isFallback: false,
  isLocaleDomain: false,
  isReady: true,
  isPreview: false,
  route: '/',
  pathname: '/',
  query: {},
};

此外,router 对象 包含所有原始方法(例如 push()replace() 等)作为模拟函数,可以使用 常规模拟 API 对其进行操作和断言。

¥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.

要覆盖这些默认值,你可以使用 参数beforeEach

¥To override these defaults, you can use parameters and beforeEach:

// .storybook/preview.ts
import { Preview } from '@storybook/react';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/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 导航

¥Next.js navigation

请注意,next/navigation 只能在 app 目录中的组件/页面中使用。

¥Please note that next/navigation can only be used in components/pages in the app directory.

nextjs.appDirectory 设置为 true

¥Set nextjs.appDirectory to true

如果你的故事导入了使用 next/navigation 的组件,则需要为该组件的故事将参数 nextjs.appDirectory 设置为 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:

NavigationBasedComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import NavigationBasedComponent from './NavigationBasedComponent';
 
const meta: Meta<typeof NavigationBasedComponent> = {
  component: NavigationBasedComponent,
  parameters: {
    nextjs: {
      appDirectory: true, // 👈 Set this
    },
  },
};
export default meta;

如果你的 Next.js 项目对每个页面都使用 app 目录(换句话说,它没有 pages 目录),你可以在 .storybook/preview.js|ts 文件中将参数 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 .storybook/preview.js|ts file to apply it to all stories.

.storybook/preview.ts
import { Preview } from '@storybook/react';
 
const preview: Preview = {
  // ...
  parameters: {
    // ...
    nextjs: {
      appDirectory: true,
    },
  },
};
 
export default preview;

覆盖默认值

¥Overriding defaults

可以通过将 nextjs.navigation 属性添加到故事 参数 上来完成每个故事的覆盖。框架将浅层合并你在此处放入路由的任何内容。

¥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.

NavigationBasedComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import NavigationBasedComponent from './NavigationBasedComponent';
 
const meta: Meta<typeof NavigationBasedComponent> = {
  component: NavigationBasedComponent,
  parameters: {
    nextjs: {
      appDirectory: true,
    },
  },
};
export default meta;
 
type Story = StoryObj<typeof NavigationBasedComponent>;
 
// If you have the actions addon,
// you can interact with the links and see the route change events there
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.

useSelectedLayoutSegmentuseSelectedLayoutSegmentsuseParams 钩子

¥useSelectedLayoutSegment, useSelectedLayoutSegments, and useParams hooks

Storybook 支持 useSelectedLayoutSegmentuseSelectedLayoutSegmentsuseParams 钩子。你必须设置 nextjs.navigation.segments 参数以返回要使用的段或参数。

¥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.

NavigationBasedComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import NavigationBasedComponent from './NavigationBasedComponent';
 
const meta: Meta<typeof NavigationBasedComponent> = {
  component: NavigationBasedComponent,
  parameters: {
    nextjs: {
      appDirectory: true,
      navigation: {
        segments: ['dashboard', 'analytics'],
      },
    },
  },
};
export default meta;

使用上述配置,故事中渲染的组件将从钩子中接收以下值:

¥With the above configuration, the component rendered in the stories would receive the following values from the hooks:

// NavigationBasedComponent.js
import { useSelectedLayoutSegment, useSelectedLayoutSegments, useParams } from 'next/navigation';
 
export default function NavigationBasedComponent() {
  const segment = useSelectedLayoutSegment(); // dashboard
  const segments = useSelectedLayoutSegments(); // ["dashboard", "analytics"]
  const params = useParams(); // {}
  // ...
}

要使用 useParams,你必须使用一个段数组,其中每个元素都是包含两个字符串的数组。第一个字符串是 param 键,第二个字符串是 param 值。

¥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.

NavigationBasedComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import NavigationBasedComponent from './NavigationBasedComponent';
 
const meta: Meta<typeof NavigationBasedComponent> = {
  component: NavigationBasedComponent,
  parameters: {
    nextjs: {
      appDirectory: true,
      navigation: {
        segments: [
          ['slug', 'hello'],
          ['framework', 'nextjs'],
        ],
      },
    },
  },
};
export default meta;

使用上述配置,故事中渲染的组件将从钩子中接收以下值:

¥With the above configuration, the component rendered in the stories would receive the following values from the hooks:

// ParamsBasedComponent.js
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.

如果未设置,nextjs.navigation.segments 的默认值为 []

¥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: {},
};

此外,router 对象 包含所有原始方法(例如 push()replace() 等)作为模拟函数,可以使用 常规模拟 API 对其进行操作和断言。

¥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.

要覆盖这些默认值,你可以使用 参数beforeEach

¥To override these defaults, you can use parameters and beforeEach:

// .storybook/preview.ts
import { Preview } from '@storybook/react';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/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 开箱即用。你可以在故事中使用它,就像在 Next.js 应用中一样。请记住,Head children 放置在 Storybook 用于渲染你的故事的 iframe 的 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

全局 Sass/Scss 样式表 也无需任何额外配置即可得到支持。只需将它们导入 .storybook/preview.js|ts

¥Global Sass/Scss stylesheets are supported without any additional configuration as well. Just import them into .storybook/preview.js|ts

// .storybook/preview.js|ts
import '../styles/globals.scss';

这将自动将你的任何 自定义 Sass 配置 包含在你的 next.config.js 文件中。

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

// 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 模块

¥CSS/Sass/Scss Modules

CSS 模块 按预期工作。

¥CSS modules work as expected.

// src/components/Button.jsx
// 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>
  );
}

样式化的 JSX

¥Styled JSX

Next.js 的内置 CSS-in-JS 解决方案是 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.

// src/components/HelloWorld.jsx
// This will work in Storybook
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;

你也可以使用自己的 babel 配置。这是一个如何自定义 styled-jsx 的示例。

¥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"]
        }
      }
    ]
  ]
}

PostCSS

Next.js 让你 自定义 PostCSS 配置。因此,该框架将自动为你处理 PostCSS 配置。

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

这允许零配置 Tailwind 等很酷的东西!(参见 Next.js 示例)

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

绝对导入

¥Absolute imports

支持来自根目录的 绝对导入

¥Absolute imports from the root directory are supported.

// index.jsx
// 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 />
    </>
  );
}

.storybook/preview.js|ts 中的全局样式也适用!

¥Also OK for global styles in .storybook/preview.js|ts!

// .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.

// index.jsx
// 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

作为 模块别名 的替代方案,你可以使用 子路径导入 导入模块。这遵循 Node 包标准,并且在 模拟模块 时有好处。

¥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.

要配置子路径导入,请在项目的 package.json 文件中定义 imports 属性。此属性将子路径映射到实际文件路径。以下示例为项目中的所有模块配置子路径导入:

¥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:

// package.json
{
  "imports": {
    "#*": ["./*", "./*.ts", "./*.tsx"]
  }
}

由于子路径导入替换了模块别名,因此你可以从 TypeScript 配置中删除路径别名。

¥Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration.

然后可以像这样使用:

¥Which can then be used like this:

// index.jsx
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

组件通常依赖于导入到组件文件中的模块。这些可以来自外部包或项目内部。在 Storybook 中渲染这些组件或测试它们时,你可能希望 模拟这些模块 来控制和断言它们的行为。

¥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

此框架为许多 Next.js 内部模块提供了模拟:

¥This framework provides mocks for many of Next.js' internal modules:

  1. @storybook/nextjs/cache.mock
  2. @storybook/nextjs/headers.mock
  3. @storybook/nextjs/navigation.mock
  4. @storybook/nextjs/router.mock

模拟其他模块

¥Mocking other modules

如何在 Storybook 中模拟其他模块取决于如何将模块导入组件。

¥How you mock other modules in Storybook depends on how you import the module into your component.

无论使用哪种方法,第一步都是 创建模拟文件。以下是名为 session 的模块的模拟文件的示例:

¥With either approach, the first step is to create a mock file. Here's an example of a mock file for a module named session:

lib/session.mock.ts
import { fn } from '@storybook/test';
import * as actual from './session';
 
export * from './session';
export const getUserFromSession = fn(actual.getUserFromSession).mockName('getUserFromSession');

使用子路径导入

¥With subpath imports

如果你使用的是 子路径导入,则可以调整配置以应用 conditions,以便在 Storybook 中使用模拟模块。以下示例为四个内部模块配置子路径导入,然后在 Storybook 中模拟:

¥If you're using subpath imports, you can adjust your configuration to apply conditions so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook:

package.json
{
  "imports": {
    "#api": {
      // storybook condition applies to Storybook
      "storybook": "./api.mock.ts",
      "default": "./api.ts"
    },
    "#app/actions": {
      "storybook": "./app/actions.mock.ts",
      "default": "./app/actions.ts"
    },
    "#lib/session": {
      "storybook": "./lib/session.mock.ts",
      "default": "./lib/session.ts"
    },
    "#lib/db": {
      // test condition applies to test environments *and* Storybook
      "test": "./lib/db.mock.ts",
      "default": "./lib/db.ts"
    },
    "#*": ["./*", "./*.ts", "./*.tsx"]
  }
}

每个子路径都必须以 # 开头,以将其与常规模块路径区分开来。#* 条目是一个将所有子路径映射到根目录的万能条目。

¥Each subpath must begin with #, to differentiate it from a regular module path. The #* entry is a catch-all that maps all subpaths to the root directory.

使用模块别名

¥With module aliases

如果你使用的是 模块别名,则可以将 Webpack 别名添加到 Storybook 配置中以指向模拟文件。

¥If you're using module aliases, you can add a Webpack alias to your Storybook configuration to point to the mock file.

.storybook/main.ts
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  viteFinal: async (config) => {
    if (config.resolve) {
      config.resolve.alias = {
        ...config.resolve?.alias,
        // 👇 External module
        lodash: require.resolve('./lodash.mock'),
        // 👇 Internal modules
        '@/api': path.resolve(__dirname, './api.mock.ts'),
        '@/app/actions': path.resolve(__dirname, './app/actions.mock.ts'),
        '@/lib/session': path.resolve(__dirname, './lib/session.mock.ts'),
        '@/lib/db': path.resolve(__dirname, './lib/db.mock.ts'),
      };
    }
 
    return config;
  },
};
 
export default config;

运行时配置

¥Runtime config

Next.js 允许 运行时配置,它允许你导入方便的 getConfig 函数以在运行时获取 next.config.js 文件中定义的某些配置。

¥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.

在具有此框架的 Storybook 上下文中,你可以期待 Next.js 的 运行时配置 功能正常工作。

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

注意,由于 Storybook 不会在服务器渲染你的组件,因此你的组件只能看到它们通常在客户端看到的内容(即它们看不到 serverRuntimeConfig 但会看到 publicRuntimeConfig)。

¥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).

例如,考虑以下 Next.js 配置:

¥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',
  },
};

在 Storybook 中调用 getConfig 时将返回以下对象:

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

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

自定义 Webpack 配置

¥Custom Webpack config

如果你使用的是 @storybook/experimental-nextjs-vite 而不是 @storybook/nextjs,则可以放心地跳过此部分。基于 Vite 的 Next.js 框架不支持 Webpack 设置。

¥You can safely skip this section if you are using @storybook/experimental-nextjs-vite instead of @storybook/nextjs. The Vite-based Next.js framework does not support Webpack settings.

Next.js 附带了很多开箱即用的东西,比如 Sass 支持,但有时你会添加 对 Next.js 的自定义 Webpack 配置修改。此框架负责处理你想要添加的大多数 Webpack 修改。如果 Next.js 支持开箱即用的功能,那么该功能将在 Storybook 中开箱即用。如果 Next.js 不支持开箱即用的东西,但使其易于配置,那么这个框架将为 Storybook 做同样的事情。

¥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.

Storybook 所需的任何 Webpack 修改都应在 .storybook/main.js|ts 中进行。

¥Any Webpack modifications desired for Storybook should be made in .storybook/main.js|ts.

注意:并非所有 Webpack 修改都可以在 next.config.js.storybook/main.js|ts 之间复制/粘贴。建议你研究如何正确修改 Storybook 的 Webpack 配置以及 Webpack 工作原理

¥Note: Not all Webpack 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 Webpack config and on how Webpack works.

下面是如何使用此框架向 Storybook 添加 SVGR 支持的示例。

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

.storybook/main.ts
import { StorybookConfig } from '@storybook/nextjs';
 
const config: StorybookConfig = {
  // ...
  webpackFinal: async (config) => {
    config.module = config.module || {};
    config.module.rules = config.module.rules || [];
 
    // 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'));
    if (imageRule) {
      imageRule['exclude'] = /\.svg$/;
    }
 
    // Configure .svg files to be loaded with @svgr/webpack
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });
 
    return config;
  },
};
 
export default config;

Typescript

Storybook 处理大多数 Typescript 配置,但此框架增加了对 Next.js 对 绝对导入和模块路径别名 的支持。简而言之,它会考虑你的 tsconfig.jsonbaseUrlpaths。因此,像下面这样的 tsconfig.json 可以开箱即用。

¥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.

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

React 服务器组件 (RSC)

¥React Server Components (RSC)

(⚠️实验性)

¥(⚠️ Experimental)

如果你的应用使用 React 服务器组件 (RSC),Storybook 可以在浏览器中的故事中渲染它们。

¥If your app uses React Server Components (RSC), Storybook can render them in stories in the browser.

要启用此功能,请在 .storybook/main.js|ts 配置中设置 experimentalRSC 功能标志:

¥To enable this set the experimentalRSC feature flag in your .storybook/main.js|ts config:

.storybook/main.ts
import { StorybookConfig } from '@storybook/nextjs';
 
const config: StorybookConfig = {
  // ...
  features: {
    experimentalRSC: true,
  },
};
 
export default config;

设置此标志会自动将你的故事封装在 Suspense 封装器中,该封装器能够在 NextJS 版本的 React 中渲染异步组件。

¥Setting this flag automatically wraps your story in a Suspense wrapper, which is able to render asynchronous components in NextJS's version of React.

如果此封装器导致你现有的任何故事出现问题,你可以在全局/组件/故事级别使用 react.rsc 参数 有选择地禁用它:

¥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:

MyServerComponent.stories.ts
import { Meta, StoryObj } from '@storybook/react';
 
import MyServerComponent from './MyServerComponent';
 
const meta: Meta<typeof MyServerComponent> = {
  component: MyServerComponent,
  parameters: {
    react: { rsc: false },
  },
};
export default meta;

请注意,如果你的服务器组件访问服务器端资源(如文件系统或特定于 Node 的库),则将服务器组件封装在 Suspense 中无济于事。要解决此问题,你需要使用 Webpack 别名storybook-addon-module-mock 等插件模拟数据访问层。

¥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 Webpack aliases or an addon like storybook-addon-module-mock.

如果你的服务器组件通过网络访问数据,我们建议使用 MSW Storybook 插件 来模拟网络请求。

¥If your server components access data via the network, we recommend using the MSW Storybook Addon to mock network requests.

将来,我们将在 Storybook 中提供更好的模拟支持和对 服务器操作 的支持。

¥In the future we will provide better mocking support in Storybook and support for Server Actions.

Yarn v2 和 v3 用户的注意事项

¥Notes for Yarn v2 and v3 users

如果你使用 Yarn v2 或 v3,你可能会遇到 Storybook 无法解析 style-loadercss-loader 的问题。例如,你可能会收到如下错误:

¥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'

这是因为这些版本的 Yarn 具有与 Yarn v1.x 不同的包解析规则。如果你遇到这种情况,请直接安装该包。

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

常见问题

¥FAQ

获取数据的页面/组件的故事

¥Stories for pages/components which fetch data

Next.js 页面可以直接在 app 目录中的服务器组件中获取数据,这些组件通常包括仅在节点环境中运行的模块导入。这在 Storybook 中(目前)不起作用,因为如果你从包含故事中这些节点模块导入的 Next.js 页面文件导入,则 Storybook 的 Webpack 将崩溃,因为这些模块不会在浏览器中运行。为了解决这个问题,你可以将页面文件中的组件提取到一个单独的文件中,然后将该纯组件导入到你的故事中。或者,如果由于某种原因不可行,你可以在 Storybook 的 webpackFinal 配置 中使用 填充这些模块

¥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 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 pure 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

// app/my-page/index.jsx
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

// app/my-page/index.jsx
 
// 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

确保你以与在正常开发中使用 next/image 时相同的方式处理图片导入。

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

在使用此框架之前,图片导入将导入图片的原始路径(例如 'static/media/stories/assets/logo.svg')。现在图片导入适用于 "Next.js 方式",这意味着你现在在导入图片时会获得一个对象。例如:

¥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"
}

因此,如果 Storybook 中的某些内容未正确显示图片,请确保你期望从导入中返回对象,而不仅仅是资源路径。

¥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.

有关 Next.js 如何处理静态图片导入的更多详细信息,请参阅 本地图片

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

未找到模块:错误:无法解析 package name

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

如果你使用的是 Yarn v2 或 v3,你可能会得到这个。有关更多详细信息,请参阅 Yarn v2 和 v3 用户的注意事项

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

如果我使用 Vite 构建器怎么办?

¥What if I'm using the Vite builder?

我们引入了实验性的 Vite 构建器支持。只需安装实验框架包 @storybook/experimental-nextjs-vite 并用 @storybook/experimental-nextjs-vite 替换所有 @storybook/nextjs 实例。

¥We have introduced experimental Vite builder support. Just install the experimental framework package @storybook/experimental-nextjs-vite and replace all instances of @storybook/nextjs with @storybook/experimental-nextjs-vite.

错误:你正在导入 avif 图片,但尚未安装 sharp。你必须安装 sharp 才能在 Next.js 中使用图片优化功能。

¥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 是 Next.js 图片优化功能的依赖。如果你看到此错误,则需要在你的项目中安装 sharp

¥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 sharp
yarn add sharp
pnpm add sharp

你可以参考 Next.js 文档中的 安装 sharp 以使用内置图片优化 了解更多信息。

¥You can refer to the Install sharp to Use Built-In Image Optimization in the Next.js documentation for more information.

API

模块

¥Modules

@storybook/nextjs 包导出了几个模块,使你能够 mock Next.js 的内部行为。

¥The @storybook/nextjs package exports several modules that enable you to mock Next.js's internal behavior.

@storybook/nextjs/export-mocks

类型:{ getPackageAliases: ({ useESM?: boolean }) => void }

¥Type: { getPackageAliases: ({ useESM?: boolean }) => void }

getPackageAliases 是一个用于生成设置 可移植故事 所需别名的助手。

¥getPackageAliases is a helper for generating the aliases needed to set up portable stories.

// jest.config.ts
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
// 👇 Import the utility function
import { getPackageAliases } from '@storybook/nextjs/export-mocks';
 
const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
});
 
const config: Config = {
  testEnvironment: 'jsdom',
  // ... rest of Jest config
  moduleNameMapper: {
    ...getPackageAliases(), // 👈 Add the utility as mapped module names
  },
};
 
export default createJestConfig(config);

@storybook/nextjs/cache.mock

类型:typeof import('next/cache')

¥Type: typeof import('next/cache')

此模块导出 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.

MyForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { revalidatePath } from '@storybook/nextjs/cache.mock';
 
import MyForm from './my-form';
 
const meta: Meta<typeof MyForm> = {
  component: MyForm,
};
 
export default meta;
 
type Story = StoryObj<typeof MyForm>;
 
export const Submitted: Story = {
  async play({ canvasElement }) {
    const canvas = within(canvasElement);
 
    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/headers.mock

类型:来自 Next.js 的 cookiesheadersdraftMode

¥Type: cookies, headers and draftMode from Next.js

此模块导出 next/headers 模块导出的可写模拟实现。你可以使用它来设置在故事中读取的 cookie 或标头,并在稍后断言它们已被调用。

¥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 的默认 headers() 导出是只读的,但此模块公开了允许你写入标题的方法:

¥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):如果值已经存在,则将其附加到标题。

    ¥headers().append(name: string, value: string): Appends the value to the header if it exists already.

  • headers().delete(name: string):删除标题

    ¥headers().delete(name: string): Deletes the header

  • headers().set(name: string, value: string):将标题设置为提供的值。

    ¥headers().set(name: string, value: string): Sets the header to the value provided.

对于 cookies,你可以使用现有的 API 来编写它们。例如,cookies().set('firstName', 'Jane')

¥For cookies, you can use the existing API to write them. E.g., cookies().set('firstName', 'Jane').

因为 headers()cookies() 及其子功能都是模拟的,所以你可以在故事中使用任何 模拟实用程序,比如 headers().getAll.mock.calls

¥Because headers(), cookies() and their sub-functions are all mocks you can use any mock utilities in your stories, like headers().getAll.mock.calls.

MyForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { cookies, headers } from '@storybook/nextjs/headers.mock';
 
import MyForm from './my-form';
 
const meta: Meta<typeof MyForm> = {
  component: MyForm,
};
 
export default meta;
 
type Story = StoryObj<typeof MyForm>;
 
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/navigation.mock

类型:typeof import('next/navigation') & getRouter: () => ReturnType<typeof import('next/navigation')['useRouter']>

¥Type: typeof import('next/navigation') & getRouter: () => ReturnType<typeof import('next/navigation')['useRouter']>

此模块导出 next/navigation 模块导出的模拟实现。它还导出一个 getRouter 函数,该函数返回 来自 useRouter 的 Next.js 的 router 对象 的模拟版本,允许操作和断言属性。你可以在故事的 播放函数 中使用它模拟实现或对模拟调用进行断言。

¥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.

MyForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
 
import MyForm from './my-form';
 
const meta: Meta<typeof MyForm> = {
  component: MyForm,
  parameters: {
    nextjs: {
      // 👇 As in the Next.js application, next/navigation only works using App Router
      appDirectory: true,
    },
  },
};
 
export default meta;
 
type Story = StoryObj<typeof MyForm>;
 
export const Unauthenticated: Story = {
  async play() => {
    // 👇 Assert that your component called redirect()
    await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
  },
};
 
export const GoBack: Story = {
  async play({ canvasElement }) {
    const canvas = within(canvasElement);
    const backBtn = await canvas.findByText('Go back');
 
    await userEvent.click(backBtn);
    // 👇 Assert that your component called back()
    await expect(getRouter().back).toHaveBeenCalled();
  },
};

@storybook/nextjs/router.mock

类型:typeof import('next/router') & getRouter: () => ReturnType<typeof import('next/router')['useRouter']>

¥Type: typeof import('next/router') & getRouter: () => ReturnType<typeof import('next/router')['useRouter']>

此模块导出 next/router 模块导出的模拟实现。它还导出一个 getRouter 函数,该函数返回 来自 useRouter 的 Next.js 的 router 对象 的模拟版本,允许操作和断言属性。你可以在故事的 播放函数 中使用它模拟实现或对模拟调用进行断言。

¥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.

MyForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
// 👇 Must include the `.mock` portion of filename to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/router.mock';
 
import MyForm from './my-form';
 
const meta: Meta<typeof MyForm> = {
  component: MyForm,
};
 
export default meta;
 
type Story = StoryObj<typeof MyForm>;
 
export const GoBack: Story = {
  async play({ canvasElement }) {
    const canvas = within(canvasElement);
    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:

// .storybook/main.js
import * as path from 'path';
 
export default {
  // ...
  framework: {
    name: '@storybook/nextjs',
    options: {
      image: {
        loading: 'eager',
      },
      nextConfigPath: path.resolve(__dirname, '../next.config.js'),
    },
  },
};

可用选项包括:

¥The available options are:

builder

类型:Record<string, any>

¥Type: Record<string, any>

配置 框架的构建器 的选项。对于 Next.js,可用选项可以在 Webpack 构建器文档 中找到。

¥Configure options for the framework's builder. For Next.js, available options can be found in the Webpack builder docs.

image

类型:object

¥Type: object

传递给每个 next/image 实例的属性。有关更多详细信息,请参阅 next/image 文档

¥Props to pass to every instance of next/image. See next/image docs for more details.

nextConfigPath

类型:string

¥Type: string

next.config.js 文件的绝对路径。如果你有一个不在项目根目录中的自定义 next.config.js 文件,则这是必需的。

¥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

此框架在 nextjs 命名空间下为 Storybook 贡献了以下 参数

¥This framework contributes the following parameters to Storybook, under the nextjs namespace:

appDirectory

类型:boolean

¥Type: boolean

默认:false

¥Default: false

如果你的故事导入了使用 next/navigation 的组件,则需要将参数 nextjs.appDirectory 设置为 true。因为这是一个参数,所以你可以将其应用于 单个故事组件的所有故事Storybook 中的每个故事。有关更多详细信息,请参阅 Next.js 导航

¥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.

类型:

¥Type:

{
  asPath?: string;
  pathname?: string;
  query?: Record<string, string>;
  segments?: (string | [string, string])[];
}

默认值:

¥Default value:

{
  segments: [];
}

传递给 next/navigation 上下文的路由对象。有关更多详细信息,请参阅 Next.js 的导航文档

¥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>;
}

传递给 next/router 上下文的路由对象。有关更多详细信息,请参阅 Next.js 的路由文档

¥The router object that is passed to the next/router context. See Next.js's router docs for more details.