Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/api/rsc/RSCHydratedRouter.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ function RSCHydratedRouter({
createFromReadableStream,
fetch: fetchImplementation = fetch,
payload,
routeDiscovery = "eager",
getContext,
}: RSCHydratedRouterProps)
```
Expand Down Expand Up @@ -90,8 +89,3 @@ navigation or fetcher call.

The decoded [`unstable_RSCPayload`](https://api.reactrouter.com/v7/types/react-router.unstable_RSCPayload.html) to hydrate.

### routeDiscovery

`"eager"` or `"lazy"` - Determines if links are eagerly discovered, or
delayed until clicked.

6 changes: 6 additions & 0 deletions docs/api/rsc/matchRSCServerRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ async function matchRSCServerRequest({
basename,
decodeReply,
requestContext,
routeDiscovery,
loadServerAction,
decodeAction,
decodeFormState,
Expand All @@ -94,6 +95,7 @@ async function matchRSCServerRequest({
onError?: (error: unknown) => void;
request: Request;
routes: RSCRouteConfigEntry[];
routeDiscovery?: RouteDiscovery;
generateResponse: (
match: RSCMatch,
{
Expand Down Expand Up @@ -158,6 +160,10 @@ The [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) to mat
An instance of [`RouterContextProvider`](../utils/RouterContextProvider) that should be created per request, to be passed to [`action`](../../start/data/route-object#action)s,
[`loader`](../../start/data/route-object#loader)s and [middleware](../../how-to/middleware).

### opts.routeDiscovery

The route discovery configuration, used to determine how the router should discover new routes during navigations.

### opts.routes

Your [route definitions](https://api.reactrouter.com/v7/types/react-router.unstable_RSCRouteConfigEntry.html).
Expand Down
3 changes: 3 additions & 0 deletions docs/api/utils/redirect.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Respons
Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).

This utility accepts absolute URLs and can navigate to external domains, so
the application should validate any user-supplied inputs to redirects.

```tsx
import { redirect } from "react-router";

Expand Down
3 changes: 3 additions & 0 deletions docs/api/utils/redirectDocument.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ that will force a document reload to the new location. Sets the status code
and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).

This utility accepts absolute URLs and can navigate to external domains, so
the application should validate any user-supplied inputs to redirects.

```tsx filename=routes/logout.tsx
import { redirectDocument } from "react-router";

Expand Down
103 changes: 60 additions & 43 deletions docs/how-to/react-server-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default defineConfig({

### Build Output

The RSC Framework Mode server build file (`build/server/index.js`) now exports a `default` request handler function (`(request: Request) => Promise<Response>`) for document/data requests.
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.

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].

Expand All @@ -104,7 +104,7 @@ app.listen(3000);

### React Elements From Loaders/Actions

In RSC Framework Mode, loaders and actions can now return React elements along with other data. These elements will only ever be rendered on the server.
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.

```tsx
import type { Route } from "./+types/route";
Expand Down Expand Up @@ -133,6 +133,8 @@ If you need to use client-only features (e.g. [Hooks][hooks], event handlers) wi
```tsx filename=src/routes/counter/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
const [count, setCount] = useState(0);
return (
Expand Down Expand Up @@ -173,7 +175,9 @@ export default function Route({

### Route Server Components

If a route exports a `ServerComponent` instead of the typical `default` component export, this will be a server component rather than the usual client component. A default export and `ServerComponent` can not both be exported from the same route module, but you can still export client-only annotations like `clientLoader` and `clientAction` alongside a `ServerComponent`, along with any other component exports such as `ErrorBoundary` or `Layout`.
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`.

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.

The following route module components have their own mutually exclusive server component counterparts:

Expand Down Expand Up @@ -213,6 +217,8 @@ If you need to use client-only features (e.g. [Hooks][hooks], event handlers) wi
```tsx filename=src/routes/counter/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
const [count, setCount] = useState(0);
return (
Expand Down Expand Up @@ -287,6 +293,8 @@ The plugin will automatically detect custom entry files in your `app` directory:

If these files are not found, React Router will use the default entries provided by the framework.

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`.

#### Basic Override Pattern

You can create a custom entry file that wraps or extends the default behavior. For example, to add custom logging to the RSC entry:
Expand Down Expand Up @@ -371,14 +379,11 @@ When copying default entries, make sure to maintain the required exports:

### Unsupported Config Options

For the initial unstable release, the following options from `react-router.config.ts` are not yet supported in RSC Framework Mode:
The following options from `react-router.config.ts` are not currently supported in RSC Framework Mode:

- `buildEnd`
- `prerender`
- `presets`
- `routeDiscovery`
- `serverBundles`
- `ssr: false` (SPA Mode)
- `future.v8_splitRouteModules`
- `future.unstable_subResourceIntegrity`

Expand All @@ -403,11 +408,13 @@ matchRSCServerRequest({
});
```

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
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.

<docs-info>

The [Route Module API][route-module] up until now has been a [Framework Mode][framework-mode] only feature. However, the `lazy` field of the RSC route config expects the same exports as the Route Module exports, unifying the APIs even further.
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.

That includes exports like `loader`, `action`, `meta`, `links`, `headers`, `ErrorBoundary`, `HydrateFallback`, and the client annotations.

</docs-info>

Expand Down Expand Up @@ -542,6 +549,10 @@ export function clientAction() {}
export function clientLoader() {}

export function shouldRevalidate() {}

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

We can then re-export these from our lazy loaded route module:
Expand All @@ -551,7 +562,7 @@ export {
clientAction,
clientLoader,
shouldRevalidate,
} from "./route.client";
} from "./client";

export default function Root() {
// ...
Expand All @@ -566,7 +577,7 @@ export {
clientAction,
clientLoader,
shouldRevalidate,
} from "./route.client";
} from "./client";

export default function Root() {
// Adding a Server Component at the root is required by bundlers
Expand Down Expand Up @@ -721,8 +732,12 @@ export async function generateHTML(
// Provide the React Server touchpoints.
createFromReadableStream,
// Render the router to HTML.
async renderHTML(getPayload) {
const payload = getPayload();
async renderHTML(getPayload, options) {
const payload = await getPayload();
const formState =
payload.type === "render"
? await payload.formState
: undefined;

const bootstrapScriptContent =
await import.meta.viteRsc.loadBootstrapScriptContent(
Expand All @@ -732,8 +747,10 @@ export async function generateHTML(
return await renderHTMLToReadableStream(
<RSCStaticRouter getPayload={getPayload} />,
{
...options,
bootstrapScriptContent,
formState: payload.formState,
formState,
signal: request.signal,
},
);
},
Expand Down Expand Up @@ -771,9 +788,9 @@ function fetchServer(request: Request) {
// The app routes.
routes: routes(),
// Encode the match with the React Server implementation.
generateResponse(match) {
generateResponse(match, options) {
return new Response(
renderToReadableStream(match.payload),
renderToReadableStream(match.payload, options),
{
status: match.statusCode,
headers: match.headers,
Expand Down Expand Up @@ -811,8 +828,8 @@ import {
unstable_createCallServer as createCallServer,
unstable_getRSCStream as getRSCStream,
unstable_RSCHydratedRouter as RSCHydratedRouter,
type unstable_RSCPayload as RSCServerPayload,
} from "react-router";
type unstable_RSCPayload as RSCPayload,
} from "react-router/dom";

// Create and set the callServer function to support post-hydration server actions.
setServerCallback(
Expand All @@ -824,31 +841,31 @@ setServerCallback(
);

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

hydrateRoot(
document,
<StrictMode>
<RSCHydratedRouter
createFromReadableStream={
createFromReadableStream
}
payload={payload}
/>
</StrictMode>,
{
formState,
},
);
});
});
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,
},
);
});
},
);
```

[picking-a-mode]: ../start/modes
Expand Down
6 changes: 6 additions & 0 deletions packages/react-router/lib/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,9 @@ export type RedirectFunction = (
* Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* @example
* import { redirect } from "react-router";
*
Expand Down Expand Up @@ -1979,6 +1982,9 @@ export const redirect: RedirectFunction = (url, init = 302) => {
* and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* ```tsx filename=routes/logout.tsx
* import { redirectDocument } from "react-router";
*
Expand Down
Loading