react-router 7.16.0 → 7.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/development/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
  3. package/dist/development/{chunk-QUQL4437.mjs → chunk-6CSD65Y2.mjs} +2 -2
  4. package/dist/{production/chunk-NALGHHKE.mjs → development/chunk-ASILSGTR.mjs} +2 -2
  5. package/dist/development/{chunk-SRID2YZ2.js → chunk-KFNXW4AL.js} +1 -1
  6. package/dist/development/{chunk-XEJDWL2B.js → chunk-PBLBZ3QU.js} +7 -7
  7. package/dist/{production/chunk-SKEDDLRM.js → development/chunk-PULC7NLK.js} +99 -99
  8. package/dist/development/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
  9. package/dist/development/dom-export.d.mts +3 -3
  10. package/dist/development/dom-export.d.ts +1 -1
  11. package/dist/development/dom-export.js +28 -28
  12. package/dist/development/dom-export.mjs +3 -3
  13. package/dist/development/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
  14. package/dist/development/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
  15. package/dist/development/index-react-server-client.d.mts +2 -2
  16. package/dist/development/index-react-server-client.d.ts +1 -1
  17. package/dist/development/index-react-server-client.js +4 -4
  18. package/dist/development/index-react-server-client.mjs +2 -2
  19. package/dist/development/index-react-server.js +1 -1
  20. package/dist/development/index-react-server.mjs +1 -1
  21. package/dist/development/index.d.mts +6 -6
  22. package/dist/development/index.d.ts +2 -2
  23. package/dist/development/index.js +85 -85
  24. package/dist/development/index.mjs +3 -3
  25. package/dist/development/lib/types/internal.js +1 -1
  26. package/dist/development/lib/types/internal.mjs +1 -1
  27. package/dist/production/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
  28. package/dist/{development/chunk-S54KXAEJ.mjs → production/chunk-5TQZEVD5.mjs} +2 -2
  29. package/dist/production/{chunk-EAQNHM3N.js → chunk-CTIXC7EV.js} +7 -7
  30. package/dist/{development/chunk-IBI7OMNB.js → production/chunk-EN242BO4.js} +99 -99
  31. package/dist/production/{chunk-Q65P7S7Y.mjs → chunk-OSYEOCBT.mjs} +2 -2
  32. package/dist/production/{chunk-Y7DNFQZP.js → chunk-RTRY3JFT.js} +1 -1
  33. package/dist/production/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
  34. package/dist/production/dom-export.d.mts +3 -3
  35. package/dist/production/dom-export.d.ts +1 -1
  36. package/dist/production/dom-export.js +28 -28
  37. package/dist/production/dom-export.mjs +3 -3
  38. package/dist/production/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
  39. package/dist/production/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
  40. package/dist/production/index-react-server-client.d.mts +2 -2
  41. package/dist/production/index-react-server-client.d.ts +1 -1
  42. package/dist/production/index-react-server-client.js +4 -4
  43. package/dist/production/index-react-server-client.mjs +2 -2
  44. package/dist/production/index-react-server.js +1 -1
  45. package/dist/production/index-react-server.mjs +1 -1
  46. package/dist/production/index.d.mts +6 -6
  47. package/dist/production/index.d.ts +2 -2
  48. package/dist/production/index.js +85 -85
  49. package/dist/production/index.mjs +3 -3
  50. package/dist/production/lib/types/internal.js +1 -1
  51. package/dist/production/lib/types/internal.mjs +1 -1
  52. package/docs/explanation/backend-for-frontend.md +50 -0
  53. package/docs/explanation/code-splitting.md +61 -0
  54. package/docs/explanation/concurrency.md +135 -0
  55. package/docs/explanation/form-vs-fetcher.md +292 -0
  56. package/docs/explanation/hot-module-replacement.md +137 -0
  57. package/docs/explanation/hydration.md +14 -0
  58. package/docs/explanation/index-query-param.md +86 -0
  59. package/docs/explanation/index.md +4 -0
  60. package/docs/explanation/lazy-route-discovery.md +78 -0
  61. package/docs/explanation/location.md +6 -0
  62. package/docs/explanation/progressive-enhancement.md +150 -0
  63. package/docs/explanation/race-conditions.md +88 -0
  64. package/docs/explanation/react-transitions.md +160 -0
  65. package/docs/explanation/route-matching.md +7 -0
  66. package/docs/explanation/server-client-execution.md +4 -0
  67. package/docs/explanation/sessions-and-cookies.md +465 -0
  68. package/docs/explanation/special-files.md +16 -0
  69. package/docs/explanation/state-management.md +524 -0
  70. package/docs/explanation/styling.md +87 -0
  71. package/docs/explanation/type-safety.md +82 -0
  72. package/docs/how-to/accessibility.md +44 -0
  73. package/docs/how-to/client-data.md +199 -0
  74. package/docs/how-to/data-strategy.md +317 -0
  75. package/docs/how-to/error-boundary.md +231 -0
  76. package/docs/how-to/error-reporting.md +142 -0
  77. package/docs/how-to/fetchers.md +307 -0
  78. package/docs/how-to/file-route-conventions.md +410 -0
  79. package/docs/how-to/file-uploads.md +217 -0
  80. package/docs/how-to/form-validation.md +120 -0
  81. package/docs/how-to/headers.md +164 -0
  82. package/docs/how-to/index.md +4 -0
  83. package/docs/how-to/instrumentation.md +556 -0
  84. package/docs/how-to/meta.md +40 -0
  85. package/docs/how-to/middleware.md +763 -0
  86. package/docs/how-to/navigation-blocking.md +233 -0
  87. package/docs/how-to/optimize-revalidation.md +12 -0
  88. package/docs/how-to/pre-rendering.md +225 -0
  89. package/docs/how-to/presets.md +103 -0
  90. package/docs/how-to/react-server-components.md +899 -0
  91. package/docs/how-to/resource-routes.md +126 -0
  92. package/docs/how-to/route-module-type-safety.md +100 -0
  93. package/docs/how-to/search-params.md +4 -0
  94. package/docs/how-to/security.md +30 -0
  95. package/docs/how-to/server-bundles.md +66 -0
  96. package/docs/how-to/spa.md +120 -0
  97. package/docs/how-to/status.md +63 -0
  98. package/docs/how-to/suspense.md +132 -0
  99. package/docs/how-to/using-handle.md +117 -0
  100. package/docs/how-to/view-transitions.md +237 -0
  101. package/docs/how-to/webhook.md +50 -0
  102. package/docs/index.md +39 -0
  103. package/docs/start/data/actions.md +138 -0
  104. package/docs/start/data/custom.md +198 -0
  105. package/docs/start/data/data-loading.md +44 -0
  106. package/docs/start/data/index.md +4 -0
  107. package/docs/start/data/installation.md +52 -0
  108. package/docs/start/data/navigating.md +12 -0
  109. package/docs/start/data/pending-ui.md +12 -0
  110. package/docs/start/data/route-object.md +268 -0
  111. package/docs/start/data/routing.md +281 -0
  112. package/docs/start/data/testing.md +8 -0
  113. package/docs/start/declarative/index.md +4 -0
  114. package/docs/start/declarative/installation.md +43 -0
  115. package/docs/start/declarative/navigating.md +133 -0
  116. package/docs/start/declarative/routing.md +237 -0
  117. package/docs/start/declarative/url-values.md +65 -0
  118. package/docs/start/framework/actions.md +174 -0
  119. package/docs/start/framework/data-loading.md +201 -0
  120. package/docs/start/framework/deploying.md +96 -0
  121. package/docs/start/framework/index.md +4 -0
  122. package/docs/start/framework/installation.md +41 -0
  123. package/docs/start/framework/navigating.md +182 -0
  124. package/docs/start/framework/pending-ui.md +142 -0
  125. package/docs/start/framework/rendering.md +59 -0
  126. package/docs/start/framework/route-module.md +527 -0
  127. package/docs/start/framework/routing.md +362 -0
  128. package/docs/start/framework/testing.md +133 -0
  129. package/docs/start/index.md +4 -0
  130. package/docs/start/modes.md +201 -0
  131. package/docs/upgrading/component-routes.md +363 -0
  132. package/docs/upgrading/future.md +280 -0
  133. package/docs/upgrading/index.md +4 -0
  134. package/docs/upgrading/remix.md +403 -0
  135. package/docs/upgrading/router-provider.md +442 -0
  136. package/docs/upgrading/v6.md +382 -0
  137. package/package.json +2 -1
@@ -0,0 +1,899 @@
1
+ ---
2
+ title: React Server Components
3
+ unstable: 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
14
+ minor/patch releases. Please use with caution and pay **very** close attention
15
+ to release notes for relevant changes.</docs-warning>
16
+
17
+ React Server Components (RSC) refers generally to an architecture and set of APIs provided by React since version 19.
18
+
19
+ From 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
+
25
+ React 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
+
27
+ If 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
+
29
+ RSC 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
+
33
+ The quickest way to get started is with one of our templates.
34
+
35
+ These 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
+
44
+ The [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
47
+ npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-framework-mode
48
+ ```
49
+
50
+ ### RSC Data Mode Templates
51
+
52
+ The [Vite RSC Data Mode template][vite-rsc-template] uses the experimental Vite `@vitejs/plugin-rsc` plugin.
53
+
54
+ ```shellscript
55
+ npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-data-mode-vite
56
+ ```
57
+
58
+ ## RSC Framework Mode
59
+
60
+ Most 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
+
64
+ RSC Framework Mode uses a different Vite plugin than non-RSC Framework Mode, currently exported as `unstable_reactRouterRSC`.
65
+
66
+ This 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
69
+ import { defineConfig } from "vite";
70
+ import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
71
+ import rsc from "@vitejs/plugin-rsc";
72
+
73
+ export default defineConfig({
74
+ plugins: [reactRouterRSC(), rsc()],
75
+ });
76
+ ```
77
+
78
+ ### Build Output
79
+
80
+ The 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
+
82
+ If 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
+
84
+ For example, in Express:
85
+
86
+ ```tsx filename=start.js
87
+ import express from "express";
88
+ import requestHandler from "./build/server/index.js";
89
+ import { createRequestListener } from "@remix-run/node-fetch-server";
90
+
91
+ const app = express();
92
+
93
+ app.use(
94
+ "/assets",
95
+ express.static("build/client/assets", {
96
+ immutable: true,
97
+ maxAge: "1y",
98
+ }),
99
+ );
100
+ app.use(express.static("build/client"));
101
+ app.use(createRequestListener(requestHandler));
102
+ app.listen(3000);
103
+ ```
104
+
105
+ ### React Elements From Loaders/Actions
106
+
107
+ In 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
110
+ import type { Route } from "./+types/route";
111
+
112
+ export async function loader() {
113
+ return {
114
+ message: "Message from the server!",
115
+ element: <p>Element from the server!</p>,
116
+ };
117
+ }
118
+
119
+ export default function Route({
120
+ loaderData,
121
+ }: Route.ComponentProps) {
122
+ return (
123
+ <>
124
+ <h1>{loaderData.message}</h1>
125
+ {loaderData.element}
126
+ </>
127
+ );
128
+ }
129
+ ```
130
+
131
+ If 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
+
136
+ import { useState } from "react";
137
+
138
+ export 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
149
+ import type { Route } from "./+types/route";
150
+ import { Counter } from "./counter";
151
+
152
+ export 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
+
164
+ export 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
+
178
+ If 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
+
180
+ You 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
+
182
+ The 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
192
+ import type { Route } from "./+types/route";
193
+ import { Outlet } from "react-router";
194
+ import { getMessage } from "./message";
195
+
196
+ export async function loader() {
197
+ return {
198
+ message: await getMessage(),
199
+ };
200
+ }
201
+
202
+ export 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
+
215
+ If 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
+
220
+ import { useState } from "react";
221
+
222
+ export 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
233
+ import { Counter } from "./counter";
234
+
235
+ export function ServerComponent() {
236
+ return (
237
+ <>
238
+ <h1>Counter</h1>
239
+ <Counter />
240
+ </>
241
+ );
242
+ }
243
+ ```
244
+
245
+ ### `.server`/`.client` Modules
246
+
247
+ To 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
+
249
+ As 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
252
+ import "server-only";
253
+
254
+ // Rest of the module...
255
+ ```
256
+
257
+ Note 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
+
259
+ If 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
262
+ import { defineConfig } from "vite";
263
+ import { denyImports } from "vite-env-only";
264
+ import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
265
+ import rsc from "@vitejs/plugin-rsc";
266
+
267
+ export 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
+
280
+ MDX routes are supported in RSC Framework Mode when using `@mdx-js/rollup` v3.1.1+.
281
+
282
+ Note 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
+
286
+ RSC Framework Mode supports custom entry files, allowing you to customize the behavior of the RSC server, SSR server, and client entry points.
287
+
288
+ The 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
+
294
+ If these files are not found, React Router will use the default entries provided by the framework.
295
+
296
+ If 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
+
300
+ You 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
303
+ import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc";
304
+ import { RouterContextProvider } from "react-router";
305
+
306
+ export 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
+
319
+ if (import.meta.hot) {
320
+ import.meta.hot.accept();
321
+ }
322
+ ```
323
+
324
+ Similarly, you can customize the SSR entry:
325
+
326
+ ```ts filename=app/entry.ssr.ts
327
+ import { generateHTML as defaultGenerateHTML } from "@react-router/dev/config/default-rsc-entries/entry.ssr";
328
+
329
+ export 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
+
342
+ And for the client:
343
+
344
+ ```ts filename=app/entry.client.ts
345
+ import "@react-router/dev/config/default-rsc-entries/entry.client";
346
+ ```
347
+
348
+ #### Copying Default Entries
349
+
350
+ For more advanced customization, you can copy the default entries and modify them as needed. To find the default entries:
351
+
352
+ 1. 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
+
358
+ 2. Copy the default entry code into your custom file
359
+
360
+ 3. Modify it to suit your needs
361
+
362
+ The 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
+
368
+ You 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
+
372
+ When 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
+
382
+ The 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
+
392
+ The RSC Framework Mode APIs described above are built on top of lower-level RSC Data Mode APIs.
393
+
394
+ RSC 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
+
398
+ Routes are configured as an argument to [`matchRSCServerRequest`][match-rsc-server-request]. At a minimum, you need a path and component:
399
+
400
+ ```tsx
401
+ function Root() {
402
+ return <h1>Hello world</h1>;
403
+ }
404
+
405
+ matchRSCServerRequest({
406
+ // ...other options
407
+ routes: [{ path: "/", Component: Root }],
408
+ });
409
+ ```
410
+
411
+ While 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
+
415
+ The `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
+
417
+ That 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
422
+ import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";
423
+
424
+ export 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
+
449
+ By default each route's `default` export renders a Server Component
450
+
451
+ ```tsx
452
+ export 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
+
467
+ A nice feature of Server Components is that you can fetch data directly from your component by making it asynchronous.
468
+
469
+ ```tsx
470
+ export 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
+
492
+ Server 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
+
494
+ Using 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
+
505
+ export 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
517
+ import { updateFavorite } from "./action.ts";
518
+ export 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
+
538
+ Note 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
+
542
+ Routes 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
+
547
+ export function clientAction() {}
548
+
549
+ export function clientLoader() {}
550
+
551
+ export function shouldRevalidate() {}
552
+
553
+ export default function ClientRoot() {
554
+ return <p>Client route</p>;
555
+ }
556
+ ```
557
+
558
+ We can then re-export these from our lazy loaded route module:
559
+
560
+ ```tsx filename=src/routes/root/route.tsx
561
+ export {
562
+ clientAction,
563
+ clientLoader,
564
+ shouldRevalidate,
565
+ } from "./client";
566
+
567
+ export default function Root() {
568
+ // ...
569
+ }
570
+ ```
571
+
572
+ This 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]
575
+ import { default as ClientRoot } from "./route.client";
576
+ export {
577
+ clientAction,
578
+ clientLoader,
579
+ shouldRevalidate,
580
+ } from "./client";
581
+
582
+ export 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
+
591
+ React 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
+
593
+ The 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
+
597
+ Besides our [route definitions](#configuring-routes), we will need to configure the following:
598
+
599
+ 1. A server to handle the incoming request, fetch the RSC payload, and convert it into HTML
600
+ 2. A React server to generate RSC payloads
601
+ 3. A browser handler to hydrate the generated HTML and set the `callServer` function to support post-hydration server actions
602
+
603
+ The following naming conventions have been chosen for familiarity and simplicity. Feel free to name and configure your entry points as you see fit.
604
+
605
+ See the relevant bundler documentation below for specific code examples for each of the following entry points.
606
+
607
+ These examples all use [express][express] and [@remix-run/node-fetch-server][node-fetch-server] for the server and request handling.
608
+
609
+ **Routes**
610
+
611
+ See [Configuring Routes](#configuring-routes).
612
+
613
+ **Server**
614
+
615
+ <docs-info>
616
+
617
+ You 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
+
623
+ Relevant APIs:
624
+
625
+ - [`routeRSCServerRequest`][route-rsc-server-request]
626
+ - [`RSCStaticRouter`][rsc-static-router]
627
+
628
+ **RSC Server**
629
+
630
+ <docs-info>
631
+
632
+ Even 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
+
638
+ Relevant 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
+
646
+ Relevant APIs:
647
+
648
+ - [`createCallServer`][create-call-server]
649
+ - [`getRSCStream`][get-rsc-stream]
650
+ - [`RSCHydratedRouter`][rsc-hydrated-router]
651
+
652
+ ### Vite
653
+
654
+ See 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
+
656
+ In addition to `react`, `react-dom`, and `react-router`, you'll need the following dependencies:
657
+
658
+ ```shellscript
659
+ npm i -D vite @vitejs/plugin-react @vitejs/plugin-rsc
660
+ ```
661
+
662
+ #### `vite.config.ts`
663
+
664
+ To configure Vite, add the following to your `vite.config.ts`:
665
+
666
+ ```ts filename=vite.config.ts
667
+ import rsc from "@vitejs/plugin-rsc/plugin";
668
+ import react from "@vitejs/plugin-react";
669
+ import { defineConfig } from "vite";
670
+
671
+ export 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
686
+ import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";
687
+
688
+ export 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
+
713
+ The following is a simplified example of a Vite SSR Server.
714
+
715
+ ```tsx filename=src/entry.ssr.tsx
716
+ import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
717
+ import { renderToReadableStream as renderHTMLToReadableStream } from "react-dom/server.edge";
718
+ import {
719
+ unstable_routeRSCServerRequest as routeRSCServerRequest,
720
+ unstable_RSCStaticRouter as RSCStaticRouter,
721
+ } from "react-router";
722
+
723
+ export 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
+
763
+ The following is a simplified example of a Vite RSC Server.
764
+
765
+ ```tsx filename=src/entry.rsc.tsx
766
+ import {
767
+ createTemporaryReferenceSet,
768
+ decodeAction,
769
+ decodeFormState,
770
+ decodeReply,
771
+ loadServerAction,
772
+ renderToReadableStream,
773
+ } from "@vitejs/plugin-rsc/rsc";
774
+ import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router";
775
+
776
+ import { routes } from "./routes/config";
777
+
778
+ function 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
+
803
+ export 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
819
+ import {
820
+ createFromReadableStream,
821
+ createTemporaryReferenceSet,
822
+ encodeReply,
823
+ setServerCallback,
824
+ } from "@vitejs/plugin-rsc/browser";
825
+ import { startTransition, StrictMode } from "react";
826
+ import { hydrateRoot } from "react-dom/client";
827
+ import {
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.
835
+ setServerCallback(
836
+ createCallServer({
837
+ createFromReadableStream,
838
+ createTemporaryReferenceSet,
839
+ encodeReply,
840
+ }),
841
+ );
842
+
843
+ // Get and decode the initial server payload.
844
+ createFromReadableStream<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