首页
/ Storybook × Next.js:手动将 React 项目的 framework 切换到 @storybook/nextjs,并看懂其 Preset 实现

Storybook × Next.js:手动将 React 项目的 framework 切换到 @storybook/nextjs,并看懂其 Preset 实现

2026-09-07 15:43:30作者:霍妲思

本文以 Storybook 官方文档中「手动安装 Next.js framework」的核心配置片段为主体,完整讲解如何将一个已有的 React + Webpack 项目切换到 @storybook/nextjs 框架:从安装依赖、修改 .storybook/main.js|ts 中的 framework 字段,到清理不再需要的旧 Addon,并结合开源仓库源码深入解析这个字段背后触发的 Preset 解析、Builder/Renderer 装配与 defineMain 类型函数的真实行为。读完本文,你可以独立完成框架切换,并理解 Storybook 配置项与底层 preset 加载机制之间的对应关系。

1. 适用场景:为什么需要手动切换 framework

当你的项目最初使用 @storybook/react-webpack5(或其他 React 框架 preset)初始化了 Storybook,但业务本身是 Next.js 应用时,官方推荐的做法是把整个框架 preset 切换为 @storybook/nextjs。这一流程在官方文档 Next.js framework 页面的 FAQ「How do I manually install the Next.js framework?」 中给出了完整步骤,其核心配置片段正是 nextjs-add-framework.md 所定义的 diff 内容:修改 framework 属性并同步更换配置类型的 import 来源。

整个流程分为三步:

  1. 安装 @storybook/nextjs 开发依赖;
  2. 修改 .storybook/main.js|ts,将 framework 从原框架改为 @storybook/nextjs,并更换 StorybookConfig 类型或 defineMain 的 import 路径;
  3. 移除此前用于集成 Next.js 的第三方 Addon(如 storybook-addon-next)。

以下各节依次展开这三步,并在第 5 节深入源码说明 framework 字段在 Storybook 内部究竟做了什么。

2. 第一步:安装 @storybook/nextjs 包

在修改任何配置之前,先按你的包管理器安装框架包(来自 nextjs-install.md):

# npm
npm install --save-dev @storybook/nextjs
# pnpm
pnpm add --save-dev @storybook/nextjs
# yarn
yarn add --dev @storybook/nextjs

安装完成后,从 package.jsonpeerDependencies 可以确认适用前提:该 preset 要求项目满足 next ^14.1.0 || ^15.0.0 || ^16.0.0react ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0,以及 webpack ^5.0.0(在 peerDependenciesMeta 中标记为可选,因为 webpack 5 由 preset 内部依赖提供)。如果你的 Next.js 版本不在上述范围内,需要先升级 Next.js 再执行本切换。

3. 第二步:修改 .storybook/main.js|ts 中的 framework 属性

这是核心文档 nextjs-add-framework.md 的主体内容。该片段覆盖了两种配置写法风格(CSF 3 传统写法与 CSF Next 实验性写法)以及两种文件语言(.js / .ts),共四种 diff 变体,切换时请按你项目实际使用的风格对照执行。

3.1 CSF 3 风格:.storybook/main.js

最小改动只有一个字段:

export default {
  // ...
-  framework: '@storybook/react-webpack5',
+  framework: '@storybook/nextjs',
};

3.2 CSF 3 风格:.storybook/main.ts

TypeScript 版本除了改 framework 字段外,还必须同步更换配置对象的类型来源——StorybookConfig 类型由框架包提供,切换框架后 import 路径要从旧框架包改为 @storybook/nextjs

- import type { StorybookConfig } from '@storybook/your-previous-framework';
+ import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
  // ...
-  framework: '@storybook/react-webpack5',
+  framework: '@storybook/nextjs',
};

export default config;

这里 your-previous-framework 是占位符,代表你当前正在使用的框架包名(如 @storybook/react-webpack5)。之所以必须改 import,是因为 StorybookConfig 类型中 framework 字段的合法值与各框架扩展的 options 类型都由框架包自身声明,沿用旧包的类型会导致字段校验与新框架的 options(见第 5.5 节)不匹配。

3.3 CSF Next(实验性)风格:defineMain 写法

如果你的 main.ts|js 采用实验性的 defineMain 辅助函数写法,改动点从类型 import 变为 defineMain 的 import 来源:

- import { defineMain } from '@storybook/your-previous-framework/node';
+ import { defineMain } from '@storybook/nextjs/node';

export default defineMain({
  // ...
-  framework: '@storybook/react-webpack5',
+  framework: '@storybook/nextjs',
});

同一段配置在 .storybook/main.js(JS 版 CSF Next 写法)中完全相同:

- import { defineMain } from '@storybook/your-previous-framework/node';
+ import { defineMain } from '@storybook/nextjs/node';

export default defineMain({
  // ...
-  framework: '@storybook/react-webpack5',
+  framework: '@storybook/nextjs',
});

关于 @storybook/nextjs/node 子路径导出,可以在 src/node/index.ts 中确认其真实实现:

import type { StorybookConfig } from '../types.ts';

export function defineMain(config: StorybookConfig) {
  return config;
}

export type { StorybookConfig };

可以看到 defineMain 是一个恒等函数(identity function),运行时不做任何处理,它的全部价值在于静态层面:借助 @storybook/nextjs/node 子路径导出的、由该框架 types.ts 定义的 StorybookConfig 类型,让配置对象获得 Next.js 框架专属的字段补全与校验。因此把 defineMain 的 import 从旧框架包切到 @storybook/nextjs/node,等价于把类型系统整体切换到 Next.js 框架的 schema 上。

4. 第三步:移除不再需要的 Next.js 集成 Addon

切换到 @storybook/nextjs 框架后,此前用于把 Next.js 特性「注入」到普通 React Storybook 的第三方 Addon 会被 preset 的原生能力取代,应当从 addons 数组中删除。根据 nextjs-remove-addons.md,可以移除的是:

export default {
  // ...
  addons: [
    // ...
    // 👇 These can both be removed
    // 'storybook-addon-next',
    // 'storybook-addon-next-router',
  ],
};

storybook-addon-nextstorybook-addon-next-router 两个 Addon。TS 配置(CSF 3 或 CSF Next 写法)中的清理方式相同,只是配置外层包的是 StorybookConfig 对象或 defineMain({...}) 调用。清理后请确认 package.json 中对应的依赖也一并卸载,避免残留依赖拉入与 preset 冲突的 webpack 插件。

5. 源码纵深:framework: '@storybook/nextjs' 在 Storybook 内部触发了什么

配置层面改一个字段,但运行时 Storybook 会据此把整个构建链换掉。以下基于本仓库 code/frameworks/nextjs 的源码说明这条链路。

5.1 framework 字段即 preset 名称

Storybook 的 framework 字段在内部按 preset 规则解析:字符串值 @storybook/nextjs 会加载该包的 preset 入口。对应到本仓库就是 preset.js(一行 re-export 到构建产物 dist/preset.js),其源码为 src/preset.ts。preset 文件按约定导出若干 PresetProperty 钩子,Storybook 在启动时逐个应用。

5.2 core 钩子:锁定 builder-webpack5 与 React renderer

src/preset.tscore 钩子是切换后行为变化的核心:

  • 首先它通过 options.presets.apply('framework') 回读 framework 配置,并在 webpack 真正启动之前调用 configureConfig 加载 Next.js 的 next.config.js 配置。源码注释说明了原因:让 Next.js 有机会先行覆写 webpack 内部行为,否则 @storybook/builder-webpack5 的文件系统缓存(fsCache: true)无法正常工作。同时支持从对象形式的 framework.options.nextConfigPath 中读取自定义的 Next.js 配置路径;
  • 然后返回固定的构建链:builder 指向 @storybook/builder-webpack5(可选合并 framework.options.builder),renderer 指向 @storybook/react/presetaddons 钩子则自动追加 @storybook/preset-react-webpack

这也解释了为什么 package.json 的 dependencies 中会直接依赖 @storybook/builder-webpack5@storybook/react@storybook/preset-react-webpack——框架 preset 自身保证了构建链的完整性。

5.3 previewAnnotations:注入 preview 与 Next.js 运行时兼容层

previewAnnotations 钩子会在 preview 编译前自动注入 @storybook/nextjs/preview 注解(对应 src/preview.tsx);对于 Next.js 16 以下版本,还会额外注入 @storybook/nextjs/config/preview(源码中留有 TODO,待只支持 Next.js 16+ 后移除)。这两个文件承载了路由 Provider、next/image 装饰器、styled-jsx、head 管理等运行时能力,全部对用户透明——这正是「切换 framework 后无需再挂第三方 Addon」的原因。

5.4 babel 钩子:复用项目的 Next.js Babel 配置

pretset.ts 的 babel 钩子 会解析项目现有的 Babel 配置,识别其中的 next/babel preset(字符串、数组或含 file.request 的配置项三种形态),据此决定如何组装 Storybook 侧的 Babel 处理链(内置 src/babel/preset.ts 与若干 Next.js 兼容插件,如 react-loadable-pluginoptimize-hook-destructuring 等)。从源码结构看,这套机制保证你在 Next.js 项目中启用的 Babel 插件在 Storybook 里也能生效。

5.5 framework 的对象形式:nextConfigPathbuilder

core 钩子的实现 可以确认,framework 除了字符串形式外还支持对象形式,其中 options.nextConfigPath 用于指定非默认的 next.config.js 位置,options.builder 用于向 @storybook/builder-webpack5 透传 builder 级选项:

// 对应 preset.ts 中的读取逻辑
nextConfigPath: typeof framework === 'string' ? undefined : framework.options.nextConfigPath,
// builder.options 合并自
...(typeof framework === 'string' ? {} : framework.options.builder || {}),

这两个选项的完整类型定义位于 src/types.ts,即 @storybook/nextjs 包导出的 StorybookConfig 所依赖的类型源。

6. @storybook/nextjs 提供的能力边界

切换 framework 后获得哪些能力、边界在哪,可以直接从 package.jsonexports 映射与 src 目录结构 相互印证。exports 暴露的用户可用子路径包括:

子路径 对应源码 用途
@storybook/nextjs/preview src/preview.tsx preview 运行时注入入口
@storybook/nextjs/node src/node/index.ts defineMainStorybookConfig 类型
@storybook/nextjs/export-mocks(及 headers.mocknavigation.mockrouter.mocklink.mock 等) src/export-mocks/ 在 Storybook 中 mock Next.js 的 headers/cookies/useRouter/usePathname 等 API
@storybook/nextjs/images/next-imageimages/next-legacy-image src/images/ 以 Next.js 方式渲染 next/image
@storybook/nextjs/rsc/server-only src/rsc/server-only.ts 实验性 React Server Components 支持所需的桩模块
@storybook/nextjs/storybook-nextjs-font-loader src/font/webpack/loader/ next/font 的字体加载 Webpack loader

src 目录结构看,preset 还内置了:routing/(App Router 与 Pages Router 两种 Provider 的装饰器)、styledJsx/(styled-jsx 编译支持)、swc/(Next.js SWC loader 补丁)、nodePolyfills/(Node 模块 polyfill,由依赖 node-polyfill-webpack-plugin 驱动)、aliases/imports/(基于 tsconfig-paths-webpack-plugin 复用项目的 tsconfig.json 路径别名)等。依赖列表中的 styled-jsxprobe-image-sizereact-refresh/@pmmmwh/react-refresh-webpack-plugin 等也都与这些能力一一对应。

结合官方 FAQ(nextjs.mdx)还有两个切换后必须知道的行为变化与限制:

  • 图片导入语义变化:启用该框架后,静态图片 import 返回的是 { src, height, width, blurDataURL } 对象(Next.js 方式),而不再是裸路径字符串,故事中处理图片的地方需要相应调整;
  • Yarn v2/v3 用户注意:由于 Yarn 2/3 的包解析规则不同,可能出现 Can't resolve 'css-loader' / 'style-loader' 报错,此时需要把这两个 loader 直接安装为项目依赖;
  • 数据获取型页面app 目录中直接 fetch 数据的页面组件引入 Node 专用模块会导致 Webpack 构建崩溃,官方建议把纯组件拆到单独文件供故事使用,或在 webpackFinal 中 polyfill 相关模块。

7. 小结与验证路径

  • 手动切换 @storybook/nextjs 框架的完整动作只有三处:安装依赖、把 framework 改为 @storybook/nextjs 并同步更换 StorybookConfig/defineMain 的 import 来源、移除 storybook-addon-next(-router) 类旧 Addon;
  • framework 字段在运行时等价于「加载该包的 preset」,@storybook/nextjs 的 preset(src/preset.ts)负责把 builder 锁定为 @storybook/builder-webpack5、renderer 锁定为 React preset,并在启动前加载 next.config.js
  • defineMain 是纯类型层面的恒等函数,其意义在于把配置对象绑定到 @storybook/nextjs/node 导出的框架类型上;
  • 切换后 Next.js 的图片、字体、路由、headers/router mock 等能力由 preset 原生提供,能力清单与边界可从 package.jsonexports/peerDependencies 精确核对(当前仓库版本为 10.6.0-beta.1,适用 Next.js 14.1+/15/16)。

关键参考文件:核心配置片段安装命令片段旧 Addon 清理片段Next.js framework 官方文档页preset 入口preset 源码node 子路径导出包清单

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

项目优选

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