Nitro 与 Vite RSC 集成实战:在 Nitro 中运行 React Server Components 全栈应用

发布时间:2026/9/15 15:34:27
Nitro 与 Vite RSC 集成实战:在 Nitro 中运行 React Server Components 全栈应用 Nitro 与 Vite RSC 集成实战在 Nitro 中运行 React Server Components 全栈应用【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro本篇指南基于 Nitro 仓库中的 vite-rsc 官方示例完整讲解如何用 Vite 的实验性 RSC 插件vitejs/plugin-rsc在 Nitro 后端之上搭建一套包含 Server Components、Client Components、Server Actions 与流式 SSR 的全栈 React 应用。读完本文你将掌握项目依赖配置、Vite 插件组合方式、三个运行时入口RSC / SSR / Browser的分工协作以及如何调试 RSC 流式载荷与无 JavaScript 环境下的渐进增强行为。一、示例概览这套全栈架构由哪些部分组成examples/vite-rsc示例展示了 Nitro 最典型的 Vite 全栈集成形态Nitro 作为服务端运行时与构建层Vite 的 RSC 插件负责 React Server Components 的编译、序列化与传输协议。整个应用由四类代码协作完成SSR Entryapp/framework/entry.ssr.tsx——处理进入的 HTTP 请求将 RSC 流反序列化后渲染为 HTMLRoot Server Componentapp/root.tsx——以服务端组件形式定义页面结构Client Componentsapp/client.tsx——通过use client指令声明的浏览器交互组件Server Actionsapp/action.tsx——以use server指令导出的服务端函数可从表单或客户端直接调用。示例还额外内置了流式 SSR、RSC 载荷内联注入FLIGHT_DATA、错误边界回退、服务端 HMR、无 JavaScript 渐进增强等进阶能力是理解 RSC 与 Nitro 协作机制的完整教材。完整目录结构如下源码位于仓库的 examples/vite-rsc 目录examples/vite-rsc/ ├── app/ │ ├── assets/ # nitro.svg / react.svg / vite.svg 三枚 Logo │ ├── framework/ # 框架层运行时入口与请求路由约定 │ │ ├── entry.browser.tsx # 浏览器端入口hydration / 客户端导航 / 服务端回调 │ │ ├── entry.rsc.tsx # RSC 环境入口序列化组件树、处理 Server Action │ │ ├── entry.ssr.tsx # SSR 环境入口加载 RSC 入口并渲染 HTML │ │ ├── error-boundary.tsx # 浏览器全局错误边界 │ │ └── request.tsx # RSC/SSR 请求解析与构造约定 │ ├── action.tsx # Server Actions服务端计数器 │ ├── client.tsx # Client Component客户端计数器 │ ├── index.css # 样式被服务端组件直接导入 │ └── root.tsx # Root Server Component ├── package.json ├── tsconfig.json └── vite.config.ts # nitro() rsc() react() 插件组合二、环境准备依赖清单与 TypeScript 配置2.1 package.json需要安装哪些包示例的 package.json 给出了完整依赖组合它采用type: moduleESM与private: true{ name: vitejs/plugin-rsc-examples-starter, version: 0.0.0, private: true, license: MIT, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { react: ^19.2.4, react-dom: ^19.2.4 }, devDependencies: { types/react: ^19.2.14, types/react-dom: ^19.2.3, vitejs/plugin-react: ^6.0.1, vitejs/plugin-rsc: ^0.5.21, nitro: latest, rsc-html-stream: ^0.0.7, vite: latest } }各依赖的职责react/react-dom^19.xRSC 需要 React 19 的并发特性与react-dom/server.edge流式渲染能力vitejs/plugin-rsc核心的 RSC 编译与运行时插件提供serverHandler、entries等配置vitejs/plugin-react提供 JSX 转换与 React Fast RefreshHMRnitroNitro 本体通过nitro/vite子路径以 Vite 插件形式接入rsc-html-stream由 devongovett 编写的工具负责把 RSC 载荷FLIGHT_DATA以script形式注入 HTML 流供浏览器端反序列化vite构建与开发服务器。三个脚本命令直接复用 Vite 的 CLInpm run dev启动开发服务器含 Nitro 开发运行时与 HMRnpm run build执行生产构建npm run preview预览构建产物。2.2 tsconfig.json类型与 JSX 配置tsconfig.json 直接继承 Nitro 提供的nitro/tsconfig基础配置并补充 React 场景所需的选项{ extends: nitro/tsconfig, compilerOptions: { lib: [ESNext, DOM, DOM.Iterable], types: [vite/client, vitejs/plugin-rsc/types], jsx: react-jsx } }要点说明lib同时包含ESNext与DOM/DOM.Iterable因为同一套代码既运行于 Node/边缘运行时SSR、RSC也运行于浏览器客户端组件types中引入vitejs/plugin-rsc/types为import.meta.viteRsc.loadModule、loadBootstrapScriptContent等编译期注入的 API 提供类型jsx: react-jsx使用自动 JSX runtime无需在每个文件显式import Reactapp/client.tsx中仍显式导入React是为了直接使用React.useState。三、Vite 配置nitro() 与 rsc() 插件的组合方式examples/vite-rsc/vite.config.ts 是整个架构的装配点import { defineConfig } from vite; import { nitro } from nitro/vite; import rsc from vitejs/plugin-rsc; import react from vitejs/plugin-react; export default defineConfig({ plugins: [ nitro(), rsc({ serverHandler: false, entries: { ssr: ./app/framework/entry.ssr.tsx, rsc: ./app/framework/entry.rsc.tsx, }, }), react(), ], environments: { client: { build: { rollupOptions: { input: { index: ./app/framework/entry.browser.tsx }, }, }, }, }, });配置中的关键点nitro()Nitro 官方 Vite 插件入口由 Nitro 的 src/vite.ts 导出。它会注册nitro专用环境负责服务端代码的打包、运行时注入与产物生成rsc({ serverHandler: false, ... })关闭 RSC 插件默认的服务端 handler因为服务端逻辑完全交给 Nitro 环境承接并显式声明两个服务端入口——ssr与rscreact()提供 JSX/TSX 转换与客户端 HMRenvironments.client.build.rollupOptions.input把entry.browser.tsx指定为客户端构建入口Vite 会据此产出浏览器 bundle。四、三个运行时入口RSC、SSR 与浏览器如何分工这套架构的精髓在于同一份组件树在不同环境RSC / SSR / Browser中扮演不同角色三者通过vitejs/plugin-rsc提供的运行时 API 通信。4.1 RSC 入口序列化组件树、处理 Server Actionapp/framework/entry.rsc.tsx 导出一个默认的handler(request: Request): PromiseResponse这是 RSC 环境对每个 HTTP 请求的响应函数。它先通过parseRenderRequest区分请求类型export default async function handler(request: Request): PromiseResponse { // 区分 RSC、SSR、Action 等请求 const renderRequest parseRenderRequest(request); request renderRequest.request; // 处理 Server Function 请求 let returnValue: RscPayload[returnValue] | undefined; let formState: ReactFormState | undefined; let temporaryReferences: unknown | undefined; let actionStatus: number | undefined; if (renderRequest.isAction true) { if (renderRequest.actionId) { // 方式一客户端经 setServerCallback 调用带 actionId const contentType request.headers.get(content-type); const body contentType?.startsWith(multipart/form-data) ? await request.formData() : await request.text(); temporaryReferences createTemporaryReferenceSet(); const args await decodeReply(body, { temporaryReferences }); const action await loadServerAction(renderRequest.actionId); try { const data await action.apply(null, args); returnValue { ok: true, data }; } catch (error_) { returnValue { ok: false, data: error_ }; actionStatus 500; } } else { // 方式二form action{...} 在 hydration 之前提交渐进增强如禁用 JS 时 const formData await request.formData(); const decodedAction await decodeAction(formData); try { const result await decodedAction(); formState await decodeFormState(result, formData); } catch { return new Response(Internal Server Error: server action failed, { status: 500, }); } } } ... }随后入口把组件树封装成RscPayload包含root、returnValue、formState通过renderToReadableStream序列化为 RSC 流。这里注释明确说明了设计意图先处理 Server Action 请求、再渲染 RSC 流从而用一次往返同时完成变更服务端状态 获取最新渲染结果。响应分两种路径若renderRequest.isRsc为真直接返回text/x-component类型的 RSC 流即_.rsc端点否则通过import.meta.viteRsc.loadModule(ssr, index)加载 SSR 入口模块调用其renderHTML把 RSC 流渲染成 HTML 返回。RscPayload的类型定义也说明了这套机制的可扩展性export type RscPayload { root: React.ReactNode; // 组件树本示例渲染整个 html 根元素 returnValue?: { ok: boolean; data: unknown }; // 非渐进增强场景下 Server Action 的返回值 formState?: ReactFormState; // 渐进增强场景下 useActionState 的表单状态 };4.2 SSR 入口反序列化并渲染 HTMLapp/framework/entry.ssr.tsx 定义了两个导出默认导出的fetch与具名导出的renderHTML。默认导出fetch负责把请求转发给 RSC 入口执行export default { fetch: async (request: Request) { const rscEntryModule await import.meta.viteRsc.loadModuletypeof import(./entry.rsc)( rsc, index ); return rscEntryModule.default(request); }, };renderHTML则完成RSC 流 → HTML的流水线核心是把同一份 RSC 流 tee 成两份一份用于 SSR 渲染 VDOM另一份注入 HTML 供浏览器端 hydrationexport async function renderHTML(rscStream, options) { // 1. 把一份 RSC 流复制成两份 const [rscStream1, rscStream2] rscStream.tee(); // 2. 在 ReactDOMServer 上下文中反序列化为 React VDOM保证 preinit/preload 生效 function SsrRoot() { payload ?? createFromReadableStreamRscPayload(rscStream1); return React.use(payload).root; } // 3. 传统 SSR渲染 HTML注入 bootstrap 脚本 const bootstrapScriptContent await import.meta.viteRsc.loadBootstrapScriptContent(index); let htmlStream: ReadableStreamUint8Array; try { htmlStream await renderToReadableStream(SsrRoot /, { bootstrapScriptContent: options?.debugNoJS ? undefined : bootstrapScriptContent, nonce: options?.nonce, formState: options?.formState, }); } catch { // 4. 兜底SSR 失败时渲染空壳浏览器端以纯 CSR 重放并触发错误边界 status 500; htmlStream await renderToReadableStream(html.../html, { bootstrapScriptContent: self.__NO_HYDRATE1; (options?.debugNoJS ? : bootstrapScriptContent), }); } // 5. 把第二份 RSC 流注入 HTML 流script...FLIGHT_DATA.../script let responseStream htmlStream; if (!options?.debugNoJS) { responseStream responseStream.pipeThrough(injectRSCPayload(rscStream2, { nonce: options?.nonce })); } return { stream: responseStream, status }; }这段代码覆盖了三种重要边界场景正常路径HTML 流中内联注入 RSC 载荷浏览器首屏即可createFromReadableStream恢复完整组件树SSR 异常返回 500 状态 空壳 HTML设置self.__NO_HYDRATE1让浏览器跳过 hydration 直接createRoot渲染从而能重放服务端组件错误并交给错误边界处理debugNoJS?__nojs不注入 bootstrap 脚本也不注入 RSC 载荷用来模拟禁用 JavaScript 的浏览器验证 Server Action 的渐进增强路径。4.3 浏览器入口hydration、客户端导航与服务端回调app/framework/entry.browser.tsx 是纯客户端代码负责恢复初始 RSC 载荷从 HTML 中注入的rscStream通过createFromReadableStream反序列化得到初始RscPayload作为 React state 的初值hydration正常情况下用hydrateRoot(document, browserRoot, { formState })挂载检测到__NO_HYDRATE in globalThis时退化为createRoot(document).render(...)纯 CSR客户端导航listenNavigation拦截popstate、pushState、replaceState与a点击触发fetchRscPayload()重新拉取 RSC 载荷实现无整页刷新的软导航Server Action 调用通过setServerCallback注册回调React 在 hydration 后调用 Server Function 时会构造带x-rsc-action头与encodeReply编码参数体的 POST 请求再把返回的载荷更新到组件树setServerCallback(async (id, args) { const temporaryReferences createTemporaryReferenceSet(); const renderRequest createRscRenderRequest(globalThis.location.href, { id, body: await encodeReply(args, { temporaryReferences }), }); const payload await createFromFetchRscPayload(fetch(renderRequest), { temporaryReferences, }); setPayload(payload); const { ok, data } payload.returnValue!; if (!ok) throw data; return data; });服务端 HMR监听import.meta.hot的rsc:update事件服务端组件代码变更时自动重新拉取 RSC 载荷并重渲染对应页面上的提示Editsrc/root.tsxto test server HMR。五、组件分层Server Components、Server Actions 与 Client Components5.1 Root Server Componentapp/root.tsxapp/root.tsx 是服务端组件只在服务器上运行。它可以直接导入 CSS、直接读取服务端数据、调用 Server Actions并把接收到的URL对象作为 props 渲染进页面import ./index.css; // 服务端组件中导入的 CSS 会被自动注入 import viteLogo from ./assets/vite.svg; import { getServerCounter, updateServerCounter } from ./action.tsx; import reactLogo from ./assets/react.svg; import nitroLogo from ./assets/nitro.svg; import { ClientCounter } from ./client.tsx; export function Root(props: { url: URL }) { return ( html langen head meta charSetUTF-8 / link relicon typeimage/svgxml href/vite.svg / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleNitro Vite RSC/title /head body App {...props} / /body /html ); } function App(props: { url: URL }) { return ( div idroot div{/* 三枚 Logo */}/div h1Vite RSC Nitro/h1 div classNamecard ClientCounter / /div div classNamecard form action{updateServerCounter.bind(null, 1)} buttonServer Counter: {getServerCounter()}/button /form /div div classNamecardRequest URL: {props.url?.href}/div ul classNameread-the-docs liEdit codesrc/client.tsx/code to test client HMR./li liEdit codesrc/root.tsx/code to test server HMR./li liVisit a href./_.rsccode_.rsc/code/a to view RSC stream payload./li liVisit a href?__nojscode?__nojs/code/a to test server action without js enabled./li /ul /div ); }值得注意的两个细节服务端组件把整个html元素作为组件树渲染/序列化这是一种框架约定——注释表明完全可以根据自己的路由约定只渲染/拉取组件树的局部页面把调试入口直接暴露在 UI 中_.rsc查看 RSC 流载荷、?__nojs模拟禁用 JS。5.2 Server Actionsapp/action.tsxapp/action.tsx 用use server指令声明服务端函数。模块顶部的serverCounter变量保存在服务器进程内从而演示跨请求保持的服务端状态use server; let serverCounter 0; export async function getServerCounter() { return serverCounter; } export async function updateServerCounter(change: number) { serverCounter change; }这两种函数分别对应两种调用路径getServerCounter()在root.tsx中作为 server component 的渲染数据源直接读取updateServerCounter.bind(null, 1)绑定为form action{...}在 hydration 之前或禁用 JS 时走渐进增强路径表单提交 →entry.rsc.tsx中decodeAction(formData)→ 执行 action →decodeFormState解码表单状态 → 重新渲染 RSC 流返回新 HTML。hydration 之后则走setServerCallback的 fetch 路径。5.3 Client Componentapp/client.tsxapp/client.tsx 用use client指令声明客户端组件拥有自己的useState交互状态use client; import React from react; export function ClientCounter() { const [count, setCount] React.useState(0); return button onClick{() setCount((count) count 1)}Client Counter: {count}/button; }遵循 RSC 的边界规则服务端组件可以导入并渲染客户端组件但客户端组件不能导入服务端组件。root.tsx中ClientCounter虽然被服务端组件导入但它会在客户端打包并 hydration从而拥有完整的浏览器交互能力。5.4 浏览器错误边界app/framework/error-boundary.tsxerror-boundary.tsx 同样以use client声明实现全局错误兜底。它参考了 Next.js 的error-boundary实现当渲染抛错时展示Caught an unexpected error页面并提供一个 Reset 按钮通过React.startTransition(() props.reset())重置错误状态。开发环境下会展示props.error.message用import.meta.env.DEV保护生产环境则只显示占位文本避免泄露堆栈细节。六、请求路由约定如何区分 RSC、SSR 与 Actionapp/framework/request.tsx 定义了这套 demo 的框架约定注释明确指出这些是arbitrary choices可按需自定义用 URL 后缀_.rsc区分 RSC 请求与普通 SSR 请求用x-rsc-action请求头传递 Server Action ID。const URL_POSTFIX _.rsc; const HEADER_ACTION_ID x-rsc-action; type RenderRequest { isRsc: boolean; // 请求是否应返回 RSC 载荷URL 以 _.rsc 结尾 isAction: boolean; // 是否为 Server Action 调用POST 请求 actionId?: string; // 来自 x-rsc-action 头的 Server Action ID request: Request; // 去除 _.rsc 后缀后的规范化 Request url: URL; // 去除 _.rsc 后缀后的规范化 URL };createRscRenderRequest(urlString, action?)客户端侧构造 RSC 请求。普通导航时为当前 URL 追加_.rsc后缀并发 GET调用 Server Action 时额外带上x-rsc-action头、POST 方法与编码后的请求体parseRenderRequest(request)服务端侧解析请求。URL 以_.rsc结尾则判定为 RSC 请求并剥离后缀、读取 action IDPOST 且无 action ID 时会抛出Missing action id header for RSC action request错误。entry.rsc.tsx正是基于这个结果决定返回 RSC 流还是交给 SSR 渲染 HTML。七、运行与调试指南在仓库的 examples/vite-rsc 目录下按序执行# 安装依赖需要 pnpm / npm 均可 pnpm install # 开发模式启动 Vite 开发服务器 Nitro 开发运行时 pnpm dev # 生产构建输出服务端与客户端产物 pnpm build # 预览生产构建结果 pnpm preview开发模式下可以验证四类行为操作预期行为点击Client Counter按钮纯客户端useState计数触发客户端 HMR 链路点击Server Counter表单按钮经 Server Action 在服务器端累加serverCounter一次往返后重渲染访问http://localhost:5173/_.rsc直接查看text/x-component的 RSC 流载荷访问http://localhost:5173/?__nojs模拟禁用 JavaScript验证表单渐进增强路径Server Action 仍可工作编辑app/root.tsx触发rsc:update服务端 HMR页面自动重拉 RSC 载荷编辑app/client.tsx触发客户端 Fast RefreshHMR八、Nitro 侧的集成原理服务环境与运行时通信从源码层看nitro()插件由 src/vite.ts 导出之所以能承载 RSC 这类多环境架构是因为 Nitro 的 Vite 插件实现了服务环境service environment机制。在 src/build/vite/services.ts 中viteServicesTemplate会为每个注册的服务环境例如rsc、ssr生成viteServices模块开发模式从globalThis.__nitro_vite_envs__动态读取对应环境的运行时实例生产模式为每个服务生成lazyService(() import(...))惰性加载包装保证mod.fetch(req)在首次请求时才加载对应环境的入口模块。同时nitroDevServiceProxy插件处理开发期的模块代理服务环境如 SSR不应打包自己的一份nitro/*运行时而是通过__VITE_ENVIRONMENT_RUNNER_IMPORT__(nitro, id)代理到 Nitro 环境生产构建时则通过createServiceEnvironment外部化处理。这正是entry.ssr.tsx中import.meta.viteRsc.loadModule(rsc, index)能在不同环境间加载模块的底层支撑——RSC 插件负责协议与序列化Nitro 负责服务环境的编排、模块加载与运行时隔离。九、总结与延伸阅读通过examples/vite-rsc示例可以提炼出在 Nitro 上落地 RSC 应用的完整范式装配vite.config.ts中nitro()rsc()react()三插件组合serverHandler: false将服务端逻辑完全交给 Nitro分层entry.rsc序列化与 Action、entry.ssrHTML 渲染与载荷注入、entry.browserhydration、导航、回调三入口各司其职约定_.rsc后缀 x-rsc-action头构成 RSC/SSR/Action 的路由区分边界use server与use client指令划分执行环境错误边界与空壳回退保证健壮性调试_.rsc与?__nojs两个内置调试端点覆盖了 RSC 流查看与渐进增强验证两大场景。如需继续深入可参考仓库内的相关材料Examples 索引、Vite 集成文档以及 Nitro 官方提供的其他 Vite 全栈示例如 vite-ssr-react、vite-ssr-vue-router、vite-trpc对比不同框架在 Nitro 上统一的 SSR 接入方式。【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询