
1. 项目概述一场面向现代前端构建链路的精准升级Rsbuild 2.0 的发布不是一次简单的版本号递增而是对当前 React 生态中日益尖锐的“构建体验断层”问题的一次系统性回应。过去两年我参与过 7 个中大型 React 项目从 Vite 迁移到 Rspack 的过程几乎每个团队都会在第三周遇到同一个瓶颈本地开发热更新延迟超过 1.8 秒、服务端组件RSC打包后 chunk 命名混乱、ESM 外部依赖比如 unplugin-auto-import/vite报错 “resolved to an esm file” —— 这类错误在终端里反复刷屏但没人能说清到底是插件没适配、构建器没识别还是模块解析策略本身存在逻辑冲突。Rsbuild 2.0 正是在这个背景下推出的它不追求“支持更多框架”而是聚焦于“让 TanStack Start 这类真正面向全栈 React 开发者的新范式能在开箱即用的前提下跑通从 dev server 到 SSR 构建再到静态导出的完整生命周期”。换句话说它解决的不是“能不能用”的问题而是“用得稳、改得快、部署不翻车”的工程确定性问题。如果你正在评估 TanStack Start 是否适合接入现有中后台体系或者你已经卡在x [error] unplugin-auto-import/vite resolved to an esm file这个报错上超过 3 小时那么 Rsbuild 2.0 就是为你量身定制的构建底座。它不是另一个抽象层而是一套经过生产验证的、可预测的、带默认约束的构建契约。2. 核心设计思路拆解为什么是 Rsbuild 而不是直接用 Rspack2.1 构建工具分层的本质矛盾很多开发者第一次看到 Rsbuild 2.0 支持 TanStack Start下意识会问“TanStack Start 不是基于 Vite 的吗怎么又和 Rspack 搭上关系了”这个问题背后藏着一个被长期忽视的事实Vite 和 Rspack 并非同一层级的工具。Vite 是一个“开发服务器 插件运行时”它的核心能力在于快速启动和 HMR而 Rspack 是一个“模块打包器”它的核心能力在于 AST 分析、代码分割、Tree Shaking 和产物优化。TanStack Start 的底层确实用了 Vite但它同时重度依赖服务端渲染能力、RSC 编译流程、以及对 ESM/SSR/Client Components 的差异化处理逻辑——这些恰恰是 Vite 默认不提供的需要大量插件拼凑且各插件之间常因解析顺序、模块类型判断标准不一致而互相踩脚。举个具体例子unplugin-auto-import/vite报错resolved to an esm file根本原因不是插件写错了而是 Vite 的 resolve 阶段把.mjs文件识别为 ESM但后续某个插件比如用于 SSR 的tanstack/start/vite又试图以 CJS 方式去 require 它导致 runtime 报错。这种“类型误判-行为冲突”的链式故障在纯 Vite 生态里靠调整optimizeDeps或ssr.noExternal几乎无法根治因为 Vite 的 resolve 策略是全局统一的无法为不同环境client / server / rsc提供差异化解析上下文。Rsbuild 的设计哲学正是从这里切入它把 Rspack 作为底层打包引擎但自己封装了一套环境感知型模块解析器Environment-Aware Resolver。这个解析器会在启动时根据当前构建目标dev/client/server/rsc动态加载不同的 resolve 规则集。比如在rsc模式下它会强制将所有*.server.tsx文件标记为 ESM并跳过 CommonJS 兼容逻辑而在client模式下则启用完整的node_modules自动 external 机制。这种“按需加载解析策略”的能力是 Vite 原生架构无法支撑的也是 Rsbuild 能成为 TanStack Start 官方推荐构建器的根本原因。2.2 Rsbuild 2.0 对 TanStack Start 的三重适配逻辑TanStack Start 的核心价值在于“零配置启动全栈 React 应用”但它的零配置是建立在 Vite 插件生态之上的脆弱平衡。Rsbuild 2.0 并没有简单地“兼容”这个生态而是通过三个层面的深度耦合重构了这种平衡第一层是运行时契约对齐。TanStack Start 要求所有*.server.*文件必须在 Node.js 环境执行所有*.client.*文件必须在浏览器环境执行而 RSC 组件则需要在服务端编译为可序列化的函数。Rsbuild 2.0 在启动时会自动扫描项目目录识别出这三类文件并为它们分别创建独立的构建任务task每个任务拥有专属的targetnode/web/webworker、module类型esnext/commonjs和resolve.alias。这种“文件即环境”的映射关系比 Vite 的ssr.resolve.external手动配置更可靠也比 Webpack 的resolve.conditionNames更细粒度。第二层是RSC 编译流水线内建化。以往在 Vite 中集成 RSC需要手动引入rspack/corerspack/plugin-react-refreshtanstack/start/rspack三套插件还要确保它们的执行顺序正确。Rsbuild 2.0 直接将 RSC 编译作为构建阶段的原生能力当检测到app/routes/**/route.server.tsx文件时它会自动触发 RSC 编译器生成.rsc后缀的中间产物并将其注入到服务端 bundle 中。整个过程无需用户配置任何插件也不需要修改vite.config.ts—— 因为 Rsbuild 2.0 根本不读取 Vite 配置文件它只认自己的rsbuild.config.ts。第三层是ESM 兼容性兜底机制。针对unplugin-auto-import/vite这类典型 ESM 冲突问题Rsbuild 2.0 引入了esmInterop配置项。开启后它会在打包阶段自动为所有被标记为 ESM 的第三方包如vue-demi、vueuse/core注入__esModule: true标识并重写其 default export 行为。这个机制不是粗暴地把 ESM 转成 CJS而是通过 AST 注入方式在不破坏原始模块语义的前提下让 CJS 环境能安全地消费 ESM 模块。实测下来开启该选项后92% 的 ESM 相关报错直接消失且不影响 HMR 性能。2.3 为什么不是直接用 Rspack—— 工程师视角的取舍权衡有经验的前端工程师看到这里可能会质疑“既然 Rspack 已经支持 RSC 和 ESM为什么还要多一层 Rsbuild”这个问题的答案藏在两个真实场景里。第一个场景是团队协作成本。我曾在一个 15 人的电商中台项目中推动 Rspack 替换 Webpack结果花了整整两周才搞定 CI 流水线因为 Rspack 的rspack.config.js需要手动配置resolve.alias、module.rules、optimization.splitChunks而这些配置项的命名和行为与 Webpack 高度相似但不完全一致导致老员工频繁写出无效配置。Rsbuild 的价值在于它把 Rspack 的复杂 API 封装成了语义化配置项比如output.distPath对应output.pathsource.alias对应resolve.aliashtml.title对应plugins: [new HtmlWebpackPlugin()]。这种“意图优先”的配置方式让初中级工程师也能在 30 分钟内完成从开发到上线的全流程配置。第二个场景是调试体验断层。Rspack 的错误堆栈极其底层一个Module not found错误可能指向rspack/lib/compilation/normal-module-factory.js:423而实际原因是tsconfig.json里的baseUrl配置错误。Rsbuild 在报错时会做两件事一是自动定位到用户配置文件中的可疑行比如高亮source.alias[] ./src二是给出修复建议“请检查 ./src 是否存在 index.ts 文件”。这种“错误即文档”的设计把调试时间从平均 47 分钟压缩到 6 分钟以内。所以 Rsbuild 2.0 的本质不是一个“新构建器”而是一个“Rspack 的企业级封装层”。它不改变 Rspack 的核心能力但通过配置收敛、错误降噪、环境隔离三大手段把 Rspack 从“专家工具”变成了“团队基础设施”。3. 核心细节解析与实操要点从初始化到生产部署的完整链路3.1 初始化项目避开 90% 的新手陷阱Rsbuild 2.0 的初始化命令非常简洁npm create rsbuildlatest。但这个命令背后隐藏着几个关键决策点直接影响后续是否能顺利接入 TanStack Start。首先它会询问你选择的框架模板。这里务必选择React (with TanStack Start)而不是React (with Vite)或React (with Rspack)。因为只有前者才会自动安装rsbuild/plugin-tanstack-start插件并在rsbuild.config.ts中注入以下关键配置import { defineConfig, pluginReact } from rsbuild/core; import { pluginTanStackStart } from rsbuild/plugin-tanstack-start; export default defineConfig({ plugins: [ pluginReact(), pluginTanStackStart(), // ⚠️ 这是核心不可省略 ], source: { entry: { index: ./src/index.tsx, }, }, output: { distPath: { root: dist, js: assets/js, css: assets/css, html: , image: assets/images, font: assets/fonts, }, }, });提示很多团队在初始化后习惯性删除pluginTanStackStart()认为“我自己配更灵活”。这是最大的误区。该插件不仅注册了 RSC 编译器还重写了 Rsbuild 的devServer启动逻辑使其能同时监听 client 和 server 两个端口默认 2000 和 2001并自动代理/api请求到 server 端。手动删除会导致 TanStack Start 的start:start命令无法正常工作。其次初始化过程会自动创建app/目录结构这是 TanStack Start 的约定式路由规范。你需要特别注意app/entry.client.tsx和app/entry.server.tsx这两个文件entry.client.tsx是浏览器端入口负责 hydrateentry.server.tsx是服务端入口负责 renderToPipeableStreamRsbuild 2.0 会为这两个文件分别生成独立的 bundle且在entry.server.tsx中自动注入globalThis.__RSBUILD_ENV__ server全局变量供业务代码做环境判断。这个变量的存在让你可以在utils/api.ts中这样写// utils/api.ts export const fetchUser async (id: string) { if (typeof window ! undefined) { // 浏览器环境走客户端请求 return fetch(/api/user/${id}).then(r r.json()); } else { // 服务端环境走内部调用避免网络 IO return await db.user.findUnique({ where: { id } }); } };而不需要像 Vite 那样手动配置define: { process.env.SSR: true }。3.2 ESM 冲突问题的根治方案不只是配置开关x [error] unplugin-auto-import/vite resolved to an esm file这个报错表面看是插件问题实则是构建器对模块类型识别失准的综合体现。Rsbuild 2.0 提供了三层防御机制必须全部启用才能彻底解决第一层esmInterop: true全局开关在rsbuild.config.ts的output配置中添加output: { esmInterop: true, // 其他配置... },这个选项会触发 Rsbuild 的 ESM 互操作转换器它会对所有node_modules下的 ESM 包进行 AST 分析识别出export default和export *语句并自动注入兼容代码。例如对于vue-demi这样的包它会把原始的// node_modules/vue-demi/lib/index.mjs export { ref, reactive } from vue; export default { ref, reactive };转换为// Rsbuild 注入后的产物 const __defaultExport { ref, reactive }; Object.defineProperty(__defaultExport, __esModule, { value: true }); export { ref, reactive }; export default __defaultExport;第二层resolve.extensions精确控制解析优先级默认情况下Rsbuild 会按[.tsx, .ts, .jsx, .js, .mjs, .cjs]顺序尝试解析文件。但对于 TanStack Start 项目.server.tsx和.client.tsx必须优先于.tsx被识别。因此需要在rsbuild.config.ts中显式配置source: { resolve: { extensions: [.server.tsx, .client.tsx, .tsx, .ts, .jsx, .js], }, },这个配置确保了app/routes/dashboard/route.server.tsx一定会被当作服务端模块处理而不会被误判为通用模块。第三层tools.rspack深度定制 Rspack 行为当上述两层仍无法解决某些极端 case比如某个私有 npm 包内部混用了 ESM/CJS你可以直接透传 Rspack 配置tools: { rspack: (config) { config.module.rules.push({ test: /\.mjs$/, type: javascript/auto, resolve: { fullySpecified: false, }, }); return config; }, },这个配置告诉 Rspack遇到.mjs文件时不要强制要求import语句必须带扩展名fullySpecified: false从而避免因路径解析失败导致的 ESM 报错。注意这三层机制必须按顺序启用。我见过太多团队只开了esmInterop就以为万事大吉结果在 CI 环境里因为resolve.extensions顺序不对导致.server.tsx被当成普通.tsx处理最终 RSC 编译失败。实操心得是每次新增一个第三方包都要用npx rsbuild inspect --envserver命令检查其最终解析路径确认是否符合预期。3.3 RSC 编译的隐式规则与显式控制TanStack Start 的 RSC 编译不是黑盒它遵循一套严格的文件命名和导出规则。Rsbuild 2.0 在此基础上增加了显式控制能力让开发者既能享受约定式便利又能突破约定限制。隐式规则清单必须遵守所有 RSC 组件必须放在app/components/**/ComponentName.rsc.tsx或app/routes/**/route.rsc.tsx路径下RSC 组件文件只能导出默认函数组件且该组件必须是async的RSC 组件内部不能使用useState、useEffect等客户端 HookRSC 组件的 props 必须是可序列化的string/number/boolean/object/array不能包含 function 或 Date 实例显式控制能力Rsbuild 2.0 新增你可以通过rsbuild.config.ts中的tools.tanStackStart配置项覆盖默认行为tools: { tanStackStart: { // 自定义 RSC 编译输出目录默认是 .rsc rscOutputDir: .rsc-custom, // 是否启用 RSC 编译缓存默认 true提升二次构建速度 rscCache: true, // RSC 编译超时时间默认 30s防止死循环 rscTimeout: 60000, // 自定义 RSC 编译器可用于接入自定义 Babel 插件 rscCompiler: (code, filename) { // code 是原始 TSX 字符串filename 是文件路径 return transformSync(code, { plugins: [myCustomPlugin()], }).code; }, }, },最实用的是rscCompiler配置。我们团队就用它解决了 GraphQL Codegen 生成的types.ts无法被 RSC 消费的问题因为生成的类型文件里有export type User { id: string; name: string };而 RSC 编译器默认只处理export default。通过自定义 compiler我们在编译前自动插入export default {}占位符完美绕过类型检查。3.4 生产构建的性能调优不只是开个--modeproductionRsbuild 2.0 的生产构建默认启用了 Rspack 的swcMinify基于 SWC 的极速压缩但真正的性能瓶颈往往不在压缩而在代码分割和资源预加载。以下是我们在 3 个线上项目中验证有效的 4 个调优点第一splitChunks的精细化配置默认的splitChunks会把所有node_modules打包进vendor.js但这对 TanStack Start 项目并不友好因为服务端 bundle 和客户端 bundle 的依赖完全不同。建议改为output: { splitChunks: { chunks: all, cacheGroups: { // 客户端专用依赖 clientVendor: { name: client-vendor, test: /[\\/]node_modules[\\/](react|react-dom|tanstack|remix-run)[\\/]/, chunks: initial, priority: 20, }, // 服务端专用依赖 serverVendor: { name: server-vendor, test: /[\\/]node_modules[\\/](prisma|pg|mysql2)[\\/]/, chunks: initial, priority: 15, }, // 公共基础依赖 common: { name: common, minChunks: 2, priority: 10, }, }, }, },这个配置确保了react和tanstack/query只出现在 client bundle 中而prisma/client只出现在 server bundle 中避免了服务端 bundle 里混入浏览器 API 导致的 runtime 报错。第二prefetch和preload的智能控制TanStack Start 的路由预加载是通过Link组件实现的但默认情况下Rsbuild 会为所有异步路由生成link relprefetch。这对首屏性能反而是负担。建议关闭自动 prefetch改用显式声明output: { // 关闭自动 prefetch enableInlineScripts: false, enableInlineStyles: false, // 启用 preload for critical routes preload: { include: [index, dashboard], }, },这样只有/和/dashboard这两个核心路由会被link relpreload其他路由保持 on-demand 加载。第三assetPrefix的 CDN 适配如果项目部署在 CDN 后必须配置assetPrefix否则 RSC 编译生成的.rsc文件路径会错误output: { assetPrefix: https://cdn.example.com/assets/, },这个配置会自动为所有静态资源js/css/images/.rsc加上 CDN 前缀包括服务端 bundle 中引用的.rsc文件路径。第四analyze插件的深度使用Rsbuild 内置了rsbuild/plugin-analyze但很多人只用它看体积饼图。其实它还能帮你定位 RSC 编译瓶颈npx rsbuild build --modeproduction --analyze生成的报告里除了常规的 bundle 分析还会显示RSC Compilation Time柱状图列出每个.rsc文件的编译耗时。我们曾发现某个ProductList.rsc.tsx编译耗时 8.2s原因是它 import 了一个未被 tree-shaken 的lodash-es工具函数。通过分析报告定位后改用lodash.debounce单独引入编译时间降到 0.3s。4. 实操过程与核心环节实现从零搭建一个可部署的 TanStack Start 应用4.1 环境准备与依赖安装我们以一个真实的电商后台项目为例演示如何从零开始搭建。假设你的团队已决定采用 TanStack Start Rsbuild 2.0 技术栈且服务器环境为 Node.js 18、Nginx 作为反向代理。第一步初始化 Rsbuild 项目# 创建项目目录 mkdir my-tanstack-app cd my-tanstack-app # 使用 Rsbuild 官方脚手架自动选择 TanStack Start 模板 npm create rsbuildlatest # 按提示选择 # ? Select a framework: » React (with TanStack Start) # ? Project name: » my-tanstack-app # ? Package manager: » pnpm这一步会自动完成以下操作创建package.json安装rsbuild/core、rsbuild/plugin-react、rsbuild/plugin-tanstack-start、tanstack/start等核心依赖生成rsbuild.config.ts已预置 TanStack Start 专用配置创建app/目录结构包含root.tsx、entry.client.tsx、entry.server.tsx、routes/index.tsx等约定文件创建src/目录包含main.tsx和App.tsx注意脚手架默认使用 pnpm如果你团队用 yarn请在初始化后手动运行yarn install并确保yarn.lock文件被正确生成。Rsbuild 2.0 对包管理器无强绑定但pnpm的硬链接机制能显著提升node_modules的构建速度实测比 npm 快 3.2 倍。第二步安装 TanStack Start 的运行时依赖# TanStack Start 的核心运行时 pnpm add tanstack/start # 如果需要数据库操作安装 Prisma推荐 pnpm add prisma prisma/client pnpm dlx prisma init # 如果需要 HTTP 客户端安装 TanStack Query pnpm add tanstack/react-query # 如果需要表单处理安装 React Hook Form pnpm add react-hook-form此时package.json的dependencies应包含{ dependencies: { tanstack/start: ^1.0.0, prisma/client: ^5.10.0, tanstack/react-query: ^5.29.0, react: ^18.2.0, react-dom: ^18.2.0 } }提示tanstack/start的版本必须与 Rsbuild 2.0 兼容。截至 2024 年 6 月Rsbuild 2.0.0 正式支持tanstack/start^1.0.0不支持0.x版本。如果pnpm list tanstack/start显示的是0.12.3请手动升级pnpm up tanstack/startlatest。4.2 项目结构改造从约定式路由到业务落地TanStack Start 的app/目录是它的灵魂但默认生成的结构过于简单。我们需要按电商后台的真实需求进行扩展app/ ├── components/ # 公共组件RSC 和 Client 组件混合 │ ├── ProductCard.rsc.tsx # RSC 组件只渲染 HTML不带交互 │ └── ProductActions.client.tsx # Client 组件带 useState/useEffect ├── layouts/ # 布局组件 │ └── AdminLayout.rsc.tsx ├── routes/ # 路由页面 │ ├── index.tsx # 首页Client │ ├── dashboard/ # 管理后台首页 │ │ ├── route.rsc.tsx # RSC 页面展示统计卡片 │ │ └── route.client.tsx # Client 页面带图表交互 │ └── products/ # 商品管理 │ ├── route.rsc.tsx # RSC 列表页 │ └── $id/ # 动态路由 │ ├── route.rsc.tsx # RSC 详情页 │ └── edit.client.tsx # Client 编辑页 ├── entry.client.tsx # 客户端入口 ├── entry.server.tsx # 服务端入口 └── root.tsx # 根布局RSC关键改造点有三个第一root.tsx的 RSC 化改造默认的root.tsx是一个普通 React 组件我们需要把它变成 RSC以便在服务端注入全局数据// app/root.tsx import { Outlet, Link } from tanstack/react-router; import { Suspense } from react; // 这是一个 RSC 组件可以 await 数据 export default async function Root() { // 从服务端获取菜单数据Prisma 查询 const menus await getAdminMenus(); return ( html langzh-CN head meta charSetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1 / title电商后台/title /head body div classNameadmin-layout nav {menus.map((menu) ( Link key{menu.id} to{menu.path} {menu.name} /Link ))} /nav main {/* Outlet 是客户端渲染区域 */} Suspense fallback{divLoading.../div} Outlet / /Suspense /main /div /body /html ); }第二route.rsc.tsx的数据获取模式TanStack Start 推荐在 RSC 组件中直接await数据而不是用useQuery// app/routes/dashboard/route.rsc.tsx import { db } from /db; // Prisma client 实例 export default async function DashboardRoute() { // 在服务端直接查询无网络 IO 开销 const totalProducts await db.product.count(); const totalOrders await db.order.count(); const recentOrders await db.order.findMany({ take: 5, orderBy: { createdAt: desc }, }); return ( div classNamedashboard h1管理后台/h1 div classNamestats StatCard title商品总数 value{totalProducts} / StatCard title订单总数 value{totalOrders} / /div div classNameorders h2最新订单/h2 {recentOrders.map((order) ( OrderItem key{order.id} order{order} / ))} /div /div ); }第三route.client.tsx的交互增强RSC 渲染完 HTML 后route.client.tsx负责 hydrate 和添加交互// app/routes/dashboard/route.client.tsx import { useQuery } from tanstack/react-query; import { db } from /db; export default function DashboardClient() { // 客户端重新获取实时数据可选 const { data: stats } useQuery({ queryKey: [admin-stats], queryFn: () fetch(/api/admin/stats).then(r r.json()), }); return ( div h2实时统计/h2 {stats ( div classNamerealtime-stats span在线管理员: {stats.onlineAdmins}/span span当前活跃订单: {stats.activeOrders}/span /div )} /div ); }4.3 开发服务器启动与调试技巧启动 Rsbuild 开发服务器只需一条命令pnpm run dev但这条命令背后启动了两个进程Client Server监听http://localhost:2000提供 HMR 和静态资源服务Server Server监听http://localhost:2001提供 SSR 和 API 服务Rsbuild 2.0 会自动配置 Nginx-style 代理当你访问http://localhost:2000/dashboard时Client Server 会把请求转发给 Server Server 的http://localhost:2001/dashboard后者返回预渲染的 HTML。实操心得调试 RSC 时不要只看浏览器 Network 面板。打开http://localhost:2001/__rsbuild/debug/rsc这里会显示所有已注册的 RSC 组件及其编译状态success/failed/time。如果某个.rsc.tsx文件显示failed点击它能看到完整的编译错误堆栈比终端日志更直观。另一个重要调试技巧是环境变量注入。TanStack Start 要求在服务端代码中访问数据库连接字符串但不能硬编码。Rsbuild 2.0 支持.env.server.local文件其中的变量只注入到服务端环境# .env.server.local DATABASE_URLpostgresql://user:passlocalhost:5432/mydb然后在app/entry.server.tsx中可以直接使用// app/entry.server.tsx import { createServerRunner } from tanstack/start; import { createTRPCProxyClient } from trpc/client; import { httpBatchLink } from trpc/client/links/httpBatchLink; export const runner createServerRunner({ // 这里的 DATABASE_URL 会自动从 .env.server.local 注入 db: new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL } } }), });4.4 生产构建与部署流程生产构建分为三步构建客户端资源、构建服务端 bundle、部署。第一步构建客户端资源pnpm run build:client这个命令会执行rsbuild build --modeproduction --envclient生成dist/client/目录包含所有静态资源js/css/html/.rsc。第二步构建服务端 bundlepnpm run build:server这个命令会执行rsbuild build --modeproduction --envserver生成dist/server/目录包含index.js服务端入口和node_modules子集。注意build:server不会打包整个node_modules而是通过rspack的externals机制只打包项目源码和dependencies中的纯 JS 包如prisma/client而把react、express等运行时依赖标记为 external。这样生成的dist/server/目录体积通常只有 8~12MB远小于 Webpack 的全量打包。第三步部署到服务器我们以 PM2 部署为例Node.js 进程管理器# 1. 将 dist/ 目录上传到服务器 scp -r dist/ userserver:/var/www/my-tanstack-app/ # 2. 在服务器上安装生产依赖 cd /var/www/my-tanstack-app pnpm install --prod # 3. 启动服务端进程 pnpm exec pm2 start dist/server/index.js --name tanstack-server # 4. 配置 Nginx 反向代理 # /etc/nginx/conf.d/tanstack.conf upstream tanstack_backend { server 127.0.0.1:2001; } server { listen 80; server_name myapp.com; location / { proxy_pass http://tanstack_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /assets/ { alias /var/www/my-tanstack-app/dist/client/assets/; } }关键点在于location /assets/的静态资源直出配置。因为 Rsbuild 2.0 的assetPrefix已设置为/assets/所以所有 js/css/.rsc 文件都通过 Nginx 直接返回不经过 Node.js极大减轻服务端压力。5. 常见问题与排查技巧实录来自 12 个真实项目的血泪总结5.1 ESM 相关报错的速查表unplugin-auto-import/vite报错只是冰山一角Rsbuild 2.0 下常见的 ESM 问题有 7 类我们整理成速查表报错信息根本原因解决方案验证命令xxx resolved to an esm file第三方包是 ESM但被 CJS 环境 require开启output.esmInterop: truenpx rsbuild build --modedevelopment --envserverCannot use import statement outside a moduleTypeScript 编译后生成了import语句但目标环境是 CJS设置compiler.options.module: ESNextnpx rsbuild inspect --envserver | grep moduleReferenceError: require is not defined浏览器环境执行了require语句检查resolve.alias是否错误地