UNPKG

29.1 kB Markdown View Raw
1---
2title: React Server Components
3unstable: true
4---
5
6# React Server Components
7
8[MODES: framework, data]
9
10<br/>
11<br/>
12
13<docs-warning>React Server Components support is experimental and subject to breaking changes in
14minor/patch releases. Please use with caution and pay **very** close attention
15to release notes for relevant changes.</docs-warning>
16
17React Server Components (RSC) refers generally to an architecture and set of APIs provided by React since version 19.
18
19From the docs:
20
21> Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.
22>
23> <cite>- [React "Server Components" docs][react-server-components-doc]</cite>
24
25React Router provides a set of APIs for integrating with RSC-compatible bundlers, allowing you to leverage [Server Components][react-server-components-doc] and [Server Functions][react-server-functions-doc] in your React Router applications.
26
27If you're unfamiliar with these React features, we recommend reading the official [Server Components documentation][react-server-components-doc] before using React Router's RSC APIs.
28
29RSC support is available in both Framework and Data Modes. For more information on the conceptual difference between these, see ["Picking a Mode"][picking-a-mode]. However, note that the APIs and features differ between RSC and non-RSC modes in ways that this guide will cover in more detail.
30
31## Quick Start
32
33The quickest way to get started is with one of our templates.
34
35These templates come with React Router RSC APIs already configured, offering you out of the box features such as:
36
37- Server Side Rendering (SSR)
38- Server Components
39- Client Components (via [`"use client"`][use-client-docs] directive)
40- Server Functions (via [`"use server"`][use-server-docs] directive)
41
42### RSC Framework Mode Template
43
44The [RSC Framework Mode template][framework-rsc-template] uses the unstable React Router RSC Vite plugin along with the experimental [`@vitejs/plugin-rsc` plugin][vite-plugin-rsc].
45
46```shellscript
47npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-framework-mode
48```
49
50### RSC Data Mode Templates
51
52The [Vite RSC Data Mode template][vite-rsc-template] uses the experimental Vite `@vitejs/plugin-rsc` plugin.
53
54```shellscript
55npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-data-mode-vite
56```
57
58## RSC Framework Mode
59
60Most APIs and features in RSC Framework Mode are the same as non-RSC Framework Mode, so this guide will focus on the differences.
61
62### New React Router RSC Vite Plugin
63
64RSC Framework Mode uses a different Vite plugin than non-RSC Framework Mode, currently exported as `unstable_reactRouterRSC`.
65
66This new Vite plugin also has a peer dependency on the experimental `@vitejs/plugin-rsc` plugin. Note that the `@vitejs/plugin-rsc` plugin should be placed after the React Router RSC plugin in your Vite config.
67
68```tsx filename=vite.config.ts
69import { defineConfig } from "vite";
70import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
71import rsc from "@vitejs/plugin-rsc";
72
73export default defineConfig({
74 plugins: [reactRouterRSC(), rsc()],
75});
76```
77
78### Build Output
79
80The RSC Framework Mode server build file (`build/server/index.js`) exports a `default` request handler function (`(request: Request) => Promise<Response>`) for document/data requests.
81
82If needed, you can convert this into a [standard Node.js request listener][node-request-listener] for use with Node's built-in `http.createServer` function (or anything that supports it, e.g. [Express][express]) by using the `createRequestListener` function from [@remix-run/node-fetch-server][node-fetch-server].
83
84For example, in Express:
85
86```tsx filename=start.js
87import express from "express";
88import requestHandler from "./build/server/index.js";
89import { createRequestListener } from "@remix-run/node-fetch-server";
90
91const app = express();
92
93app.use(
94 "/assets",
95 express.static("build/client/assets", {
96 immutable: true,
97 maxAge: "1y",
98 }),
99);
100app.use(express.static("build/client"));
101app.use(createRequestListener(requestHandler));
102app.listen(3000);
103```
104
105### React Elements From Loaders/Actions
106
107In RSC Framework Mode, loaders and actions can return React elements along with other data. These elements will only ever be rendered on the server.
108
109```tsx
110import type { Route } from "./+types/route";
111
112export async function loader() {
113 return {
114 message: "Message from the server!",
115 element: <p>Element from the server!</p>,
116 };
117}
118
119export default function Route({
120 loaderData,
121}: Route.ComponentProps) {
122 return (
123 <>
124 <h1>{loaderData.message}</h1>
125 {loaderData.element}
126 </>
127 );
128}
129```
130
131If you need to use client-only features (e.g. [Hooks][hooks], event handlers) within React elements returned from loaders/actions, you'll need to extract components using these features into a [client module][use-client-docs]:
132
133```tsx filename=src/routes/counter/counter.tsx
134"use client";
135
136import { useState } from "react";
137
138export function Counter() {
139 const [count, setCount] = useState(0);
140 return (
141 <button onClick={() => setCount(count + 1)}>
142 Count: {count}
143 </button>
144 );
145}
146```
147
148```tsx filename=src/routes/counter/route.tsx
149import type { Route } from "./+types/route";
150import { Counter } from "./counter";
151
152export async function loader() {
153 return {
154 message: "Message from the server!",
155 element: (
156 <>
157 <p>Element from the server!</p>
158 <Counter />
159 </>
160 ),
161 };
162}
163
164export default function Route({
165 loaderData,
166}: Route.ComponentProps) {
167 return (
168 <>
169 <h1>{loaderData.message}</h1>
170 {loaderData.element}
171 </>
172 );
173}
174```
175
176### Route Server Components
177
178If a route exports a `ServerComponent` instead of the typical `default` component export, the route renders on the server instead of the client. A route module cannot export both `default` and `ServerComponent`.
179
180You can still export client-only annotations like `clientLoader` and `clientAction` alongside a `ServerComponent`. The other route module component exports follow the same client/server split: `ErrorBoundary`, `Layout`, and `HydrateFallback` are client components, while `ServerErrorBoundary`, `ServerLayout`, and `ServerHydrateFallback` render on the server.
181
182The following route module components have their own mutually exclusive server component counterparts:
183
184| Server Component Export | Client Component |
185| ----------------------- | ----------------- |
186| `ServerComponent` | `default` |
187| `ServerErrorBoundary` | `ErrorBoundary` |
188| `ServerLayout` | `Layout` |
189| `ServerHydrateFallback` | `HydrateFallback` |
190
191```tsx
192import type { Route } from "./+types/route";
193import { Outlet } from "react-router";
194import { getMessage } from "./message";
195
196export async function loader() {
197 return {
198 message: await getMessage(),
199 };
200}
201
202export function ServerComponent({
203 loaderData,
204}: Route.ServerComponentProps) {
205 return (
206 <>
207 <h1>Server Component Route</h1>
208 <p>Message from the server: {loaderData.message}</p>
209 <Outlet />
210 </>
211 );
212}
213```
214
215If you need to use client-only features (e.g. [Hooks][hooks], event handlers) within a server-first route, you'll need to extract components using these features into a [client module][use-client-docs]:
216
217```tsx filename=src/routes/counter/counter.tsx
218"use client";
219
220import { useState } from "react";
221
222export function Counter() {
223 const [count, setCount] = useState(0);
224 return (
225 <button onClick={() => setCount(count + 1)}>
226 Count: {count}
227 </button>
228 );
229}
230```
231
232```tsx filename=src/routes/counter/route.tsx
233import { Counter } from "./counter";
234
235export function ServerComponent() {
236 return (
237 <>
238 <h1>Counter</h1>
239 <Counter />
240 </>
241 );
242}
243```
244
245### `.server`/`.client` Modules
246
247To avoid confusion with RSC's `"use server"` and `"use client"` directives, support for [`.server` modules][server-modules] and [`.client` modules][client-modules] is no longer built-in when using RSC Framework Mode.
248
249As an alternative solution that doesn't rely on file naming conventions, we recommend using the `"server-only"` and `"client-only"` imports provided by [`@vitejs/plugin-rsc`][vite-plugin-rsc]. For example, to ensure a module is never accidentally included in the client build, simply import from `"server-only"` as a side effect within your server-only module.
250
251```ts filename=app/utils/db.ts
252import "server-only";
253
254// Rest of the module...
255```
256
257Note that while there are official npm packages [`server-only`][server-only-package] and [`client-only`][client-only-package] created by the React team, they don't need to be installed. `@vitejs/plugin-rsc` internally handles these imports and provides build-time validation instead of runtime errors.
258
259If you'd like to quickly migrate existing code that relies on the `.server` and `.client` file naming conventions, we recommend using the [`vite-env-only` plugin][vite-env-only] directly. For example, to ensure `.server` modules aren't accidentally included in the client build:
260
261```tsx filename=vite.config.ts
262import { defineConfig } from "vite";
263import { denyImports } from "vite-env-only";
264import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
265import rsc from "@vitejs/plugin-rsc";
266
267export default defineConfig({
268 plugins: [
269 denyImports({
270 client: { files: ["**/.server/*", "**/*.server.*"] },
271 }),
272 reactRouterRSC(),
273 rsc(),
274 ],
275});
276```
277
278### MDX Route Support
279
280MDX routes are supported in RSC Framework Mode when using `@mdx-js/rollup` v3.1.1+.
281
282Note that any components exported from an MDX route must also be valid in RSC environments, meaning that they cannot use client-only features like [Hooks][hooks]. Any components that need to use these features should be extracted into a [client module][use-client-docs].
283
284### Custom Entry Files
285
286RSC Framework Mode supports custom entry files, allowing you to customize the behavior of the RSC server, SSR server, and client entry points.
287
288The plugin will automatically detect custom entry files in your `app` directory:
289
290- `app/entry.rsc.ts` (or `.tsx`) - Custom RSC server entry
291- `app/entry.ssr.ts` (or `.tsx`) - Custom SSR server entry
292- `app/entry.client.tsx` - Custom client entry
293
294If these files are not found, React Router will use the default entries provided by the framework.
295
296If you want to inspect the generated defaults before overriding them, you can also use `react-router reveal entry.client`, `react-router reveal entry.rsc`, and `react-router reveal entry.ssr`.
297
298#### Basic Override Pattern
299
300You can create a custom entry file that wraps or extends the default behavior. For example, to add custom logging to the RSC entry:
301
302```ts filename=app/entry.rsc.ts
303import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc";
304import { RouterContextProvider } from "react-router";
305
306export default {
307 fetch(request: Request): Promise<Response> {
308 console.log(
309 "Custom RSC entry handling request:",
310 request.url,
311 );
312
313 const requestContext = new RouterContextProvider();
314
315 return defaultEntry.fetch(request, requestContext);
316 },
317};
318
319if (import.meta.hot) {
320 import.meta.hot.accept();
321}
322```
323
324Similarly, you can customize the SSR entry:
325
326```ts filename=app/entry.ssr.ts
327import { generateHTML as defaultGenerateHTML } from "@react-router/dev/config/default-rsc-entries/entry.ssr";
328
329export function generateHTML(
330 request: Request,
331 serverResponse: Response,
332): Promise<Response> {
333 console.log(
334 "Custom SSR entry generating HTML for:",
335 request.url,
336 );
337
338 return defaultGenerateHTML(request, serverResponse);
339}
340```
341
342And for the client:
343
344```ts filename=app/entry.client.ts
345import "@react-router/dev/config/default-rsc-entries/entry.client";
346```
347
348#### Copying Default Entries
349
350For more advanced customization, you can copy the default entries and modify them as needed. To find the default entries:
351
3521. In your IDE, use "Go to Definition" (or Cmd/Ctrl+Click) on the default entry import:
353
354 ```ts
355 import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc";
356 ```
357
3582. Copy the default entry code into your custom file
359
3603. Modify it to suit your needs
361
362The default entries are located at:
363
364- [`@react-router/dev/config/default-rsc-entries/entry.rsc`][entry-rsc-source]
365- [`@react-router/dev/config/default-rsc-entries/entry.ssr`][entry-ssr-source]
366- [`@react-router/dev/config/default-rsc-entries/entry.client`][entry-client-source]
367
368You can view the source code on GitHub using the links above, or navigate directly to these files in `node_modules/@react-router/dev/dist/config/default-rsc-entries/`.
369
370<docs-info>
371
372When copying default entries, make sure to maintain the required exports:
373
374- `entry.rsc.ts` must export a default object with a `fetch` method
375- `entry.ssr.ts` must export a `generateHTML` function
376- `entry.client.tsx` should handle client-side hydration
377
378</docs-info>
379
380### Unsupported Config Options
381
382The following options from `react-router.config.ts` are not currently supported in RSC Framework Mode:
383
384- `buildEnd`
385- `presets`
386- `serverBundles`
387- `future.v8_splitRouteModules`
388- `subResourceIntegrity`
389
390## RSC Data Mode
391
392The RSC Framework Mode APIs described above are built on top of lower-level RSC Data Mode APIs.
393
394RSC Data Mode is missing some of the features of RSC Framework Mode (e.g. `routes.ts` config and file system routing, HMR and Hot Data Revalidation), but is more flexible and allows you to integrate with your own bundler and server abstractions.
395
396### Configuring Routes
397
398Routes are configured as an argument to [`matchRSCServerRequest`][match-rsc-server-request]. At a minimum, you need a path and component:
399
400```tsx
401function Root() {
402 return <h1>Hello world</h1>;
403}
404
405matchRSCServerRequest({
406 // ...other options
407 routes: [{ path: "/", Component: Root }],
408});
409```
410
411While you can define components inline, we recommend using the `lazy()` option and defining [Route Modules][route-module] for both startup performance and code organization.
412
413<docs-info>
414
415The `lazy` field of the RSC route config expects the same exports as the [Route Module API][route-module], which keeps the route-module shape consistent across [Framework Mode][framework-mode] and RSC Data Mode.
416
417That includes exports like `loader`, `action`, `meta`, `links`, `headers`, `ErrorBoundary`, `HydrateFallback`, and the client annotations.
418
419</docs-info>
420
421```tsx filename=app/routes.ts
422import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";
423
424export function routes() {
425 return [
426 {
427 id: "root",
428 path: "",
429 lazy: () => import("./root/route"),
430 children: [
431 {
432 id: "home",
433 index: true,
434 lazy: () => import("./home/route"),
435 },
436 {
437 id: "about",
438 path: "about",
439 lazy: () => import("./about/route"),
440 },
441 ],
442 },
443 ] satisfies RSCRouteConfig;
444}
445```
446
447### Server Component Routes
448
449By default each route's `default` export renders a Server Component
450
451```tsx
452export default function Home() {
453 return (
454 <main>
455 <article>
456 <h1>Welcome to React Router RSC</h1>
457 <p>
458 You won't find me running any JavaScript in the
459 browser!
460 </p>
461 </article>
462 </main>
463 );
464}
465```
466
467A nice feature of Server Components is that you can fetch data directly from your component by making it asynchronous.
468
469```tsx
470export default async function Home() {
471 let user = await getUserData();
472
473 return (
474 <main>
475 <article>
476 <h1>Welcome to React Router RSC</h1>
477 <p>
478 You won't find me running any JavaScript in the
479 browser!
480 </p>
481 <p>
482 Hello, {user ? user.name : "anonymous person"}!
483 </p>
484 </article>
485 </main>
486 );
487}
488```
489
490<docs-info>
491
492Server Components can also be returned from your loaders and actions. In general, if you are using RSC to build your application, loaders are primarily useful for things like setting `status` codes or returning a `redirect`.
493
494Using Server Components in loaders can be helpful for incremental adoption of RSC.
495
496</docs-info>
497
498### Server Functions
499
500[Server Functions][react-server-functions-doc] are a React feature that allow you to call async functions executed on the server. They're defined with the [`"use server"`][use-server-docs] directive.
501
502```tsx
503"use server";
504
505export async function updateFavorite(formData: FormData) {
506 let movieId = formData.get("id");
507 let intent = formData.get("intent");
508 if (intent === "add") {
509 await addFavorite(Number(movieId));
510 } else {
511 await removeFavorite(Number(movieId));
512 }
513}
514```
515
516```tsx
517import { updateFavorite } from "./action.ts";
518export async function AddToFavoritesForm({
519 movieId,
520}: {
521 movieId: number;
522}) {
523 let isFav = await isFavorite(movieId);
524 return (
525 <form action={updateFavorite}>
526 <input type="hidden" name="id" value={movieId} />
527 <input
528 type="hidden"
529 name="intent"
530 value={isFav ? "remove" : "add"}
531 />
532 <AddToFavoritesButton isFav={isFav} />
533 </form>
534 );
535}
536```
537
538Note that after server functions are called, React Router will automatically revalidate the route and update the UI with the new server content. You don't have to mess around with any cache invalidation.
539
540### Client Properties
541
542Routes are defined on the server at runtime, but we can still provide `clientLoader`, `clientAction`, and `shouldRevalidate` through the utilization of client references and `"use client"`.
543
544```tsx filename=src/routes/root/client.tsx
545"use client";
546
547export function clientAction() {}
548
549export function clientLoader() {}
550
551export function shouldRevalidate() {}
552
553export default function ClientRoot() {
554 return <p>Client route</p>;
555}
556```
557
558We can then re-export these from our lazy loaded route module:
559
560```tsx filename=src/routes/root/route.tsx
561export {
562 clientAction,
563 clientLoader,
564 shouldRevalidate,
565} from "./client";
566
567export default function Root() {
568 // ...
569}
570```
571
572This is also the way we would make an entire route a Client Component.
573
574```tsx filename=src/routes/root/route.tsx lines=[1,11]
575import { default as ClientRoot } from "./route.client";
576export {
577 clientAction,
578 clientLoader,
579 shouldRevalidate,
580} from "./client";
581
582export default function Root() {
583 // Adding a Server Component at the root is required by bundlers
584 // if you're using css side-effects imports.
585 return <ClientRoot />;
586}
587```
588
589### Bundler Configuration
590
591React Router provides several APIs that allow you to easily integrate with RSC-compatible bundlers, useful if you are using React Router Data Mode to make your own [custom framework][custom-framework].
592
593The following steps show how to setup a React Router application to use Server Components (RSC) to server-render (SSR) pages and hydrate them for single-page app (SPA) navigations. You don't have to use SSR (or even client-side hydration) if you don't want to. You can also leverage the HTML generation for Static Site Generation (SSG) or Incremental Static Regeneration (ISR) if you prefer. This guide is meant merely to explain how to wire up all the different APIs for a typically RSC-based application.
594
595### Entry points
596
597Besides our [route definitions](#configuring-routes), we will need to configure the following:
598
5991. A server to handle the incoming request, fetch the RSC payload, and convert it into HTML
6002. A React server to generate RSC payloads
6013. A browser handler to hydrate the generated HTML and set the `callServer` function to support post-hydration server actions
602
603The following naming conventions have been chosen for familiarity and simplicity. Feel free to name and configure your entry points as you see fit.
604
605See the relevant bundler documentation below for specific code examples for each of the following entry points.
606
607These examples all use [express][express] and [@remix-run/node-fetch-server][node-fetch-server] for the server and request handling.
608
609**Routes**
610
611See [Configuring Routes](#configuring-routes).
612
613**Server**
614
615<docs-info>
616
617You don't have to use SSR at all. You can choose to use RSC to "prerender" HTML for Static Site Generation (SSG) or something like Incremental Static Regeneration (ISR).
618
619</docs-info>
620
621`entry.ssr.tsx` is the entry point for the server. It is responsible for handling the request, calling the RSC server, and converting the RSC payload into HTML on document requests (server-side rendering).
622
623Relevant APIs:
624
625- [`routeRSCServerRequest`][route-rsc-server-request]
626- [`RSCStaticRouter`][rsc-static-router]
627
628**RSC Server**
629
630<docs-info>
631
632Even though you have a "React Server" and a server responsible for request handling/SSR, you don't actually need to have 2 separate servers. You can simply have 2 separate module graphs within the same server. This is important because React behaves differently when generating RSC payloads vs. when generating HTML to be hydrated on the client.
633
634</docs-info>
635
636`entry.rsc.tsx` is the entry point for the React Server. It is responsible for matching the request to a route and generating RSC payloads.
637
638Relevant APIs:
639
640- [`matchRSCServerRequest`][match-rsc-server-request]
641
642**Browser**
643
644`entry.browser.tsx` is the entry point for the client. It is responsible for hydrating the generated HTML and setting the `callServer` function to support post-hydration server actions.
645
646Relevant APIs:
647
648- [`createCallServer`][create-call-server]
649- [`getRSCStream`][get-rsc-stream]
650- [`RSCHydratedRouter`][rsc-hydrated-router]
651
652### Vite
653
654See the [@vitejs/plugin-rsc docs][vite-plugin-rsc] for more information. You can also refer to our [Vite RSC Data Mode template][vite-rsc-template] to see a working version.
655
656In addition to `react`, `react-dom`, and `react-router`, you'll need the following dependencies:
657
658```shellscript
659npm i -D vite @vitejs/plugin-react @vitejs/plugin-rsc
660```
661
662#### `vite.config.ts`
663
664To configure Vite, add the following to your `vite.config.ts`:
665
666```ts filename=vite.config.ts
667import rsc from "@vitejs/plugin-rsc/plugin";
668import react from "@vitejs/plugin-react";
669import { defineConfig } from "vite";
670
671export default defineConfig({
672 plugins: [
673 react(),
674 rsc({
675 entries: {
676 client: "src/entry.browser.tsx",
677 rsc: "src/entry.rsc.tsx",
678 ssr: "src/entry.ssr.tsx",
679 },
680 }),
681 ],
682});
683```
684
685```tsx filename=src/routes/config.ts
686import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";
687
688export function routes() {
689 return [
690 {
691 id: "root",
692 path: "",
693 lazy: () => import("./root/route"),
694 children: [
695 {
696 id: "home",
697 index: true,
698 lazy: () => import("./home/route"),
699 },
700 {
701 id: "about",
702 path: "about",
703 lazy: () => import("./about/route"),
704 },
705 ],
706 },
707 ] satisfies RSCRouteConfig;
708}
709```
710
711#### `entry.ssr.tsx`
712
713The following is a simplified example of a Vite SSR Server.
714
715```tsx filename=src/entry.ssr.tsx
716import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
717import { renderToReadableStream as renderHTMLToReadableStream } from "react-dom/server.edge";
718import {
719 unstable_routeRSCServerRequest as routeRSCServerRequest,
720 unstable_RSCStaticRouter as RSCStaticRouter,
721} from "react-router";
722
723export async function generateHTML(
724 request: Request,
725 serverResponse: Response,
726): Promise<Response> {
727 return await routeRSCServerRequest({
728 // The incoming request.
729 request,
730 // The React Server response
731 serverResponse,
732 // Provide the React Server touchpoints.
733 createFromReadableStream,
734 // Render the router to HTML.
735 async renderHTML(getPayload, options) {
736 const payload = await getPayload();
737 const formState =
738 payload.type === "render"
739 ? await payload.formState
740 : undefined;
741
742 const bootstrapScriptContent =
743 await import.meta.viteRsc.loadBootstrapScriptContent(
744 "index",
745 );
746
747 return await renderHTMLToReadableStream(
748 <RSCStaticRouter getPayload={getPayload} />,
749 {
750 ...options,
751 bootstrapScriptContent,
752 formState,
753 signal: request.signal,
754 },
755 );
756 },
757 });
758}
759```
760
761#### `entry.rsc.tsx`
762
763The following is a simplified example of a Vite RSC Server.
764
765```tsx filename=src/entry.rsc.tsx
766import {
767 createTemporaryReferenceSet,
768 decodeAction,
769 decodeFormState,
770 decodeReply,
771 loadServerAction,
772 renderToReadableStream,
773} from "@vitejs/plugin-rsc/rsc";
774import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router";
775
776import { routes } from "./routes/config";
777
778function fetchServer(request: Request) {
779 return matchRSCServerRequest({
780 // Provide the React Server touchpoints.
781 createTemporaryReferenceSet,
782 decodeAction,
783 decodeFormState,
784 decodeReply,
785 loadServerAction,
786 // The incoming request.
787 request,
788 // The app routes.
789 routes: routes(),
790 // Encode the match with the React Server implementation.
791 generateResponse(match, options) {
792 return new Response(
793 renderToReadableStream(match.payload, options),
794 {
795 status: match.statusCode,
796 headers: match.headers,
797 },
798 );
799 },
800 });
801}
802
803export default async function handler(request: Request) {
804 // Import the generateHTML function from the client environment
805 const ssr = await import.meta.viteRsc.loadModule<
806 typeof import("./entry.ssr")
807 >("ssr", "index");
808
809 return ssr.generateHTML(
810 request,
811 await fetchServer(request),
812 );
813}
814```
815
816#### `entry.browser.tsx`
817
818```tsx filename=src/entry.browser.tsx
819import {
820 createFromReadableStream,
821 createTemporaryReferenceSet,
822 encodeReply,
823 setServerCallback,
824} from "@vitejs/plugin-rsc/browser";
825import { startTransition, StrictMode } from "react";
826import { hydrateRoot } from "react-dom/client";
827import {
828 unstable_createCallServer as createCallServer,
829 unstable_getRSCStream as getRSCStream,
830 unstable_RSCHydratedRouter as RSCHydratedRouter,
831 type unstable_RSCPayload as RSCPayload,
832} from "react-router/dom";
833
834// Create and set the callServer function to support post-hydration server actions.
835setServerCallback(
836 createCallServer({
837 createFromReadableStream,
838 createTemporaryReferenceSet,
839 encodeReply,
840 }),
841);
842
843// Get and decode the initial server payload.
844createFromReadableStream<RSCPayload>(getRSCStream()).then(
845 (payload) => {
846 startTransition(async () => {
847 const formState =
848 payload.type === "render"
849 ? await payload.formState
850 : undefined;
851
852 hydrateRoot(
853 document,
854 <StrictMode>
855 <RSCHydratedRouter
856 createFromReadableStream={
857 createFromReadableStream
858 }
859 payload={payload}
860 />
861 </StrictMode>,
862 {
863 formState,
864 },
865 );
866 });
867 },
868);
869```
870
871[picking-a-mode]: ../start/modes
872[react-server-components-doc]: https://react.dev/reference/rsc/server-components
873[react-server-functions-doc]: https://react.dev/reference/rsc/server-functions
874[use-client-docs]: https://react.dev/reference/rsc/use-client
875[use-server-docs]: https://react.dev/reference/rsc/use-server
876[route-module]: ../start/framework/route-module
877[framework-mode]: ../start/modes#framework
878[custom-framework]: ../start/data/custom
879[vite-plugin-rsc]: https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc
880[match-rsc-server-request]: ../api/rsc/matchRSCServerRequest
881[route-rsc-server-request]: ../api/rsc/routeRSCServerRequest
882[rsc-static-router]: ../api/rsc/RSCStaticRouter
883[create-call-server]: ../api/rsc/createCallServer
884[get-rsc-stream]: ../api/rsc/getRSCStream
885[rsc-hydrated-router]: ../api/rsc/RSCHydratedRouter
886[express]: https://expressjs.com/
887[node-fetch-server]: https://www.npmjs.com/package/@remix-run/node-fetch-server
888[framework-rsc-template]: https://github.com/remix-run/react-router-templates/tree/main/unstable_rsc-framework-mode
889[vite-rsc-template]: https://github.com/remix-run/react-router-templates/tree/main/unstable_rsc-data-mode-vite
890[node-request-listener]: https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener
891[hooks]: https://react.dev/reference/react/hooks
892[vite-env-only]: https://github.com/pcattori/vite-env-only
893[server-modules]: ../api/framework-conventions/server-modules
894[client-modules]: ../api/framework-conventions/client-modules
895[server-only-package]: https://www.npmjs.com/package/server-only
896[client-only-package]: https://www.npmjs.com/package/client-only
897[entry-rsc-source]: https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx
898[entry-ssr-source]: https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/config/default-rsc-entries/entry.ssr.tsx
899[entry-client-source]: https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/config/default-rsc-entries/entry.client.tsx
900
\No newline at end of file