diff --git a/CHANGELOG.md b/CHANGELOG.md index b935013783..6a91a2fec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) + - [v8.3.0](#v830) + - [RSC Entry Updates](#rsc-entry-updates) - [v8.2.0](#v820) - [Web Streams Default Server Entry](#web-streams-default-server-entry) - [v8.1.0](#v810) @@ -109,6 +111,117 @@ We manage release notes in this file instead of the paginated Github Releases Pa +## v8.3.0 + +Date: 2026-07-22 + +### What's Changed + +#### RSC Entry Updates + +This release includes several updates for unstable RSC apps that use custom entry files. Apps using the default RSC Framework entries do not need any changes. + +If you maintain custom RSC entries, review the generated unstable change notes for the new client version, subresource integrity, and CSP nonce wiring. Custom `entry.rsc.tsx` files should pass the generated client version to `unstable_matchRSCServerRequest`, and custom `entry.ssr.tsx` files may need to pass the generated import map integrity data and request nonce through React's HTML renderer. + +### Minor Changes + +- `@react-router/dev` - Restart `react-router dev` with `--conditions=development` when not already configured ([#15291](https://github.com/remix-run/react-router/pull/15291)) + +### Patch Changes + +- `react-router` - Encode path params in `href`/`generatePath` per RFC 3986 path-segment rules instead of `encodeURIComponent` ([#15310](https://github.com/remix-run/react-router/pull/15310)) + - Characters that are valid literally in a path segment (`$ & + , ; = : @` — RFC 3986 `pchar`) are no longer percent-encoded, so values like a semver build `1.0.0+1` interpolate unchanged instead of becoming `1.0.0%2B1` + - Structural/unsafe characters (`/ ? # %`, whitespace, non-ASCII) are still escaped exactly as before +- `react-router` - Use `crypto.randomUUID()` for `createMemorySessionStorage` session ids ([#15302](https://github.com/remix-run/react-router/pull/15302)) + - `createMemorySessionStorage` is only intended for local development and testing - sessions are lost when the server restarts +- `react-router` - Fix `NavLink` not applying its `pending` state when `to` has a trailing slash ([#15300](https://github.com/remix-run/react-router/pull/15300)) +- `@react-router/architect` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/cloudflare` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/dev` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/express` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/fs-routes` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/node` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- `@react-router/remix-routes-option-adapter` - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- `react-router` - Preserve RSC route component metadata so routes with a `clientLoader` can skip unnecessary server requests once their components have rendered while still fetching missing server-rendered elements ([#15323](https://github.com/remix-run/react-router/pull/15323)) +- `react-router` - Harden RSC CSRF code paths ([#15311](https://github.com/remix-run/react-router/pull/15311)) +- `react-router` - Fix server crash (`TypeError: Invalid state: Unable to enqueue`) when a request is aborted while the RSC HTML stream has a pending flush ([#15286](https://github.com/remix-run/react-router/pull/15286)) + - Handle cancellation of the `injectRSCPayload` readable side, clear the pending flush, and cancel the underlying RSC payload stream +- `react-router` - Detect stale RSC clients during lazy route discovery and reload the destination document ([#15318](https://github.com/remix-run/react-router/pull/15318)) + + #### Migration + + Apps using the default RSC Framework entry do not need to make any changes. Apps with a custom `entry.rsc.tsx` should import the generated client version and pass it to `unstable_matchRSCServerRequest`: + + ```tsx + import clientVersion from "virtual:react-router/unstable_rsc/client-version"; + + return unstable_matchRSCServerRequest({ + // ... + clientVersion, + }); + ``` + +- `react-router` - Add CSP nonce support to RSC document rendering ([#15320](https://github.com/remix-run/react-router/pull/15320)) + - Add `nonce` options to `unstable_routeRSCServerRequest` and `unstable_RSCStaticRouter` + - Forward the nonce to the HTML renderer and apply it to injected RSC payload scripts and nonce-aware framework components + + To adopt nonce-based CSP, update your `entry.ssr.tsx` (run `react-router reveal entry.ssr` first in RSC Framework Mode) to generate a fresh nonce for each request. Pass it to `routeRSCServerRequest`, spread the `renderHTML` options into React's HTML renderer, pass `options.nonce` to `RSCStaticRouter`, and use the same nonce in the `Content-Security-Policy` response header: + + ```tsx + const nonce = crypto.randomUUID(); + const response = await routeRSCServerRequest({ + request, + serverResponse, + createFromReadableStream, + nonce, + async renderHTML(getPayload, options) { + const payload = getPayload(); + return renderHTMLToReadableStream( + , + { + ...options, + bootstrapScriptContent, + formState: await payload.formState, + signal: request.signal, + }, + ); + }, + }); + response.headers.set( + "Content-Security-Policy", + `script-src 'self' 'nonce-${nonce}'`, + ); + ``` + +- `@react-router/dev` - Add `unstable_rsc/client-version` client build version virtual module ([#15318](https://github.com/remix-run/react-router/pull/15318)) + +- `@react-router/dev` - Support the `subResourceIntegrity` config option in RSC Framework Mode ([#15321](https://github.com/remix-run/react-router/pull/15321)) + + #### Migration guide + + No changes are required when using the default RSC SSR entry. If you maintain a custom `app/entry.ssr.tsx`, import the new virtual module and pass its hashes to React's `importMap` render option: + + ```diff + +import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity"; + + return renderToReadableStream(, { + ...options, + bootstrapScriptContent, + formState, + + importMap: subResourceIntegrity + + ? { integrity: subResourceIntegrity } + + : undefined, + signal: request.signal, + }); + ``` + +**Full Changelog**: [`v8.2.0...v8.3.0`](https://github.com/remix-run/react-router/compare/react-router@8.2.0...react-router@8.3.0) + ## v8.2.0 Date: 2026-07-08 diff --git a/packages/create-react-router/CHANGELOG.md b/packages/create-react-router/CHANGELOG.md index ac095d86de..8f76964af6 100644 --- a/packages/create-react-router/CHANGELOG.md +++ b/packages/create-react-router/CHANGELOG.md @@ -1,5 +1,11 @@ # `create-react-router` +## v8.3.0 + +### Patch Changes + +- _No changes_ + ## v8.2.0 ### Minor Changes diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json index ca37d076e5..591f0198ff 100644 --- a/packages/create-react-router/package.json +++ b/packages/create-react-router/package.json @@ -1,7 +1,7 @@ { "name": "create-react-router", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Create a new React Router app", "homepage": "https://reactrouter.com", "bugs": { diff --git a/packages/react-router-architect/CHANGELOG.md b/packages/react-router-architect/CHANGELOG.md index 9801e8cae5..b00158fb5b 100644 --- a/packages/react-router-architect/CHANGELOG.md +++ b/packages/react-router-architect/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/architect` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + - [`@react-router/node@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json index 0cc770f96b..9f34612754 100644 --- a/packages/react-router-architect/package.json +++ b/packages/react-router-architect/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/architect", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Architect server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -61,7 +61,7 @@ "peerDependencies": { "@react-router/node": "workspace:^", "react-router": "workspace:^", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-cloudflare/CHANGELOG.md b/packages/react-router-cloudflare/CHANGELOG.md index 40637211dc..e8d293c861 100644 --- a/packages/react-router-cloudflare/CHANGELOG.md +++ b/packages/react-router-cloudflare/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/cloudflare` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json index 44b10eb9c3..54c3e287e2 100644 --- a/packages/react-router-cloudflare/package.json +++ b/packages/react-router-cloudflare/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/cloudflare", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Cloudflare platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -50,7 +50,7 @@ "peerDependencies": { "@cloudflare/workers-types": "^4.0.0", "react-router": "workspace:^", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-dev/.changes/minor.development-conditions.md b/packages/react-router-dev/.changes/minor.development-conditions.md deleted file mode 100644 index 423b22f725..0000000000 --- a/packages/react-router-dev/.changes/minor.development-conditions.md +++ /dev/null @@ -1 +0,0 @@ -Restart `react-router dev` with `--conditions=development` when not already configured diff --git a/packages/react-router-dev/.changes/unstable.rsc-client-version.md b/packages/react-router-dev/.changes/unstable.rsc-client-version.md deleted file mode 100644 index 001cc296b0..0000000000 --- a/packages/react-router-dev/.changes/unstable.rsc-client-version.md +++ /dev/null @@ -1 +0,0 @@ -Add `unstable_rsc/client-version` client build version virtual module diff --git a/packages/react-router-dev/.changes/unstable.rsc-subresource-integrity.md b/packages/react-router-dev/.changes/unstable.rsc-subresource-integrity.md deleted file mode 100644 index 01a1047593..0000000000 --- a/packages/react-router-dev/.changes/unstable.rsc-subresource-integrity.md +++ /dev/null @@ -1,19 +0,0 @@ -Support the `subResourceIntegrity` config option in RSC Framework Mode - -#### Migration guide - -No changes are required when using the default RSC SSR entry. If you maintain a custom `app/entry.ssr.tsx`, import the new virtual module and pass its hashes to React's `importMap` render option: - -```diff -+import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity"; - -return renderToReadableStream(, { - ...options, - bootstrapScriptContent, - formState, -+ importMap: subResourceIntegrity -+ ? { integrity: subResourceIntegrity } -+ : undefined, - signal: request.signal, -}); -``` diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md index 886f5ded0c..ffb149f4b0 100644 --- a/packages/react-router-dev/CHANGELOG.md +++ b/packages/react-router-dev/CHANGELOG.md @@ -1,5 +1,46 @@ # `@react-router/dev` +## v8.3.0 + +### Minor Changes + +- Restart `react-router dev` with `--conditions=development` when not already configured ([#15291](https://github.com/remix-run/react-router/pull/15291)) + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- Add `unstable_rsc/client-version` client build version virtual module ([#15318](https://github.com/remix-run/react-router/pull/15318)) + +- Support the `subResourceIntegrity` config option in RSC Framework Mode ([#15321](https://github.com/remix-run/react-router/pull/15321)) + + #### Migration guide + + No changes are required when using the default RSC SSR entry. If you maintain a custom `app/entry.ssr.tsx`, import the new virtual module and pass its hashes to React's `importMap` render option: + + ```diff + +import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity"; + + return renderToReadableStream(, { + ...options, + bootstrapScriptContent, + formState, + + importMap: subResourceIntegrity + + ? { integrity: subResourceIntegrity } + + : undefined, + signal: request.signal, + }); + ``` + +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + - [`@react-router/node@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.3.0) + - [`@react-router/serve@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.3.0) + ## v8.2.0 ### Minor Changes diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json index 68460fe48f..7f25d822c1 100644 --- a/packages/react-router-dev/package.json +++ b/packages/react-router-dev/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/dev", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Dev tools and CLI for React Router", "keywords": [ "react", @@ -144,7 +144,7 @@ "@vitejs/plugin-rsc": "catalog:", "react-router": "workspace:^", "react-server-dom-webpack": "catalog:", - "typescript": "^5.1.0 || ^6.0.0", + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0", "vite": "^7.0.0 || ^8.0.0", "wrangler": "^4.0.0" }, diff --git a/packages/react-router-express/CHANGELOG.md b/packages/react-router-express/CHANGELOG.md index 9140efdf0b..8971e53f07 100644 --- a/packages/react-router-express/CHANGELOG.md +++ b/packages/react-router-express/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/express` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + - [`@react-router/node@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json index 92d36d8ece..a021fca8d8 100644 --- a/packages/react-router-express/package.json +++ b/packages/react-router-express/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/express", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Express server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -57,7 +57,7 @@ "peerDependencies": { "express": "^4.22.2 || ^5", "react-router": "workspace:*", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-fs-routes/CHANGELOG.md b/packages/react-router-fs-routes/CHANGELOG.md index c5a54a047d..23841a9f84 100644 --- a/packages/react-router-fs-routes/CHANGELOG.md +++ b/packages/react-router-fs-routes/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/fs-routes` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`@react-router/dev@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json index d220813406..4ff1b3269f 100644 --- a/packages/react-router-fs-routes/package.json +++ b/packages/react-router-fs-routes/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/fs-routes", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "File system routing conventions for React Router, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -51,7 +51,7 @@ }, "peerDependencies": { "@react-router/dev": "workspace:^", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-node/CHANGELOG.md b/packages/react-router-node/CHANGELOG.md index efe82bbc88..82fd7243bc 100644 --- a/packages/react-router-node/CHANGELOG.md +++ b/packages/react-router-node/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/node` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index 27f09bfe2a..c030b65222 100644 --- a/packages/react-router-node/package.json +++ b/packages/react-router-node/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/node", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Node.js platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -53,7 +53,7 @@ }, "peerDependencies": { "react-router": "workspace:*", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md index f1dcea567c..0f2d219194 100644 --- a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md +++ b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/remix-config-routes-adapter` +## v8.3.0 + +### Patch Changes + +- Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317)) +- Updated dependencies: + - [`@react-router/dev@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-remix-routes-option-adapter/package.json b/packages/react-router-remix-routes-option-adapter/package.json index bad639636c..1c5eb57da4 100644 --- a/packages/react-router-remix-routes-option-adapter/package.json +++ b/packages/react-router-remix-routes-option-adapter/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/remix-routes-option-adapter", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Adapter for Remix's \"routes\" config option, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" @@ -47,7 +47,7 @@ }, "peerDependencies": { "@react-router/dev": "workspace:^", - "typescript": "^5.1.0 || ^6.0.0" + "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-router-serve/CHANGELOG.md b/packages/react-router-serve/CHANGELOG.md index 446ea33f1e..4466b07bf9 100644 --- a/packages/react-router-serve/CHANGELOG.md +++ b/packages/react-router-serve/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/serve` +## v8.3.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0) + - [`@react-router/express@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/express@8.3.0) + - [`@react-router/node@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.3.0) + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json index e4bb702106..906ef0f3f3 100644 --- a/packages/react-router-serve/package.json +++ b/packages/react-router-serve/package.json @@ -1,7 +1,7 @@ { "name": "@react-router/serve", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Production application server for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router/.changes/patch.encode-path-params-pchar.md b/packages/react-router/.changes/patch.encode-path-params-pchar.md deleted file mode 100644 index 811f55cafe..0000000000 --- a/packages/react-router/.changes/patch.encode-path-params-pchar.md +++ /dev/null @@ -1,4 +0,0 @@ -Encode path params in `href`/`generatePath` per RFC 3986 path-segment rules instead of `encodeURIComponent` - -- Characters that are valid literally in a path segment (`$ & + , ; = : @` — RFC 3986 `pchar`) are no longer percent-encoded, so values like a semver build `1.0.0+1` interpolate unchanged instead of becoming `1.0.0%2B1` -- Structural/unsafe characters (`/ ? # %`, whitespace, non-ASCII) are still escaped exactly as before diff --git a/packages/react-router/.changes/patch.memory-session-random-uuid.md b/packages/react-router/.changes/patch.memory-session-random-uuid.md deleted file mode 100644 index a201faead7..0000000000 --- a/packages/react-router/.changes/patch.memory-session-random-uuid.md +++ /dev/null @@ -1,3 +0,0 @@ -Use `crypto.randomUUID()` for `createMemorySessionStorage` session ids - -- `createMemorySessionStorage` is only intended for local development and testing - sessions are lost when the server restarts diff --git a/packages/react-router/.changes/patch.navlink-pending-trailing-slash.md b/packages/react-router/.changes/patch.navlink-pending-trailing-slash.md deleted file mode 100644 index 085ad0f54f..0000000000 --- a/packages/react-router/.changes/patch.navlink-pending-trailing-slash.md +++ /dev/null @@ -1 +0,0 @@ -Fix `NavLink` not applying its `pending` state when `to` has a trailing slash diff --git a/packages/react-router/.changes/patch.rsc-client-loader-revalidation.md b/packages/react-router/.changes/patch.rsc-client-loader-revalidation.md deleted file mode 100644 index f3df2a93aa..0000000000 --- a/packages/react-router/.changes/patch.rsc-client-loader-revalidation.md +++ /dev/null @@ -1 +0,0 @@ -Preserve RSC route component metadata so routes with a `clientLoader` can skip unnecessary server requests once their components have rendered while still fetching missing server-rendered elements. diff --git a/packages/react-router/.changes/patch.rsc-csrf.md b/packages/react-router/.changes/patch.rsc-csrf.md deleted file mode 100644 index f778a415cf..0000000000 --- a/packages/react-router/.changes/patch.rsc-csrf.md +++ /dev/null @@ -1 +0,0 @@ -Harden RSC CSRF codepaths. diff --git a/packages/react-router/.changes/patch.rsc-html-stream-cancel.md b/packages/react-router/.changes/patch.rsc-html-stream-cancel.md deleted file mode 100644 index 322d3e5488..0000000000 --- a/packages/react-router/.changes/patch.rsc-html-stream-cancel.md +++ /dev/null @@ -1 +0,0 @@ -Fix server crash (`TypeError: Invalid state: Unable to enqueue`) when a request is aborted while the RSC HTML stream has a pending flush — `injectRSCPayload` now handles cancellation of its readable side, clears the pending flush, and cancels the underlying RSC payload stream diff --git a/packages/react-router/.changes/unstable.rsc-client-version.md b/packages/react-router/.changes/unstable.rsc-client-version.md deleted file mode 100644 index 66d652cf88..0000000000 --- a/packages/react-router/.changes/unstable.rsc-client-version.md +++ /dev/null @@ -1,14 +0,0 @@ -Detect stale RSC clients during lazy route discovery and reload the destination document - -#### Migration - -Apps using the default RSC Framework entry do not need to make any changes. Apps with a custom `entry.rsc.tsx` should import the generated client version and pass it to `unstable_matchRSCServerRequest`: - -```tsx -import clientVersion from "virtual:react-router/unstable_rsc/client-version"; - -return unstable_matchRSCServerRequest({ - // ... - clientVersion, -}); -``` diff --git a/packages/react-router/.changes/unstable.rsc-nonce.md b/packages/react-router/.changes/unstable.rsc-nonce.md deleted file mode 100644 index b4ce627b20..0000000000 --- a/packages/react-router/.changes/unstable.rsc-nonce.md +++ /dev/null @@ -1,32 +0,0 @@ -Add CSP nonce support to RSC document rendering - -- Add `nonce` options to `unstable_routeRSCServerRequest` and `unstable_RSCStaticRouter` -- Forward the nonce to the HTML renderer and apply it to injected RSC payload scripts and nonce-aware framework components - -To adopt nonce-based CSP, update your `entry.ssr.tsx` (run `react-router reveal entry.ssr` first in RSC Framework Mode) to generate a fresh nonce for each request. Pass it to `routeRSCServerRequest`, spread the `renderHTML` options into React's HTML renderer, pass `options.nonce` to `RSCStaticRouter`, and use the same nonce in the `Content-Security-Policy` response header: - -```tsx -const nonce = crypto.randomUUID(); -const response = await routeRSCServerRequest({ - request, - serverResponse, - createFromReadableStream, - nonce, - async renderHTML(getPayload, options) { - const payload = getPayload(); - return renderHTMLToReadableStream( - , - { - ...options, - bootstrapScriptContent, - formState: await payload.formState, - signal: request.signal, - }, - ); - }, -}); -response.headers.set( - "Content-Security-Policy", - `script-src 'self' 'nonce-${nonce}'`, -); -``` diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 2883614256..45abf9f3f4 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,71 @@ # `react-router` +## v8.3.0 + +### Patch Changes + +- Encode path params in `href`/`generatePath` per RFC 3986 path-segment rules instead of `encodeURIComponent` ([#15310](https://github.com/remix-run/react-router/pull/15310)) + - Characters that are valid literally in a path segment (`$ & + , ; = : @` — RFC 3986 `pchar`) are no longer percent-encoded, so values like a semver build `1.0.0+1` interpolate unchanged instead of becoming `1.0.0%2B1` + - Structural/unsafe characters (`/ ? # %`, whitespace, non-ASCII) are still escaped exactly as before +- Use `crypto.randomUUID()` for `createMemorySessionStorage` session ids ([#15302](https://github.com/remix-run/react-router/pull/15302)) + - `createMemorySessionStorage` is only intended for local development and testing - sessions are lost when the server restarts +- Fix `NavLink` not applying its `pending` state when `to` has a trailing slash ([#15300](https://github.com/remix-run/react-router/pull/15300)) +- Preserve RSC route component metadata so routes with a `clientLoader` can skip unnecessary server requests once their components have rendered while still fetching missing server-rendered elements ([#15323](https://github.com/remix-run/react-router/pull/15323)) +- Harden RSC CSRF code paths ([#15311](https://github.com/remix-run/react-router/pull/15311)) +- Fix server crash (`TypeError: Invalid state: Unable to enqueue`) when a request is aborted while the RSC HTML stream has a pending flush ([#15286](https://github.com/remix-run/react-router/pull/15286)) + - Handle cancellation of the `injectRSCPayload` readable side, clear the pending flush, and cancel the underlying RSC payload stream + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- Detect stale RSC clients during lazy route discovery and reload the destination document ([#15318](https://github.com/remix-run/react-router/pull/15318)) + + #### Migration + + Apps using the default RSC Framework entry do not need to make any changes. Apps with a custom `entry.rsc.tsx` should import the generated client version and pass it to `unstable_matchRSCServerRequest`: + + ```tsx + import clientVersion from "virtual:react-router/unstable_rsc/client-version"; + + return unstable_matchRSCServerRequest({ + // ... + clientVersion, + }); + ``` + +- Add CSP nonce support to RSC document rendering ([#15320](https://github.com/remix-run/react-router/pull/15320)) + - Add `nonce` options to `unstable_routeRSCServerRequest` and `unstable_RSCStaticRouter` + - Forward the nonce to the HTML renderer and apply it to injected RSC payload scripts and nonce-aware framework components + + To adopt nonce-based CSP, update your `entry.ssr.tsx` (run `react-router reveal entry.ssr` first in RSC Framework Mode) to generate a fresh nonce for each request. Pass it to `routeRSCServerRequest`, spread the `renderHTML` options into React's HTML renderer, pass `options.nonce` to `RSCStaticRouter`, and use the same nonce in the `Content-Security-Policy` response header: + + ```tsx + const nonce = crypto.randomUUID(); + const response = await routeRSCServerRequest({ + request, + serverResponse, + createFromReadableStream, + nonce, + async renderHTML(getPayload, options) { + const payload = getPayload(); + return renderHTMLToReadableStream( + , + { + ...options, + bootstrapScriptContent, + formState: await payload.formState, + signal: request.signal, + }, + ); + }, + }); + response.headers.set( + "Content-Security-Policy", + `script-src 'self' 'nonce-${nonce}'`, + ); + ``` + ## v8.2.0 ### Patch Changes diff --git a/packages/react-router/package.json b/packages/react-router/package.json index db9c66f667..91528904fc 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,7 +1,7 @@ { "name": "react-router", "type": "module", - "version": "8.2.0", + "version": "8.3.0", "description": "Declarative routing for React", "keywords": [ "react",