首页
/ Next.js 官方示例解析:用 Storybook 构建组件驱动开发工作流(with-storybook)

Next.js 官方示例解析:用 Storybook 构建组件驱动开发工作流(with-storybook)

2026-09-07 14:15:49作者:裘旻烁

本文基于 Next.js 官方仓库中的 with-storybook 示例,完整拆解如何在 Next.js 项目中集成 Storybook:包括通过 create-next-app 引导项目、.storybook 配置文件中各参数的含义、使用 @storybook/nextjs 框架在 Story 中直接使用 styled-jsx 等 Next.js 特性的方式,以及 storybook build 静态产物与 vercel.json 部署配置的配合。读完后你可以在任意 Next.js 项目中复刻这套组件开发、预览、交互测试与静态构建发布的完整工作流。

示例定位与核心能力

官方 README 对示例的定义是:一套使用 @storybook/nextjs(官方文档中提及的包名,仓库内 devDependencies 版本为 ^8.0.9)的标准 Storybook 配置,并且示例中的 stories 专门用来演示在 Storybook 中使用 Next.js 特性的能力。

从源码可以印证这一点:Button 组件 在渲染时内联了一段 styled-jsx 样式,直接消费 backgroundColor prop:

// examples/with-storybook/stories/Button.tsx#L40-L55
return (
  <button
    type="button"
    className={["storybook-button", `storybook-button--${size}`, mode].join(" ")}
    {...props}
  >
    {label}
    <style jsx>{`
      button {
        background-color: ${backgroundColor};
      }
    `}</style>
  </button>
);

styled-jsx 是 Next.js 内置的 CSS-in-JS 方案,普通 React 项目中无法直接使用;该示例选择它作为演示载体,正是为了说明 @storybook/nextjs 框架已经替组件开发环境接好了 Next.js 的编译链路(styled-jsx、next.config.mjs 等),组件在 Storybook Canvas 中的表现与在 Next.js 应用中一致。

快速开始:引导项目与运行命令

使用 create-next-app 引导

按照 README 的说明,使用 create-next-app 配合 npm、Yarn 或 pnpm 引导示例:

npx create-next-app --example with-storybook with-storybook-app
yarn create next-app --example with-storybook with-storybook-app
pnpm create next-app --example with-storybook with-storybook-app

引导完成后得到一个名为 with-storybook-app 的独立项目,示例源码位于仓库的 examples/with-storybook 目录,其目录结构为:

路径 作用
.storybook/main.ts Storybook 主配置:stories 发现规则、addons、框架(@storybook/nextjs)与 builder 选项
.storybook/preview.ts 全局预览参数(controls 匹配规则)
stories/ 示例组件(ButtonHeaderPage)及其 .stories.ts 与 CSS 文件,另有 Configure.mdx 文档页
app/ 标准 App Router 落地页(layout.tsxpage.tsx),与 Storybook 并行存在
vercel.json 声明部署框架为 storybook、构建命令为 storybook build
package.json 脚本与依赖声明(next 14.2.2、storybook ^8.0.9,要求 Node >= 18)

运行开发态 Storybook

package.json 中的 storybook 脚本实际执行的是 storybook dev -p 6006

npm run storybook
# or
yarn storybook
# or
pnpm storybook

服务默认监听 6006 端口,打开后左侧面板按 Example/ButtonExample/HeaderExample/Page 的 title 组织各 Story。

构建静态 Storybook

npm run build-storybook
# or
yarn build-storybook
# or
pnpm build-storybook

该脚本执行 storybook build,输出到 storybook-static 目录,产物为纯静态站点,可直接被任意静态托管服务或 Vercel 部署。README 特别指出:部署到 Vercel 时需要将输出目录指定为 storybook-static,而 vercel.json 正是为此准备的:

{
  "framework": "storybook",
  "buildCommand": "storybook build"
}

framework 字段让部署平台识别这是一个 Storybook 项目,buildCommand 显式指定构建命令,两者配合使 storybook-static 产物成为部署输出。

深入 .storybook/main.ts:框架、构建器与 stories 发现

main.ts 是理解本示例的关键文件,完整内容如下:

import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
  stories: [
    "../stories/**/*.mdx",
    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@chromatic-com/storybook",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/nextjs",
    options: {
      builder: {
        useSWC: true,
      },
    },
  },
  docs: {
    autodocs: "tag",
  },
  staticDirs: ["../public"],
};

export default config;

逐项说明各配置的作用:

  • stories:两个 glob 定义了 Story 的发现规则——../stories 目录(相对 .storybook/)下的全部 .mdx 文档页与全部 *.stories.(js|jsx|mjs|ts|tsx) 文件。本示例中即 Button.stories.tsHeader.stories.tsPage.stories.tsConfigure.mdx。路径以 ../ 开头,因为配置目录是 .storybook/,需要回到项目根才能找到 stories/public/
  • framework:声明使用 @storybook/nextjs 框架,这是本示例的核心。它意味着 Story 编译走 Next.js 的转译/构建管线(styled-jsx、CSS Modules、next.config.mjs 均被识别),而不仅仅是 babel/react 框架。
  • framework.options.builder.useSWC:启用 SWC 作为底层构建器,显著加快 dev 启动与 HMR 速度。
  • addons
    • @storybook/addon-links:在 Story 之间创建链接(配合 params.links 实现 Story 到 Story 的跳转);
    • @storybook/addon-essentials:捆绑 Controls、Actions、Viewport、Backgrounds、Docs 等基础能力;
    • @storybook/addon-interactions:与 @storybook/test 配合,为交互测试(play 函数)提供 UI 支持;
    • @chromatic-com/storybook:视觉回归(Chromatic)集成,在 CI 中对比 Story 渲染快照。
  • docs.autodocs: "tag":自动文档页的生成策略——只为带 autodocs tag 的组件生成自动文档页(见下文 Button/Header 的 tags: ["autodocs"])。
  • staticDirs: ["../public"]:将项目 public/ 目录中的静态资源(本示例中的 next.svgvercel.svg)暴露给 Story 使用,组件里可以用绝对路径引用这些资源。

preview.ts 则配置全局 Controls 的匹配器,使所有以 background/color 结尾的参数自动呈现颜色选择器、以 Date 结尾的参数呈现日期选择器:

import type { Preview } from "@storybook/react";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

这也解释了为什么 ButtonbackgroundColor 属性在 Controls 面板中显示为颜色控件(见下文 argTypes 部分)。

逐读三个 Story:args、actions 与交互测试

Button:参数类型与动作监听

Button.stories.ts 展示了元数据(meta)驱动的写法,核心片段:

// examples/with-storybook/stories/Button.stories.ts#L13-L26
tags: ["autodocs"],
argTypes: {
  backgroundColor: {
    control: "color",
  },
},
args: { onClick: fn() },
  • tags: ["autodocs"]main.ts 中的 docs.autodocs: "tag" 联动,为该组件自动生成文档页;
  • argTypes.backgroundColor.control: "color" 显式将 backgroundColor 声明为颜色控件(与 preview.ts 的正则匹配器双重保险);
  • args.onClick: fn() 中的 fn 来自 @storybook/test,作为 spy 监听 onClick——点击画布中的按钮后,调用会出现在 Actions 面板中。

组件本身基于 args 派生出四个 Story:Primaryprimary: true)、SecondaryLargesize: "large")、Smallsize: "small")。由于 Button 组件primary 默认 falsesize 默认 "medium",每个 Story 只需覆盖差异参数即可,这正是 args 模式的价值。

Header:组合组件与状态变体

Header 组件 复用了 Button,按 user prop 的有无呈现两种 UI 分支(登录态显示欢迎语与 Log out,未登录显示 Log in 与 Sign up)。对应 Header.stories.ts 定义了两个 Story:

export const LoggedIn: Story = {
  args: { user: { name: "Jane Doe" } },
};

export const LoggedOut: Story = {};

meta 中将 onLogin/onLogout/onCreateAccount 全部绑定 fn()layout: "fullscreen" 让头部组件铺满画布,模拟真实页面中的展示位置。

Page:用 play 函数编写交互测试

Page.stories.ts 演示了 Storybook 的交互测试(Interaction Testing):play 函数在 Story 渲染后运行一段脚本,模拟用户操作并断言结果:

// examples/with-storybook/stories/Page.stories.ts#L21-L31
export const LoggedIn: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const loginButton = canvas.getByRole("button", { name: /Log in/i });
    await expect(loginButton).toBeInTheDocument();
    await userEvent.click(loginButton);
    await expect(loginButton).not.toBeInTheDocument();

    const logoutButton = canvas.getByRole("button", { name: /Log out/i });
    await expect(logoutButton).toBeInTheDocument();
  },
};

脚本语义:断言画布中存在 Log in 按钮 → 模拟点击 → 断言其消失 → 断言 Log out 按钮出现。整个链路基于 Page 组件 内的 useState 完成本地登录态切换,within/userEvent/expect 均来自 @storybook/test(内部封装了 Testing Library 与用户事件模拟)。依赖列表中 @storybook/addon-interactions 负责在 UI 中展示这些交互结果。这是"组件文档 + 轻量行为验证"合二为一的典型用法。

TypeScript 支持与版本前提

README 指出:自 Storybook v6.0 起内置 TypeScript 支持,开箱即用无需额外配置;如需定制默认配置需参考 Storybook 的 TypeScript 文档。本示例的 tsconfig.json 是标准 Next.js 严格模式配置(strict: truemoduleResolution: "bundler"@/* 路径别名、next 插件),Story 文件在其中与 App 代码共享同一套类型检查。

适用前提(以 package.json 为准):

  • Next.js 固定为 14.2.2,React 为 ^18
  • Storybook 全家桶(@storybook/nextjs@storybook/react@storybook/blocks@storybook/test 及各 addon)均为 ^8.0.9,即 Storybook 8.x 的 API(Meta/StoryObj 类型、satisfies 约束 meta 写法);
  • 要求 Node >= 18engines 字段)。

如果你的项目使用 Next.js 15 或 Storybook 9,本示例的目录结构与配置思路仍然适用,但类型导入与部分 addon 包名需要以对应版本的官方文档为准。

小结:这套工作流解决了什么

该示例展示的双轨结构值得注意:app/ 目录是一个完整的 App Router 应用(落地页用于验证 next dev/next build 正常),stories/ + .storybook/ 则是并行的组件工坊。日常开发中,UI 组件先在 Storybook 6006 端口中被隔离开发、通过 Controls 调参、通过 Actions 与 play 交互测试验证行为;storybook build 产出的静态站点可独立部署供团队评审(vercel.json 已配好输出约定);而组件本身因为运行在 @storybook/nextjs 框架下,可直接使用 styled-jsx 等 Next.js 特性,所见即所得地迁移回 app/ 应用中使用。

如需继续阅读相关源码,可从 examples/with-storybook/README.md 出发,配合 examples/with-storybook/.storybook/main.tsexamples/with-storybook/stories/Button.stories.tsexamples/with-storybook/stories/Page.stories.ts 对照理解每一处配置的实际效果。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391