Skip to content
3 changes: 2 additions & 1 deletion docs/api/hooks/useLinkClickHandler.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/Vie

### options.defaultShouldRevalidate

Specify the default revalidation behavior for the navigation. Defaults to `true`.
Specify the default revalidation behavior for the navigation. When not specified, loaders revalidate
according to the router's standard revalidation behavior.

### options.mask

Expand Down
17 changes: 14 additions & 3 deletions docs/api/rsc/RSCStaticRouter.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@ routeRSCServerRequest({
request,
serverResponse,
createFromReadableStream,
async renderHTML(getPayload) {
nonce,
async renderHTML(getPayload, options) {
const payload = getPayload();

return await renderHTMLToReadableStream(
<RSCStaticRouter getPayload={getPayload} />,
<RSCStaticRouter
getPayload={getPayload}
nonce={options.nonce}
/>,
{
...options,
bootstrapScriptContent,
formState: await payload.formState,
}
Expand All @@ -62,7 +67,7 @@ routeRSCServerRequest({
## Signature

```tsx
function RSCStaticRouter({ getPayload }: RSCStaticRouterProps)
function RSCStaticRouter({ getPayload, nonce }: RSCStaticRouterProps)
```

## Props
Expand All @@ -72,3 +77,9 @@ function RSCStaticRouter({ getPayload }: RSCStaticRouterProps)
A function that starts decoding of the [`unstable_RSCPayload`](https://api.reactrouter.com/v8/types/react-router.unstable_RSCPayload.html). Usually passed
through from [`unstable_routeRSCServerRequest`](../rsc/routeRSCServerRequest)'s `renderHTML`.

### nonce

An optional [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
used as the default for nonce-aware components such as `<Links>` and
`<ScrollRestoration>`.

6 changes: 6 additions & 0 deletions docs/api/rsc/matchRSCServerRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ async function matchRSCServerRequest({
loadServerAction,
decodeAction,
decodeFormState,
clientVersion,
onError,
request,
routes,
Expand All @@ -92,6 +93,7 @@ async function matchRSCServerRequest({
decodeFormState?: DecodeFormStateFunction;
requestContext?: RouterContextProvider;
loadServerAction?: LoadServerActionFunction;
clientVersion?: string;
onError?: (error: unknown) => void;
request: Request;
routes: RSCRouteConfigEntry[];
Expand Down Expand Up @@ -147,6 +149,10 @@ encoding the [`unstable_RSCPayload`](https://api.reactrouter.com/v8/types/react-

Your `react-server-dom-xyz/server`'s `loadServerAction` function, used to load a server action by ID.

### opts.clientVersion

A version derived from the client build output used to detect stale clients during lazy route discovery.

### opts.onError

An optional error handler that will be called with any errors that occur during the request processing.
Expand Down
16 changes: 14 additions & 2 deletions docs/api/rsc/routeRSCServerRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,17 @@ routeRSCServerRequest({
request,
serverResponse,
createFromReadableStream,
async renderHTML(getPayload) {
nonce,
async renderHTML(getPayload, options) {
const payload = getPayload();

return await renderHTMLToReadableStream(
<RSCStaticRouter getPayload={getPayload} />,
<RSCStaticRouter
getPayload={getPayload}
nonce={options.nonce}
/>,
{
...options,
bootstrapScriptContent,
formState: await payload.formState,
}
Expand All @@ -70,18 +75,21 @@ async function routeRSCServerRequest({
createFromReadableStream,
renderHTML,
hydrate = true,
nonce,
}: {
request: Request;
serverResponse: Response;
createFromReadableStream: SSRCreateFromReadableStreamFunction;
renderHTML: (
getPayload: () => DecodedPayload,
options: {
nonce?: string;
onError(error: unknown): string | undefined;
onHeaders(headers: Headers): void;
},
) => ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>;
hydrate?: boolean;
nonce?: string;
}): Promise<Response>
```

Expand All @@ -99,6 +107,10 @@ A Response or partial response generated by the [RSC](https://react.dev/referenc

Whether to hydrate the server response with the RSC payload. Defaults to `true`.

### opts.nonce

An optional [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce) for inline scripts generated while rendering the HTML document.

### opts.renderHTML

A function that renders the [`unstable_RSCPayload`](https://api.reactrouter.com/v8/types/react-router.unstable_RSCPayload.html) to HTML, usually using a [`<RSCStaticRouter>`](../rsc/RSCStaticRouter).
Expand Down
54 changes: 53 additions & 1 deletion docs/how-to/react-server-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,6 @@ The following options from `react-router.config.ts` are not currently supported
- `presets`
- `serverBundles`
- `splitRouteModules`
- `subResourceIntegrity`

## RSC Data Mode

Expand Down Expand Up @@ -868,7 +867,60 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then(
);
```

## Content Security Policy nonces

A [Content Security Policy][csp] can use a per-response nonce to allow the inline scripts required for RSC hydration without allowing arbitrary inline scripts. The nonce is an HTML concern, so configure it in `entry.ssr.tsx`; it does not need to be passed to `matchRSCServerRequest` or included in the RSC payload.

In RSC Framework Mode, first run `react-router reveal entry.ssr` to create a custom SSR entry. In RSC Data Mode, update your existing SSR entry. Generate a fresh nonce for each document response, then pass it to `routeRSCServerRequest`, the `RSCStaticRouter`, and your CSP response header:

```tsx filename=app/entry.ssr.tsx
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;
}
```

The `nonce` option on `routeRSCServerRequest` applies the nonce to the inline scripts that transfer the RSC payload into the HTML document. Spreading its `renderHTML` options into `renderHTMLToReadableStream` applies the same nonce to scripts generated by React. Passing it to `RSCStaticRouter` makes it the default for nonce-aware components such as `<Links>` and `<ScrollRestoration>`.

The default RSC Framework entry does not generate a nonce. Only generate one when your application also sends a matching CSP header. For statically prerendered pages, prefer CSP hashes or external scripts instead of a per-response nonce.

[picking-a-mode]: ../start/modes
[csp]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
[react-server-components-doc]: https://react.dev/reference/rsc/server-components
[react-server-functions-doc]: https://react.dev/reference/rsc/server-functions
[use-client-docs]: https://react.dev/reference/rsc/use-client
Expand Down
9 changes: 8 additions & 1 deletion docs/how-to/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Security

# Security

[MODES: framework]
[MODES: framework, data]

<br/>
<br/>
Expand All @@ -13,6 +13,8 @@ This is by no means a comprehensive guide, but React Router provides features to

## `Content-Security-Policy`

### Framework Mode without RSC

If you are implementing a [Content-Security-Policy (CSP)][csp] in your application, specifically one using the `unsafe-inline` directive, you will need to specify a [`nonce`][nonce] attribute on the inline `<script>` elements rendered in your HTML.

Add a nonce to these two spots in [`entry.server.tsx`][entryserver]:
Expand All @@ -22,6 +24,10 @@ Add a nonce to these two spots in [`entry.server.tsx`][entryserver]:
- If those components specify their own `nonce` prop, it will override the `ServerRouter` value
- The `nonce` options of [`renderToPipeableStream`][renderToPipeableStream]/[`renderToReadableStream`][renderToReadableStream]

### RSC Framework and RSC Data Mode

For RSC Framework and RSC Data Mode, generate the nonce in `entry.ssr.tsx` and pass it to `routeRSCServerRequest`, `RSCStaticRouter`, and the CSP response header. See the [RSC Content Security Policy nonce guide][rsc-csp]. The nonce is only needed while generating the HTML document; it should not be included in the RSC payload or passed to `matchRSCServerRequest`.

[csp]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
[entryserver]: ../api/framework-conventions/entry.server.tsx
[nonce]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce
Expand All @@ -30,3 +36,4 @@ Add a nonce to these two spots in [`entry.server.tsx`][entryserver]:
[scripts]: ../api/components/Scripts
[scrollrestoration]: ../api/components/ScrollRestoration
[serverrouter]: ../api/framework-routers/ServerRouter
[rsc-csp]: ./react-server-components#content-security-policy-nonces
Loading