首页
/ React Router RSC 双模式实践:从 unstable_reactRouterRSC 插件到 matchRSCServerRequest 底层实现

React Router RSC 双模式实践:从 unstable_reactRouterRSC 插件到 matchRSCServerRequest 底层实现

2026-09-05 15:19:40作者:袁立春Spencer

React Router 的 React Server Components(RSC)支持目前处于不稳定(unstable)阶段,分为 RSC Framework Mode 与 RSC Data Mode 两种变体。本文围绕这两个模式展开:如何识别项目中已启用的 RSC 变体、RSC 路由模块与普通路由模块的差异(ServerComponentServerErrorBoundary 等导出)、客户端/服务端边界划分,以及 Data Mode 下三个入口(RSC Server / SSR Server / Browser)的完整接线方式,并对照开源仓库中的默认入口实现与 matchRSCServerRequest 源码,讲清每个 API 在请求链路中的真实作用。

RSC 支持的两种变体

React Router 的 RSC 支持是实验性的,API 可能在 minor/patch 版本中发生破坏性变更。官方文档在 react-server-components.md 中明确标注了 unstable: true,并提示使用者需密切关注发布说明。RSC 指 React 19 起提供的一类在打包前、独立于客户端与 SSR 服务端的环境中预渲染的组件,其核心概念是 Server Components 与 Server Functions("use client" / "use server" 指令)。

在本仓库中,RSC 支持存在两个层次:

  • RSC Framework Mode:Framework Mode 加上不稳定的 RSC Vite 插件(unstable_reactRouterRSC),并搭配实验性的 @vitejs/plugin-rsc
  • RSC Data Mode:更底层的 RSC 运行时 API 与手动打包器/服务器集成,由 RSC Framework Mode 的 API 构建在其之上。

阅读顺序建议(也是仓库技能参考文档 .agents/skills/react-router/references/rsc.md 给出的路径):先读 react-server-components.md,再读对应基础模式的文档 Framework ModeData Mode。RSC 文档描述的是与非 RSC 模式的差异,而非重复全部 Framework/Data 概念,因此两层知识都要放在脑子里。

识别 RSC Framework Mode

在接触一个已有项目时,先判断它是否使用了 RSC Framework Mode。识别信号:

  • @react-router/dev/vite 导入 unstable_reactRouterRSC
  • 依赖中出现 @vitejs/plugin-rsc
  • vite.config.ts 中形如 plugins: [reactRouterRSC(), rsc()]
  • Framework 路由模块中出现 RSC 路由导出(ServerComponent 等);
  • 存在 RSC 入口文件,如 app/entry.rsc.ts

从源码结构看,该插件在 vite.ts 中仅有一行导出:

export { reactRouterRSCVitePlugin as unstable_reactRouterRSC } from "./vite/rsc/plugin";

unstable_reactRouterRSC 是一个独立的 Vite 插件实现,而非普通 reactRouter() 插件的选项分支。RSC Framework Mode 使用的是不同的 Vite 插件,不能把它替换成普通的 reactRouter() 插件。

标准配置如下(@vitejs/plugin-rsc 必须放在 React Router RSC 插件之后):

import { defineConfig } from "vite";
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
import rsc from "@vitejs/plugin-rsc";

export default defineConfig({
  plugins: [reactRouterRSC(), rsc()],
});

RSC Framework Mode 详解

构建产物

RSC Framework Mode 的服务端构建文件(build/server/index.js)导出一个 default 请求处理函数((request: Request) => Promise<Response>),同时处理文档请求与数据请求。如需把它挂到标准 Node.js http.createServer 或 Express 上,可用 @remix-run/node-fetch-servercreateRequestListener

import express from "express";
import requestHandler from "./build/server/index.js";
import { createRequestListener } from "@remix-run/node-fetch-server";

const app = express();

app.use(
  "/assets",
  express.static("build/client/assets", {
    immutable: true,
    maxAge: "1y",
  }),
);
app.use(express.static("build/client"));
app.use(createRequestListener(requestHandler));
app.listen(3000);

Loaders/Actions 返回 React 元素

在 RSC Framework Mode 中,loaders 和 actions 除了普通数据外还可以返回 React 元素,这些元素只在服务端渲染

import type { Route } from "./+types/route";

export async function loader() {
  return {
    message: "Message from the server!",
    element: <p>Element from the server!</p>,
  };
}

export default function Route({
  loaderData,
}: Route.ComponentProps) {
  return (
    <>
      <h1>{loaderData.message}</h1>
      {loaderData.element}
    </>
  );
}

如果返回的元素中需要用到客户端特性(Hooks、事件处理器),必须把这部分提取到 "use client" 客户端模块:

"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
import type { Route } from "./+types/route";
import { Counter } from "./counter";

export async function loader() {
  return {
    message: "Message from the server!",
    element: (
      <>
        <p>Element from the server!</p>
        <Counter />
      </>
    ),
  };
}

export default function Route({
  loaderData,
}: Route.ComponentProps) {
  return (
    <>
      <h1>{loaderData.message}</h1>
      {loaderData.element}
    </>
  );
}

路由 Server Components

当路由导出 ServerComponent 而不是通常的 default 组件时,该路由在服务端渲染而不是客户端。同一角色下,路由模块不能同时导出 defaultServerComponent。客户端专属的注解(如 clientLoaderclientAction)可以与 ServerComponent 并存;其余组件导出遵循同一套客户端/服务端划分:ErrorBoundaryLayoutHydrateFallback 是客户端组件,而 ServerErrorBoundaryServerLayoutServerHydrateFallback 在服务端渲染。

互斥的成对导出如下:

Server Component 导出 客户端组件导出
ServerComponent default
ServerErrorBoundary ErrorBoundary
ServerLayout Layout
ServerHydrateFallback HydrateFallback
import type { Route } from "./+types/route";
import { Outlet } from "react-router";
import { getMessage } from "./message";

export async function loader() {
  return {
    message: await getMessage(),
  };
}

export function ServerComponent({
  loaderData,
}: Route.ServerComponentProps) {
  return (
    <>
      <h1>Server Component Route</h1>
      <p>Message from the server: {loaderData.message}</p>
      <Outlet />
    </>
  );
}

服务端优先路由中如需客户端能力,同样把带 Hooks 的部分提取为 "use client" 模块(如上一节的 Counter),再在 ServerComponent 中引入。

.server/.client 模块不再内置

为避免与 RSC 的 "use server""use client" 指令混淆,使用 RSC Framework Mode 时,.server 模块.client 模块的文件命名约定不再内置支持。推荐改用 @vitejs/plugin-rsc 提供的 "server-only""client-only" 边界导入,在模块内以副作用方式导入以保证构建期校验(无需安装官方 npm 包 server-only/client-only@vitejs/plugin-rsc 会在内部处理这些导入,用构建期校验替代运行时报错):

import "server-only";

// Rest of the module...

如果存量代码依赖 .server/.client 命名约定,推荐直接使用 vite-env-only 插件做快速迁移,例如确保 .server 模块不会误入客户端构建:

import { defineConfig } from "vite";
import { denyImports } from "vite-env-only";
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
import rsc from "@vitejs/plugin-rsc";

export default defineConfig({
  plugins: [
    denyImports({
      client: { files: ["**/.server/*", "**/*.server.*"] },
    }),
    reactRouterRSC(),
    rsc(),
  ],
});

MDX 路由支持

使用 @mdx-js/rollup v3.1.1+ 时,RSC Framework Mode 支持 MDX 路由。注意 MDX 路由中导出的任何组件都必须是 RSC 环境合法的(不能使用 Hooks 等客户端特性),需要这些特性的组件应提取到 "use client" 模块。

自定义入口文件

RSC Framework Mode 支持自定义入口,可分别定制 RSC 服务器、SSR 服务器与客户端入口。插件会自动在 app 目录探测以下文件,找不到则使用框架默认入口:

  • app/entry.rsc.ts(或 .tsx)—— 自定义 RSC 服务器入口;
  • app/entry.ssr.ts(或 .tsx)—— 自定义 SSR 服务器入口;
  • app/entry.client.tsx —— 自定义客户端入口。

想查看框架生成的默认入口,可以用 react-router reveal entry.clientreact-router reveal entry.rscreact-router reveal entry.ssr。默认入口的源码就在本仓库的 default-rsc-entries 目录 中,构建后可在 node_modules/@react-router/dev/dist/config/default-rsc-entries/ 直接查看:

基本覆盖模式:自定义入口可以包装或扩展现有默认行为。例如为 RSC 入口增加日志:

import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc";
import { RouterContextProvider } from "react-router";

export default {
  fetch(request: Request): Promise<Response> {
    console.log(
      "Custom RSC entry handling request:",
      request.url,
    );

    const requestContext = new RouterContextProvider();

    return defaultEntry.fetch(request, requestContext);
  },
};

if (import.meta.hot) {
  import.meta.hot.accept();
}

SSR 入口同理:

import { generateHTML as defaultGenerateHTML } from "@react-router/dev/config/default-rsc-entries/entry.ssr";

export function generateHTML(
  request: Request,
  serverResponse: Response,
): Promise<Response> {
  console.log(
    "Custom SSR entry generating HTML for:",
    request.url,
  );

  return defaultGenerateHTML(request, serverResponse);
}

客户端入口则可以直接引用默认实现:

import "@react-router/dev/config/default-rsc-entries/entry.client";

复制默认入口做深度定制:在 IDE 中对默认入口的导入使用"跳转到定义",把默认入口代码复制到自己的文件里再修改即可。复制时必须保留必需的导出约定:

  • entry.rsc.ts 必须导出带 fetch 方法的默认对象;
  • entry.ssr.ts 必须导出 generateHTML 函数;
  • entry.client.tsx 负责处理客户端 hydration。

对照仓库中的默认实现可以看到这条链路:entry.rsc.tsx 的默认导出是 async fetch(request, requestContext?),内部通过 import.meta.viteRsc.loadModule 动态加载 SSR 模块并调用 ssr.generateHTML(request, await fetchServer(request, requestContext))fetchServer 则调用 matchRSCServerRequest 并传入 virtual:react-router/unstable_rsc/routes 等由插件生成的虚拟模块(路由、route discovery、basename、clientVersion)。而 entry.ssr.tsxgenerateHTML 调用 routeRSCServerRequest,在其 renderHTML 回调中用 renderToReadableStream<RSCStaticRouter getPayload={getPayload} /> 渲染为 HTML 流。这印证了文档中"一个请求先经 RSC 服务器生成 payload、再由 SSR 入口转成 HTML"的两段式结构。

不支持的配置项

以下 react-router.config.ts 选项目前不支持 RSC Framework Mode:

  • buildEnd
  • presets
  • serverBundles
  • splitRouteModules

RSC Data Mode

RSC Framework Mode 的 API 构建在更底层的 Data Mode API 之上。Data Mode 缺少 routes.ts 配置与文件系统路由、HMR 与 Hot Data Revalidation 等特性,但更灵活,允许接入自己的打包器与服务器抽象。Data Mode 比 Framework Mode 更手动:在改动路由或入口之前,先摸清应用现有的打包器与服务器抽象。识别信号包括:unstable_RSCRouteConfig、传给底层 RSC API 的路由配置、unstable_matchRSCServerRequest / unstable_routeRSCServerRequest / unstable_RSCHydratedRouter / unstable_RSCStaticRouter 等 API、围绕 RSC 的自定义打包器/服务器设置。

配置路由

路由以参数形式传给 matchRSCServerRequest。最小需求是 path 与 component:

function Root() {
  return <h1>Hello world</h1>;
}

matchRSCServerRequest({
  // ...other options
  routes: [{ path: "/", Component: Root }],
});

虽然可以内联定义组件,但出于启动性能与代码组织考虑,官方推荐使用 lazy() 选项并遵循 Route Module 约定。RSC 路由配置中的 lazy 字段期望与 Route Module API 相同的导出,从而保持路由模块形状在 Framework Mode 与 RSC Data Mode 之间一致——包括 loaderactionmetalinksheadersErrorBoundaryHydrateFallback 以及各类客户端注解。

类型定义 RSCRouteConfigserver.rsc.ts 中:每条 RSCRouteConfigEntryid(必填)、可选 pathlazy 返回的模块中 defaultComponent 二选一(类型上用 never 互斥),且 index: truechildren 互斥:

import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";

export function routes() {
  return [
    {
      id: "root",
      path: "",
      lazy: () => import("./root/route"),
      children: [
        {
          id: "home",
          index: true,
          lazy: () => import("./home/route"),
        },
        {
          id: "about",
          path: "about",
          lazy: () => import("./about/route"),
        },
      ],
    },
  ] satisfies RSCRouteConfig;
}

Server Component 路由

Data Mode 下,每条路由的 default 导出默认渲染为 Server Component:

export default function Home() {
  return (
    <main>
      <article>
        <h1>Welcome to React Router RSC</h1>
        <p>
          You won't find me running any JavaScript in the
          browser!
        </p>
      </article>
    </main>
  );
}

Server Components 的一个特性是可以把组件写成异步函数从而直接在服务端取数

export default async function Home() {
  let user = await getUserData();

  return (
    <main>
      <article>
        <h1>Welcome to React Router RSC</h1>
        <p>
          You won't find me running any JavaScript in the
          browser!
        </p>
        <p>
          Hello, {user ? user.name : "anonymous person"}!
        </p>
      </article>
    </main>
  );
}

官方文档同时提示:Server Components 也可以从 loaders/actions 中返回;在全面使用 RSC 构建应用时,loader 主要用于设置 status 码或返回 redirect,而在 loader 中使用 Server Components 对渐进式引入 RSC 很友好。

Server Functions

Server Functions 是 React 提供的服务端异步函数特性,以 "use server" 指令定义:

"use server";

export async function updateFavorite(formData: FormData) {
  let movieId = formData.get("id");
  let intent = formData.get("intent");
  if (intent === "add") {
    await addFavorite(Number(movieId));
  } else {
    await removeFavorite(Number(movieId));
  }
}
import { updateFavorite } from "./action.ts";
export async function AddToFavoritesForm({
  movieId,
}: {
  movieId: number;
}) {
  let isFav = await isFavorite(movieId);
  return (
    <form action={updateFavorite}>
      <input type="hidden" name="id" value={movieId} />
      <input
        type="hidden"
        name="intent"
        value={isFav ? "remove" : "add"}
      />
      <AddToFavoritesButton isFav={isFav} />
    </form>
  );
}

Server Functions 被调用后,React Router 会自动重新验证(revalidate)路由并用新的服务端内容更新 UI,无需手动处理缓存失效。这一行为在源码中对应 processServerAction:当请求带 rsc-action-id 头时走增强型 server action 路径(decodeReply + loadServerAction);当是带 $ACTION_ 前缀字段的表单请求时走 decodeAction 路径,两者最终都产出一个 revalidation request。

Client Properties

路由在运行时于服务端定义,但借助 client references 与 "use client" 仍可提供 clientLoaderclientActionshouldRevalidate

"use client";

export function clientAction() {}

export function clientLoader() {}

export function shouldRevalidate() {}

export default function ClientRoot() {
  return <p>Client route</p>;
}

然后在懒加载的路由模块中重新导出:

export {
  clientAction,
  clientLoader,
  shouldRevalidate,
} from "./client";

export default function Root() {
  // ...
}

这也把整条路由变成 Client Component 的方式(此时路由的 default 导出需要保留一个 Server Component 来承载客户端组件,这也是使用 CSS 副作用导入时打包器的硬性要求):

import { default as ClientRoot } from "./route.client";
export {
  clientAction,
  clientLoader,
  shouldRevalidate,
} from "./client";

export default function Root() {
  // Adding a Server Component at the root is required by bundlers
  // if you're using css side-effects imports.
  return <ClientRoot />;
}

打包器接线:三个入口

React Router 提供的 API 让你容易把 Data Mode 接入 RSC 兼容的打包器(例如构建自己的 custom framework)。以"SSR + SPA 导航 hydration"的典型接线为例,需要配置三样东西:

  1. 处理入站请求、获取 RSC payload 并转成 HTML 的服务器;
  2. 生成 RSC payload 的 React 服务器;
  3. 客户端浏览器处理器:hydrate 生成的 HTML,并设置 callServer 以支持 hydration 之后的 server actions。

命名约定仅为熟悉起见,可以自行命名与配置。"React Server"与请求处理服务器不必是两个独立进程——可以是同一服务器内的两个独立模块图,这很重要,因为 React 在生成 RSC payload 与生成客户端 HTML 时行为不同。

入口 职责 相关 API
entry.ssr.tsx(Server) 处理请求、调用 RSC 服务器、把 payload 转为 HTML routeRSCServerRequestRSCStaticRouter
entry.rsc.tsx(RSC Server) 把请求匹配到路由并生成 RSC payload matchRSCServerRequest
entry.browser.tsx(Browser) hydrate HTML,设置 callServer createCallServergetRSCStreamRSCHydratedRouter

提示:SSR 并非必须。可以用 RSC 预渲染 HTML 用于 SSG 或 ISR。

以下示例使用 Express 与 @remix-run/node-fetch-server。Vite 侧除了 reactreact-domreact-router,还需要:

npm i -D vite @vitejs/plugin-react @vitejs/plugin-rsc

vite.config.ts

import rsc from "@vitejs/plugin-rsc/plugin";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    react(),
    rsc({
      entries: {
        client: "src/entry.browser.tsx",
        rsc: "src/entry.rsc.tsx",
        ssr: "src/entry.ssr.tsx",
      },
    }),
  ],
});

src/entry.ssr.tsx

import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
import { renderToReadableStream as renderHTMLToReadableStream } from "react-dom/server.edge";
import {
  unstable_routeRSCServerRequest as routeRSCServerRequest,
  unstable_RSCStaticRouter as RSCStaticRouter,
} from "react-router";

export async function generateHTML(
  request: Request,
  serverResponse: Response,
): Promise<Response> {
  return await routeRSCServerRequest({
    // The incoming request.
    request,
    // The React Server response
    serverResponse,
    // Provide the React Server touchpoints.
    createFromReadableStream,
    // Render the router to HTML.
    async renderHTML(getPayload, options) {
      const payload = await getPayload();
      const formState =
        payload.type === "render"
          ? await payload.formState
          : undefined;

      const bootstrapScriptContent =
        await import.meta.viteRsc.loadBootstrapScriptContent(
          "index",
        );

      return await renderHTMLToReadableStream(
        <RSCStaticRouter getPayload={getPayload} />,
        {
          ...options,
          bootstrapScriptContent,
          formState,
          signal: request.signal,
        },
      );
    },
  });
}

src/entry.rsc.tsx

import {
  createTemporaryReferenceSet,
  decodeAction,
  decodeFormState,
  decodeReply,
  loadServerAction,
  renderToReadableStream,
} from "@vitejs/plugin-rsc/rsc";
import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router";

import { routes } from "./routes/config";

function fetchServer(request: Request) {
  return matchRSCServerRequest({
    // Provide the React Server touchpoints.
    createTemporaryReferenceSet,
    decodeAction,
    decodeFormState,
    decodeReply,
    loadServerAction,
    // The incoming request.
    request,
    // The app routes.
    routes: routes(),
    // Encode the match with the React Server implementation.
    generateResponse(match, options) {
      return new Response(
        renderToReadableStream(match.payload, options),
        {
          status: match.statusCode,
          headers: match.headers,
        },
      );
    },
  });
}

export default async function handler(request: Request) {
  // Import the generateHTML function from the client environment
  const ssr = await import.meta.viteRsc.loadModule<
    typeof import("./entry.ssr")
  >("ssr", "index");

  return ssr.generateHTML(
    request,
    await fetchServer(request),
  );
}

src/entry.browser.tsx

import {
  createFromReadableStream,
  createTemporaryReferenceSet,
  encodeReply,
  setServerCallback,
} from "@vitejs/plugin-rsc/browser";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import {
  unstable_createCallServer as createCallServer,
  unstable_getRSCStream as getRSCStream,
  unstable_RSCHydratedRouter as RSCHydratedRouter,
  type unstable_RSCPayload as RSCPayload,
} from "react-router/dom";

// Create and set the callServer function to support post-hydration server actions.
setServerCallback(
  createCallServer({
    createFromReadableStream,
    createTemporaryReferenceSet,
    encodeReply,
  }),
);

// Get and decode the initial server payload.
createFromReadableStream<RSCPayload>(getRSCStream()).then(
  (payload) => {
    startTransition(async () => {
      const formState =
        payload.type === "render"
          ? await payload.formState
          : undefined;

      hydrateRoot(
        document,
        <StrictMode>
          <RSCHydratedRouter
            createFromReadableStream={
              createFromReadableStream
            }
            payload={payload}
          />
        </StrictMode>,
        {
          formState,
        },
      );
    });
  },
);

从服务端源码看这条链路的数据形态:RSCPayload 是四种载荷的联合类型——render(含 matchesloaderDataerrorsactionDataformState)、manifest(懒路由发现的补丁)、actionredirectmatchRSCServerRequest 实现(server.rsc.ts)会先归一化 .rsc / _.rsc 后缀的数据请求 URL,判断 manifest 请求与数据请求,再经由 createStaticHandler 走标准的 loader/action 查询流程,最终把结果封装为 generateResponse 可渲染的 RSCMatchstatusCode + headers + payload)。仓库中 playground/rsc-vite-framework 是一个可直接运行的 Data Mode 示例,integration/rsc/rsc-test.ts 等集成测试则覆盖了 nojs、prerender 等场景。

CSP Nonce 配置

CSP 可以用每次响应独立的 nonce 来允许 RSC hydration 所需的内联脚本,同时禁止任意内联脚本。nonce 是 HTML 层面的问题,应配置在 entry.ssr.tsx 中,不需要传给 matchRSCServerRequest,也不应出现在 RSC payload 里。

RSC Framework Mode 下先运行 react-router reveal entry.ssr 生成自定义 SSR 入口;Data Mode 下直接更新现有 SSR 入口。每个文档响应生成新 nonce,并传给 routeRSCServerRequestRSCStaticRouter 与 CSP 响应头:

export async function generateHTML(
  request: Request,
  serverResponse: Response,
): Promise<Response> {
  const nonce = crypto.randomUUID();

  const response = await routeRSCServerRequest({
    request,
    serverResponse,
    createFromReadableStream,
    nonce,
    async renderHTML(getPayload, options) {
      const payload = getPayload();
      const bootstrapScriptContent =
        await import.meta.viteRsc.loadBootstrapScriptContent(
          "index",
        );

      return renderHTMLToReadableStream(
        <RSCStaticRouter
          getPayload={getPayload}
          nonce={options.nonce}
        />,
        {
          ...options,
          bootstrapScriptContent,
          formState: await payload.formState,
          signal: request.signal,
        },
      );
    },
  });

  response.headers.set(
    "Content-Security-Policy",
    `script-src 'self' 'nonce-${nonce}'`,
  );
  return response;
}

三个传递路径的作用:routeRSCServerRequestnonce 选项把它应用到把 RSC payload 转移进 HTML 文档的内联脚本;把 renderHTMLoptions 展开进 renderHTMLToReadableStream 使其应用到 React 生成的脚本;传给 RSCStaticRouter 则让 <Links><ScrollRestoration> 这类感知 nonce 的组件默认使用它。默认 RSC Framework 入口不生成 nonce,只有当应用同时发送匹配 CSP 头时才应生成;对静态预渲染页面,优先使用 CSP hash 或外部脚本而非每响应 nonce。

客户端/服务端边界与数据加载

RSC 代码必须遵守 React 的客户端/服务端划分:

  • 需要 Hooks、浏览器 API 或事件处理器的组件使用 "use client"
  • 服务端数据访问与密钥使用 server-only 模块;
  • RSC Framework Mode 下优先使用文档描述的 server-onlyclient-only 边界导入;
  • 不要假设 .server/.client 文件命名约定在 RSC Framework Mode 下行为一致,依赖这些约定前先读 RSC 文档。

RSC 改变了数据可以加载的位置:Server Components 可以直接在服务端取数;loaders/actions 仍然存在且可带 RSC 特有行为(如返回 React 元素、配合 useActionStateformState);客户端组件仍然只能拿到客户端安全的数据,不能直接访问 server-only 模块。在"Server Component 直接取数、loader、客户端 loader/action"之间做选择时,跟随 RSC 文档并与应用既有模式保持一致。

稳定性检查清单

RSC API 被明确标记为 unstable。在实现或重构 RSC 代码之前:

  • 确认已安装的 React Router 版本(本仓库 unstable_ 前缀的 API 可能在 minor/patch 版本发生破坏性变更);
  • 确认已安装的 @vitejs/plugin-rsc 版本(实验性插件,需与 RSC 入口 API 匹配);
  • 阅读应用现有的 RSC 入口/配置文件(app/entry.rsc.tsapp/entry.ssr.tsvite.config.ts);
  • 优先做符合当前模式的最小改动;
  • RSC 路由模块中同一角色的成对导出(default/ServerComponentErrorBoundary/ServerErrorBoundaryLayout/ServerLayoutHydrateFallback/ServerHydrateFallback)互斥,添加这些导出前先读 RSC 文档
登录后查看全文
热门项目推荐
相关项目推荐