diff --git a/docs/api/hooks/useLinkClickHandler.md b/docs/api/hooks/useLinkClickHandler.md
index a484f7682a..4b0e014bd0 100644
--- a/docs/api/hooks/useLinkClickHandler.md
+++ b/docs/api/hooks/useLinkClickHandler.md
@@ -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
diff --git a/docs/api/rsc/RSCStaticRouter.md b/docs/api/rsc/RSCStaticRouter.md
index e402d1d507..65930906d6 100644
--- a/docs/api/rsc/RSCStaticRouter.md
+++ b/docs/api/rsc/RSCStaticRouter.md
@@ -45,12 +45,17 @@ routeRSCServerRequest({
request,
serverResponse,
createFromReadableStream,
- async renderHTML(getPayload) {
+ nonce,
+ async renderHTML(getPayload, options) {
const payload = getPayload();
return await renderHTMLToReadableStream(
- ,
+ ,
{
+ ...options,
bootstrapScriptContent,
formState: await payload.formState,
}
@@ -62,7 +67,7 @@ routeRSCServerRequest({
## Signature
```tsx
-function RSCStaticRouter({ getPayload }: RSCStaticRouterProps)
+function RSCStaticRouter({ getPayload, nonce }: RSCStaticRouterProps)
```
## Props
@@ -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 `` and
+``.
+
diff --git a/docs/api/rsc/matchRSCServerRequest.md b/docs/api/rsc/matchRSCServerRequest.md
index f140225600..c2cdebc504 100644
--- a/docs/api/rsc/matchRSCServerRequest.md
+++ b/docs/api/rsc/matchRSCServerRequest.md
@@ -79,6 +79,7 @@ async function matchRSCServerRequest({
loadServerAction,
decodeAction,
decodeFormState,
+ clientVersion,
onError,
request,
routes,
@@ -92,6 +93,7 @@ async function matchRSCServerRequest({
decodeFormState?: DecodeFormStateFunction;
requestContext?: RouterContextProvider;
loadServerAction?: LoadServerActionFunction;
+ clientVersion?: string;
onError?: (error: unknown) => void;
request: Request;
routes: RSCRouteConfigEntry[];
@@ -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.
diff --git a/docs/api/rsc/routeRSCServerRequest.md b/docs/api/rsc/routeRSCServerRequest.md
index 3f716940e6..5307f33d47 100644
--- a/docs/api/rsc/routeRSCServerRequest.md
+++ b/docs/api/rsc/routeRSCServerRequest.md
@@ -47,12 +47,17 @@ routeRSCServerRequest({
request,
serverResponse,
createFromReadableStream,
- async renderHTML(getPayload) {
+ nonce,
+ async renderHTML(getPayload, options) {
const payload = getPayload();
return await renderHTMLToReadableStream(
- ,
+ ,
{
+ ...options,
bootstrapScriptContent,
formState: await payload.formState,
}
@@ -70,6 +75,7 @@ async function routeRSCServerRequest({
createFromReadableStream,
renderHTML,
hydrate = true,
+ nonce,
}: {
request: Request;
serverResponse: Response;
@@ -77,11 +83,13 @@ async function routeRSCServerRequest({
renderHTML: (
getPayload: () => DecodedPayload,
options: {
+ nonce?: string;
onError(error: unknown): string | undefined;
onHeaders(headers: Headers): void;
},
) => ReadableStream | Promise>;
hydrate?: boolean;
+ nonce?: string;
}): Promise
```
@@ -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 [``](../rsc/RSCStaticRouter).
diff --git a/docs/how-to/react-server-components.md b/docs/how-to/react-server-components.md
index 6283213d99..0fee0f9e4e 100644
--- a/docs/how-to/react-server-components.md
+++ b/docs/how-to/react-server-components.md
@@ -385,7 +385,6 @@ The following options from `react-router.config.ts` are not currently supported
- `presets`
- `serverBundles`
- `splitRouteModules`
-- `subResourceIntegrity`
## RSC Data Mode
@@ -868,7 +867,60 @@ createFromReadableStream(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 {
+ 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(
+ ,
+ {
+ ...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 `` and ``.
+
+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
diff --git a/docs/how-to/security.md b/docs/how-to/security.md
index d3ec9491d1..6e9415789f 100644
--- a/docs/how-to/security.md
+++ b/docs/how-to/security.md
@@ -4,7 +4,7 @@ title: Security
# Security
-[MODES: framework]
+[MODES: framework, data]
@@ -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 `