From 44ceb6dbbc0f3139aaae59aaf0fedaf56902ca7d Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 8 Jul 2026 10:07:26 -0400 Subject: [PATCH 1/4] Use runtime-specific default server entries (#15290) * Use runtime-specific default server entries Assisted-By: devx/e4d8ffad-97c4-40b6-9373-8114dbad5591 * Add readable stream future flag for Node defaults * Split Node readable stream future flag change file * Mark Node readable stream flag change as unstable * Clarify Node readable stream future flag docs * Update Assisted-By: devx/e4d8ffad-97c4-40b6-9373-8114dbad5591 * Update Node readable stream change note * Fix e2e tests Assisted-By: devx/e4d8ffad-97c4-40b6-9373-8114dbad5591 --- docs/upgrading/future.md | 33 ++++++++++ integration/cli-test.ts | 62 ++++++++++++++++++- integration/vite-plugin-cloudflare-test.ts | 20 +++++- integration/vite-presets-test.ts | 1 + .../minor.default-server-entry-runtime.md | 5 ++ ...stable.node-readable-stream-future-flag.md | 3 + packages/react-router-dev/cli/commands.ts | 13 ++-- packages/react-router-dev/config/config.ts | 35 ++++++++--- .../config/defaults/entry.server.web.tsx | 15 ++++- packages/react-router-dev/vite/plugin.ts | 7 +-- .../app/entry.server.tsx | 43 ------------- 11 files changed, 169 insertions(+), 68 deletions(-) create mode 100644 packages/react-router-dev/.changes/minor.default-server-entry-runtime.md create mode 100644 packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md rename integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx => packages/react-router-dev/config/defaults/entry.server.web.tsx (79%) delete mode 100644 playground/vite-plugin-cloudflare/app/entry.server.tsx diff --git a/docs/upgrading/future.md b/docs/upgrading/future.md index 65182ed69c..b54361e751 100644 --- a/docs/upgrading/future.md +++ b/docs/upgrading/future.md @@ -46,6 +46,39 @@ _No known planned breaking changes yet_ We document some [unstable] flags here as a reference for folks contributing to the project via beta testing, but they are not generally recommended for production use and may have breaking changes in patch or minor releases - adopt with caution! +### `future.unstable_enableNodeReadableStream` + +[MODES: framework] + +
+
+ +**Background** + +Now that the Web Streams API is [stable](https://nodejs.org/docs/latest-v22.x/api/webstreams.html) in Node 22+, it's viable for React Router to use React's [`renderToReadableStream`](https://react.dev/reference/react-dom/server/renderToReadableStream) in the server entry. + +When no `entry.server.tsx` file is present, React Router defaults to [`renderToPipeableStream`](https://react.dev/reference/react-dom/server/renderToPipeableStream) when a Node runtime is detected, and `renderToReadableStream` otherwise. + +With this flag enabled, React Router will default to `renderToReadableStream` on all runtimes, including Node. You can continue to use `renderToPipeableStream` via a custom `entry.server.tsx` file if needed. + +Enabling this flag might even provide slight performance gains because we are already using Web Streams internally, so this flag removes some unnecessary transforms between Web and Node streams. + +👉 **Enable the Flag** + +```ts filename=react-router.config.ts +import type { Config } from "@react-router/dev/config"; + +export default { + future: { + unstable_enableNodeReadableStream: true, + }, +} satisfies Config; +``` + +**Update your Code** + +No code changes are required. If your app has a custom `entry.server.tsx`, this flag will not change your runtime behavior. + ### `future.unstable_optimizeDeps` [MODES: framework] diff --git a/integration/cli-test.ts b/integration/cli-test.ts index 471e1b4184..6d0d2c5e10 100644 --- a/integration/cli-test.ts +++ b/integration/cli-test.ts @@ -4,6 +4,7 @@ import { existsSync, mkdirSync, mkdtempSync, + readFileSync, rmSync, writeFileSync, } from "node:fs"; @@ -15,7 +16,7 @@ import { expect, test } from "@playwright/test"; import dedent from "dedent"; import semver from "semver"; -import { createProject } from "./helpers/vite"; +import { build, createProject, reactRouterConfig } from "./helpers/vite"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDirectory = path.resolve(__dirname, ".."); @@ -198,6 +199,51 @@ test.describe("cli", () => { expect(existsSync(entryServerFile)).toBeTruthy(); expect(existsSync(entryClientFile)).toBeTruthy(); + expect(readFileSync(entryServerFile, "utf-8")).toContain( + "renderToPipeableStream", + ); + }); + + test("generates a web server entry for non-Node projects", async () => { + const cwd = await createProject(); + let packageJsonPath = path.join(cwd, "package.json"); + let pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + delete pkg.dependencies["@react-router/express"]; + delete pkg.dependencies["@react-router/node"]; + delete pkg.dependencies["@react-router/serve"]; + writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2)); + + let entryServerFile = path.join(cwd, "app", "entry.server.tsx"); + + expect(existsSync(entryServerFile)).toBeFalsy(); + + run(["reveal", "entry.server"], { cwd }); + + expect(existsSync(entryServerFile)).toBeTruthy(); + expect(readFileSync(entryServerFile, "utf-8")).toContain( + "renderToReadableStream", + ); + }); + + test("generates a web server entry for Node projects with the readable stream future flag", async () => { + const cwd = await createProject({ + "react-router.config.ts": reactRouterConfig({ + future: { + unstable_enableNodeReadableStream: true, + }, + }), + }); + + let entryServerFile = path.join(cwd, "app", "entry.server.tsx"); + + expect(existsSync(entryServerFile)).toBeFalsy(); + + run(["reveal", "entry.server"], { cwd }); + + expect(existsSync(entryServerFile)).toBeTruthy(); + expect(readFileSync(entryServerFile, "utf-8")).toContain( + "renderToReadableStream", + ); }); test("rsc generates entry.{ssr,rsc,client}.tsx in the app directory", async () => { @@ -250,4 +296,18 @@ test.describe("cli", () => { expect(existsSync(entryClientFile)).toBeTruthy(); }); }); + + test("builds a Node project with the readable stream future flag and default server entry", async () => { + const cwd = await createProject({ + "react-router.config.ts": reactRouterConfig({ + future: { + unstable_enableNodeReadableStream: true, + }, + }), + }); + + const buildResult = build({ cwd }); + + expect(buildResult.status).toBe(0); + }); }); diff --git a/integration/vite-plugin-cloudflare-test.ts b/integration/vite-plugin-cloudflare-test.ts index a8ceb5754a..31fee594fc 100644 --- a/integration/vite-plugin-cloudflare-test.ts +++ b/integration/vite-plugin-cloudflare-test.ts @@ -2,7 +2,13 @@ import { expect } from "@playwright/test"; import dedent from "dedent"; import getPort from "get-port"; -import { type Files, test, viteConfig } from "./helpers/vite.js"; +import { + build, + createProject, + type Files, + test, + viteConfig, +} from "./helpers/vite.js"; const tsx = dedent; const css = dedent; @@ -177,4 +183,16 @@ test.describe("vite-plugin-cloudflare", () => { "20px", ); }); + + test("builds project with default server entry", async () => { + const files = defineFiles(); + const cwd = await createProject( + await files({ port: 0 }), + "vite-plugin-cloudflare-template", + ); + + const buildResult = build({ cwd }); + + expect(buildResult.status).toBe(0); + }); }); diff --git a/integration/vite-presets-test.ts b/integration/vite-presets-test.ts index 9fbd0a1473..ece2db31d7 100644 --- a/integration/vite-presets-test.ts +++ b/integration/vite-presets-test.ts @@ -246,6 +246,7 @@ test.describe("Vite / presets", async () => { // Ensure future flags from presets are properly merged expect(buildEndArgsMeta.futureFlags).toEqual({ + unstable_enableNodeReadableStream: false, unstable_optimizeDeps: true, }); expect(buildEndArgsMeta.splitRouteModules).toBe(true); diff --git a/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md b/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md new file mode 100644 index 0000000000..66e7e5a70b --- /dev/null +++ b/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md @@ -0,0 +1,5 @@ +Add a web streams default server entry for non-Node Framework mode apps + +- Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry +- Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry +- Non-Node apps with their own `entry.server.tsx` may be able to remove it in favor of the default if it is not doing anything custom diff --git a/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md b/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md new file mode 100644 index 0000000000..17e089bf1b --- /dev/null +++ b/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md @@ -0,0 +1,3 @@ +Add a new [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` + +- This flag has no effect if you have your own `entry.server.tsx` diff --git a/packages/react-router-dev/cli/commands.ts b/packages/react-router-dev/cli/commands.ts index 481a2cc886..e83556b168 100644 --- a/packages/react-router-dev/cli/commands.ts +++ b/packages/react-router-dev/cli/commands.ts @@ -10,7 +10,7 @@ import "react-router"; import type { ViteDevOptions } from "../vite/dev"; import type { ViteBuildOptions } from "../vite/build"; -import { loadConfig } from "../config/config"; +import { hasNodeDependency, loadConfig } from "../config/config"; import { formatRoutes } from "../config/format"; import type { RoutesFormat } from "../config/format"; import { transpile as convertFileToJS } from "./useJavascript"; @@ -171,12 +171,6 @@ export async function generateEntry( await copyFile(defaultEntry, outputFile); } else { let pkgJson = await readPackageJSON(rootDirectory); - let deps = pkgJson.dependencies ?? {}; - - if (!deps["@react-router/node"]) { - console.error(colors.red(`No default server entry detected.`)); - return; - } let defaultEntryClient = path.resolve( defaultsDirectory, @@ -185,7 +179,10 @@ export async function generateEntry( let defaultEntryServer = path.resolve( defaultsDirectory, - `entry.server.node.tsx`, + hasNodeDependency(pkgJson.dependencies) && + !configResult.value.future.unstable_enableNodeReadableStream + ? `entry.server.node.tsx` + : `entry.server.web.tsx`, ); let isServerEntry = entry === "entry.server"; diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts index edce424a69..4773c09b2c 100644 --- a/packages/react-router-dev/config/config.ts +++ b/packages/react-router-dev/config/config.ts @@ -8,7 +8,12 @@ import chokidar, { type FSWatcher, type EmitArgs as ChokidarEmitArgs, } from "chokidar"; -import { readPackageJSON, sortPackage, updatePackage } from "pkg-types"; +import { + readPackageJSON, + sortPackage, + updatePackage, + type PackageJson, +} from "pkg-types"; import colors from "picocolors"; import pick from "lodash/pick.js"; import omit from "lodash/omit.js"; @@ -89,6 +94,7 @@ type ServerModuleFormat = "esm" | "cjs"; type ValidateConfigFunction = (config: ReactRouterConfig) => string | void; interface FutureConfig { + unstable_enableNodeReadableStream: boolean; unstable_optimizeDeps: boolean; } @@ -738,6 +744,8 @@ async function resolveConfig({ } let future: FutureConfig = { + unstable_enableNodeReadableStream: + userAndPresetConfigs.future?.unstable_enableNodeReadableStream ?? false, unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false, }; @@ -1045,12 +1053,6 @@ export async function resolveEntryFiles({ let pkgJson = await readPackageJSON(packageJsonDirectory); let deps = pkgJson.dependencies ?? {}; - if (!deps["@react-router/node"]) { - throw new Error( - `Could not determine server runtime. Please install @react-router/node, or provide a custom entry.server.tsx/jsx file in your app directory.`, - ); - } - if (!deps["isbot"]) { console.log( "adding `isbot@5` to your package.json, you should commit this change", @@ -1070,7 +1072,11 @@ export async function resolveEntryFiles({ }); } - entryServerFile = `entry.server.node.tsx`; + entryServerFile = + hasNodeDependency(deps) && + !reactRouterConfig.future.unstable_enableNodeReadableStream + ? `entry.server.node.tsx` + : `entry.server.web.tsx`; } let entryClientFilePath = userEntryClientFile @@ -1127,6 +1133,19 @@ function omitRoutes( const entryExts = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".mts"]; +export function hasNodeDependency(deps: PackageJson["dependencies"]) { + // Match the server condition check in the Vite plugin: missing dependencies + // imply Node for backwards compatibility. + return ( + !deps || + Boolean( + deps["@react-router/node"] || + deps["@react-router/express"] || + deps["@react-router/serve"], + ) + ); +} + function isEntryFile(entryBasename: string, filename: string) { return entryExts.some((ext) => filename === `${entryBasename}${ext}`); } diff --git a/integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx b/packages/react-router-dev/config/defaults/entry.server.web.tsx similarity index 79% rename from integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx rename to packages/react-router-dev/config/defaults/entry.server.web.tsx index 6ac910a0f8..b8e9fcd328 100644 --- a/integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx +++ b/packages/react-router-dev/config/defaults/entry.server.web.tsx @@ -3,6 +3,8 @@ import { ServerRouter } from "react-router"; import { isbot } from "isbot"; import { renderToReadableStream } from "react-dom/server"; +export const streamTimeout = 5_000; + export default async function handleRequest( request: Request, responseStatusCode: number, @@ -10,15 +12,24 @@ export default async function handleRequest( routerContext: EntryContext, _loadContext: RouterContextProvider, ) { + // https://httpwg.org/specs/rfc9110.html#HEAD + if (request.method.toUpperCase() === "HEAD") { + return new Response(null, { + status: responseStatusCode, + headers: responseHeaders, + }); + } + let shellRendered = false; - const userAgent = request.headers.get("user-agent"); + let userAgent = request.headers.get("user-agent"); const body = await renderToReadableStream( , { + signal: AbortSignal.timeout(streamTimeout + 1000), onError(error: unknown) { responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log + // Log streaming rendering errors from inside the shell. Don't log // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index 4c785aee35..ac89e62bc9 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -84,6 +84,7 @@ import { type BuildManifest, type ConfigLoader, createConfigLoader, + hasNodeDependency, resolveEntryFiles, configRouteToBranchRoute, type PrerenderPaths, @@ -3577,11 +3578,7 @@ export async function getEnvironmentOptionsResolvers( let defaultExternalConditions = vite.defaultExternalConditions ?? ["node"]; // If we couldn't find the package.json, we assume node for backwards compatibility - let isNode = - !pkgJson.dependencies || - pkgJson.dependencies["@react-router/node"] || - pkgJson.dependencies["@react-router/express"] || - pkgJson.dependencies["@react-router/serve"]; + let isNode = hasNodeDependency(pkgJson.dependencies); if (!isNode) { maybeDefaultServerConditions = maybeDefaultServerConditions.filter( diff --git a/playground/vite-plugin-cloudflare/app/entry.server.tsx b/playground/vite-plugin-cloudflare/app/entry.server.tsx deleted file mode 100644 index 6ac910a0f8..0000000000 --- a/playground/vite-plugin-cloudflare/app/entry.server.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { EntryContext, RouterContextProvider } from "react-router"; -import { ServerRouter } from "react-router"; -import { isbot } from "isbot"; -import { renderToReadableStream } from "react-dom/server"; - -export default async function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - routerContext: EntryContext, - _loadContext: RouterContextProvider, -) { - let shellRendered = false; - const userAgent = request.headers.get("user-agent"); - - const body = await renderToReadableStream( - , - { - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - }, - ); - shellRendered = true; - - // Ensure requests from bots and SPA Mode renders wait for all content to load before responding - // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation - if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) { - await body.allReady; - } - - responseHeaders.set("Content-Type", "text/html"); - return new Response(body, { - headers: responseHeaders, - status: responseStatusCode, - }); -} From 63861d5cf470dd202616faabe8cf545883896e48 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 8 Jul 2026 10:25:41 -0400 Subject: [PATCH 2/4] Add release notes preparation skill Assisted-By: devx/a66d11d7-9a11-4761-8e32-5504beaa4231 --- .agents/skills/prepare-release-notes/SKILL.md | 97 +++++++++++++++++++ .../prepare-release-notes/agents/openai.yaml | 4 + .../references/whats-changed.md | 35 +++++++ 3 files changed, 136 insertions(+) create mode 100644 .agents/skills/prepare-release-notes/SKILL.md create mode 100644 .agents/skills/prepare-release-notes/agents/openai.yaml create mode 100644 .agents/skills/prepare-release-notes/references/whats-changed.md diff --git a/.agents/skills/prepare-release-notes/SKILL.md b/.agents/skills/prepare-release-notes/SKILL.md new file mode 100644 index 0000000000..d16a8a5c63 --- /dev/null +++ b/.agents/skills/prepare-release-notes/SKILL.md @@ -0,0 +1,97 @@ +--- +name: prepare-release-notes +description: Prepare React Router release notes before running the changes/versioning scripts. Use when asked to review, polish, normalize, or prepare pending change files under packages/*/.changes, remove semantic commit prefixes from release bullets, enforce imperative tense, decide whether a manual scripts/changes/whats-changed.md section is warranted, or draft long-form release notes for new features, stable future flags, or unstable flags. +--- + +# Prepare Release Notes + +Polish pending React Router change files and add manual release notes only when the release needs narrative context beyond the generated change lists. + +## Workflow + +1. Inspect local state: + +```sh +git status --short +find packages -path '*/.changes/*.md' -not -name README.md -not -name .gitkeep -print | sort +``` + +2. Read every pending change file. Do not edit generated changelogs or released notes directly. + +3. Normalize each change file: + - Remove `feat:`, `feat(...)`, `fix:`, and `fix(...)` semantic-commit prefixes from prose + - Use present or imperative tense: prefer `Add`, `Fix`, `Remove`, `Support`, `Stabilize`, `Preserve`, `Update`, `Avoid`, `Prevent`, `Throw`, `Warn`, `Expose` + - Remove terminal sentence periods from bullet items because release generation appends PR/commit links after the first bullet line + - Remove terminal sentence periods from nested bullet items too, unless the punctuation is part of code, a URL, an abbreviation, a version number, or another token where removing it would be wrong + - If one bullet contains multiple sentences, split it into a shorter parent bullet plus nested bullet items + - Keep the first line concise and user-facing; use nested bullets for details or migration notes + +4. Review whether `scripts/changes/whats-changed.md` is needed: + - Read `CHANGELOG.md` examples or `references/whats-changed.md` when uncertain + - Add `scripts/changes/whats-changed.md` only for features, future flag stabilizations, unstable flags, migration guidance, breaking bug fixes, or complex behavior that needs long-form text or examples + - Do not add it for ordinary bug fixes, dependency cleanup, internal refactors, or release bullets that are already clear + - If adding it, write the body only; the release script adds `### What's Changed` when missing + +5. Validate: + +```sh +pnpm changes:validate +pnpm changes:preview +``` + +Use `changes:preview` to inspect the generated root release notes and confirm the PR/commit link placement, section ordering, and any manual What's Changed placement. If dependencies are missing or the command is too expensive for the context, state what was skipped. + +## Change File Style + +Single-line entries should read well with an auto-appended PR link: + +```markdown +Fix `href()` to stringify and URL-encode param values like `generatePath()` +``` + +Use nested bullets for additional sentences: + +```markdown +Fix route ranking for dynamic parameters with static extension suffixes + +- Identify `/:name.xml` as a dynamic segment instead of a static segment +- Preserve static route priority for paths like `/sitemap.xml` +``` + +Avoid semantic commit prefixes: + +```markdown +Add support for nub as a package manager +``` + +not: + +```markdown +feat: add support for nub as a package manager. +``` + +## What's Changed Guidance + +Use `scripts/changes/whats-changed.md` for release-level narrative, not package-specific bullets. Good candidates include: + +- A new user-facing API or feature that benefits from example code +- Stabilization or renaming of unstable APIs/flags, especially when adopters must migrate +- A stable future flag that changes behavior and needs adoption guidance +- A breaking bug fix or adapter/runtime behavior change that may require deployment checks +- A cluster of related changes whose combined effect matters more than the individual bullet list + +Keep the tone direct and practical. Prefer headings under the generated `### What's Changed` section: + +````markdown +#### Feature Name + +Explain what changed, who it affects, and how to adopt it. + +```ts +// Optional short example +``` +```` + +Do not duplicate every bullet from Minor/Patch/Unstable Changes. Let generated change files carry ordinary PR-level details. + +See `references/whats-changed.md` for examples distilled from the existing changelog. diff --git a/.agents/skills/prepare-release-notes/agents/openai.yaml b/.agents/skills/prepare-release-notes/agents/openai.yaml new file mode 100644 index 0000000000..9a5cf0ece1 --- /dev/null +++ b/.agents/skills/prepare-release-notes/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Prepare Release Notes" + short_description: "Polish React Router release notes" + default_prompt: "Use $prepare-release-notes to review pending React Router change files and draft any needed What's Changed notes." diff --git a/.agents/skills/prepare-release-notes/references/whats-changed.md b/.agents/skills/prepare-release-notes/references/whats-changed.md new file mode 100644 index 0000000000..69b40c722d --- /dev/null +++ b/.agents/skills/prepare-release-notes/references/whats-changed.md @@ -0,0 +1,35 @@ +# What's Changed Reference + +Load this when deciding whether a React Router release needs `scripts/changes/whats-changed.md`. + +## Include A Manual Section For + +- Major releases with baseline changes, removed packages, removed deprecated APIs, new runtime requirements, or migration guidance +- Stabilizations and flag renames where adopters of unstable APIs need explicit before/after guidance +- New APIs or flags that deserve a narrative introduction or usage example +- Cross-cutting performance or behavior work where several bullets form one user-facing story +- Breaking bug fixes, deployment-sensitive fixes, or adapter/runtime behavior changes where users may need to test or update config + +## Usually Do Not Include One For + +- Straightforward bug fixes +- Dependency removals or upgrades where the bullet is sufficient +- Internal refactors with no public API change +- A minor feature that is fully understandable from one concise bullet and nested detail bullets +- Releases where the only content is a normal patch list + +## Existing Changelog Patterns + +- `v8.1.0`: Uses headings for agent skill installation and observability metadata, including an instrumentation code example. The generated minor bullets still carry the per-package details. +- `v8.0.0`: Uses long-form migration notes for a major release: baseline support, adopted future flags, removed packages/APIs, and behavior changes. +- `v7.18.0`: Explains a CSRF check fix that may be a breaking bug fix for reverse-proxy deployments and tells users what to test. +- `v7.15.0`: Groups several unstable-to-stable API renames and route matching optimizations into narrative sections before listing individual minor bullets. +- `v7.15.1`: Uses a "What's New" style section for an unstable hook with a code example. For new release notes, prefer the current `What's Changed` file path; the generated heading will be `### What's Changed`. + +## Drafting Tips + +- Use `####` headings inside `scripts/changes/whats-changed.md`; the release script wraps the file in `### What's Changed` if needed +- Mention applicable React Router modes when the distinction matters: Declarative, Data, Framework, RSC Data, or RSC Framework +- Keep examples short and directly tied to adoption +- Avoid repeating the package prefix and PR-link details already provided by generated change sections +- If unsure, run `pnpm changes:preview` with and without the manual section and keep it only if the generated notes are meaningfully clearer with the narrative From d6ed3515c736b9954ec9dd108e1262fe4443919b Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 8 Jul 2026 11:22:50 -0400 Subject: [PATCH 3/4] Prep 8.2.0 release notes --- .agents/skills/prepare-release-notes/SKILL.md | 3 ++- .../minor.detect-nub-package-manager.md | 2 +- .../minor.default-server-entry-runtime.md | 2 +- .../minor.detect-nub-package-manager.md | 2 +- ...detect-user-rolldownoptions-config-vite.md | 2 +- ...stable.node-readable-stream-future-flag.md | 2 +- ...atch.fix-optional-static-segment-params.md | 2 +- ...ank-static-routes-above-suffixed-params.md | 2 +- .../.changes/patch.rsc-form-state.md | 2 +- scripts/changes/whats-changed.md | 19 +++++++++++++++++++ 10 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 scripts/changes/whats-changed.md diff --git a/.agents/skills/prepare-release-notes/SKILL.md b/.agents/skills/prepare-release-notes/SKILL.md index d16a8a5c63..8abb9a81d1 100644 --- a/.agents/skills/prepare-release-notes/SKILL.md +++ b/.agents/skills/prepare-release-notes/SKILL.md @@ -20,7 +20,8 @@ find packages -path '*/.changes/*.md' -not -name README.md -not -name .gitkeep - 3. Normalize each change file: - Remove `feat:`, `feat(...)`, `fix:`, and `fix(...)` semantic-commit prefixes from prose - - Use present or imperative tense: prefer `Add`, `Fix`, `Remove`, `Support`, `Stabilize`, `Preserve`, `Update`, `Avoid`, `Prevent`, `Throw`, `Warn`, `Expose` + - Use present or imperative tense for the first line and top-level release bullets: prefer `Add`, `Fix`, `Remove`, `Support`, `Stabilize`, `Preserve`, `Update`, `Avoid`, `Prevent`, `Throw`, `Warn`, `Expose` + - Nested detail bullets can stay explanatory when they expand on the parent bullet; do not rewrite them solely to force present or imperative tense - Remove terminal sentence periods from bullet items because release generation appends PR/commit links after the first bullet line - Remove terminal sentence periods from nested bullet items too, unless the punctuation is part of code, a URL, an abbreviation, a version number, or another token where removing it would be wrong - If one bullet contains multiple sentences, split it into a shorter parent bullet plus nested bullet items diff --git a/packages/create-react-router/.changes/minor.detect-nub-package-manager.md b/packages/create-react-router/.changes/minor.detect-nub-package-manager.md index 57fc984e5d..5505c4838a 100644 --- a/packages/create-react-router/.changes/minor.detect-nub-package-manager.md +++ b/packages/create-react-router/.changes/minor.detect-nub-package-manager.md @@ -1 +1 @@ -Detect nub as a supported package manager when creating new projects +Detect `nub` as a supported package manager when creating new projects diff --git a/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md b/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md index 66e7e5a70b..abb3720576 100644 --- a/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md +++ b/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md @@ -1,4 +1,4 @@ -Add a web streams default server entry for non-Node Framework mode apps +Add a Web Streams default server entry for non-Node Framework mode apps - Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry - Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry diff --git a/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md b/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md index ad0907e34b..5b29d3753c 100644 --- a/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md +++ b/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md @@ -1 +1 @@ -Detect nub as a supported package manager when installing framework dependencies +Detect `nub` as a supported package manager when installing framework dependencies diff --git a/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md b/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md index 9c429f9aa2..c3185b4763 100644 --- a/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md +++ b/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md @@ -1 +1 @@ -Properly detect user `rolldownOptions` config in Vite 8+ +Detect user `rolldownOptions` config in Vite 8+ diff --git a/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md b/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md index 17e089bf1b..deefbae636 100644 --- a/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md +++ b/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md @@ -1,3 +1,3 @@ -Add a new [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` +Add the [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` - This flag has no effect if you have your own `entry.server.tsx` diff --git a/packages/react-router/.changes/patch.fix-optional-static-segment-params.md b/packages/react-router/.changes/patch.fix-optional-static-segment-params.md index 3a93e1fe09..b4e3758cce 100644 --- a/packages/react-router/.changes/patch.fix-optional-static-segment-params.md +++ b/packages/react-router/.changes/patch.fix-optional-static-segment-params.md @@ -1,4 +1,4 @@ -Fix incorrect dynamic param extraction when optional static segments are present +Fix dynamic param extraction for routes with optional static segments - When a route path contains optional static segments (e.g. `/school?/user/:id`), the internal regex's incorrectly shifted parameter indices resulting in incorrect parameter extraction - Consecutive optional static segments (e.g. `/one?/two?`) were only partially handled diff --git a/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md b/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md index 0132c8269e..ddac5333f9 100644 --- a/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md +++ b/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md @@ -1,4 +1,4 @@ -Fix route ranking/scoring bug with dynamic parameters containing static extension suffixes (i.e., `/:name.xml`) +Fix route ranking for dynamic parameters with static extension suffixes - These were not being detected as dynamic param segments and instead got incorrectly scored higher as a static segment - This meant they could potentially tie truly static routes like `/sitemap.xml` and outrank them based on definition order diff --git a/packages/react-router/.changes/patch.rsc-form-state.md b/packages/react-router/.changes/patch.rsc-form-state.md index 1bf83a8517..d17a9820a6 100644 --- a/packages/react-router/.changes/patch.rsc-form-state.md +++ b/packages/react-router/.changes/patch.rsc-form-state.md @@ -1 +1 @@ -Update to use ReactFormState types instead of unknown. +Use ReactFormState types instead of unknown diff --git a/scripts/changes/whats-changed.md b/scripts/changes/whats-changed.md new file mode 100644 index 0000000000..b8339688ea --- /dev/null +++ b/scripts/changes/whats-changed.md @@ -0,0 +1,19 @@ +#### Web Streams Default Server Entry + +Non-Node runtime Framework Mode apps no longer need a custom `entry.server.tsx` file using React's `renderToReadableStream` API. Apps with `@react-router/{node,express,serve}` dependencies will continue to default to `renderToPipeableStream`, while non-Node apps default to `renderToReadableStream`. + +Because Web Streams are stable in Node 22+, Node apps can also opt-into the Web Streams default entry with the new `future.unstable_enableNodeReadableStream` flag: + +```ts filename=react-router.config.ts +import type { Config } from "@react-router/dev/config"; + +export default { + future: { + unstable_enableNodeReadableStream: true, + }, +} satisfies Config; +``` + +This flag has no effect if you have a custom `entry.server.tsx` keep using their custom entry file. It only applies to the default entry used if one doesn't exist. + +Node apps opting-into the Web Streams API _might_ even see a small performance boost because React Router already uses Web Streams internally, so this avoids additional conversions between Web/Node streams. If you see perf changes one way or another upon adopting this flag, please let us know! From 05180441b118d26da4df94bacc2211923d87e4c1 Mon Sep 17 00:00:00 2001 From: Ryan Florence Date: Wed, 8 Jul 2026 11:21:03 -0600 Subject: [PATCH 4/4] Release v8.2.0 (#15264) Co-authored-by: Remix Run Bot --- CHANGELOG.md | 61 +++++++++++++++++++ .../minor.detect-nub-package-manager.md | 1 - packages/create-react-router/CHANGELOG.md | 6 ++ packages/create-react-router/package.json | 2 +- packages/react-router-architect/CHANGELOG.md | 8 +++ packages/react-router-architect/package.json | 2 +- packages/react-router-cloudflare/CHANGELOG.md | 7 +++ packages/react-router-cloudflare/package.json | 2 +- .../minor.default-server-entry-runtime.md | 5 -- .../minor.detect-nub-package-manager.md | 1 - ...detect-user-rolldownoptions-config-vite.md | 1 - ...stable.node-readable-stream-future-flag.md | 3 - packages/react-router-dev/CHANGELOG.md | 25 ++++++++ packages/react-router-dev/package.json | 2 +- packages/react-router-express/CHANGELOG.md | 8 +++ packages/react-router-express/package.json | 2 +- packages/react-router-fs-routes/CHANGELOG.md | 7 +++ packages/react-router-fs-routes/package.json | 2 +- packages/react-router-node/CHANGELOG.md | 7 +++ packages/react-router-node/package.json | 2 +- .../CHANGELOG.md | 7 +++ .../package.json | 2 +- packages/react-router-serve/CHANGELOG.md | 9 +++ packages/react-router-serve/package.json | 2 +- .../.changes/patch.encode-href-params.md | 3 - ...atch.fix-optional-static-segment-params.md | 4 -- ...tion-blocker-state-through-revalidation.md | 1 - ...ank-static-routes-above-suffixed-params.md | 5 -- .../.changes/patch.rsc-form-state.md | 1 - packages/react-router/CHANGELOG.md | 16 +++++ packages/react-router/package.json | 2 +- scripts/changes/whats-changed.md | 19 ------ 32 files changed, 171 insertions(+), 54 deletions(-) delete mode 100644 packages/create-react-router/.changes/minor.detect-nub-package-manager.md delete mode 100644 packages/react-router-dev/.changes/minor.default-server-entry-runtime.md delete mode 100644 packages/react-router-dev/.changes/minor.detect-nub-package-manager.md delete mode 100644 packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md delete mode 100644 packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md delete mode 100644 packages/react-router/.changes/patch.encode-href-params.md delete mode 100644 packages/react-router/.changes/patch.fix-optional-static-segment-params.md delete mode 100644 packages/react-router/.changes/patch.preserve-navigation-blocker-state-through-revalidation.md delete mode 100644 packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md delete mode 100644 packages/react-router/.changes/patch.rsc-form-state.md delete mode 100644 scripts/changes/whats-changed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4875750f..de2fa1fe14 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.2.0](#v820) + - [Web Streams Default Server Entry](#web-streams-default-server-entry) - [v8.1.0](#v810) - [Agent Skills Installation via `create-react-router`](#agent-skills-installation-via-create-react-router) - [Observability Metadata](#observability-metadata) @@ -107,6 +109,65 @@ We manage release notes in this file instead of the paginated Github Releases Pa +## v8.2.0 + +Date: 2026-07-08 + +### What's Changed + +#### Web Streams Default Server Entry + +Non-Node runtime Framework Mode apps no longer need a custom `entry.server.tsx` file using React's `renderToReadableStream` API. Apps with `@react-router/{node,express,serve}` dependencies will continue to default to `renderToPipeableStream`, while non-Node apps default to `renderToReadableStream`. + +Because Web Streams are stable in Node 22+, Node apps can also opt-into the Web Streams default entry with the new `future.unstable_enableNodeReadableStream` flag: + +```ts filename=react-router.config.ts +import type { Config } from "@react-router/dev/config"; + +export default { + future: { + unstable_enableNodeReadableStream: true, + }, +} satisfies Config; +``` + +This flag has no effect if you have a custom `entry.server.tsx` keep using their custom entry file. It only applies to the default entry used if one doesn't exist. + +Node apps opting-into the Web Streams API _might_ even see a small performance boost because React Router already uses Web Streams internally, so this avoids additional conversions between Web/Node streams. If you see perf changes one way or another upon adopting this flag, please let us know! + +### Minor Changes + +- `@react-router/dev` - Add a Web Streams default server entry for non-Node Framework mode apps ([#15290](https://github.com/remix-run/react-router/pull/15290)) + - Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry + - Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry + - Non-Node apps with their own `entry.server.tsx` may be able to remove it in favor of the default if it is not doing anything custom +- `@react-router/dev` - Detect `nub` as a supported package manager when installing framework dependencies ([#15276](https://github.com/remix-run/react-router/pull/15276)) +- `create-react-router` - Detect `nub` as a supported package manager when creating new projects ([#15276](https://github.com/remix-run/react-router/pull/15276)) + +### Patch Changes + +- `react-router` - Fix `href()` to properly stringify and URL-encode param values, matching `generatePath()` ([#15277](https://github.com/remix-run/react-router/pull/15277)) + - splat params preserve path separators while encoding each segment individually +- `react-router` - Fix dynamic param extraction for routes with optional static segments ([#15200](https://github.com/remix-run/react-router/pull/15200)) + - When a route path contains optional static segments (e.g. `/school?/user/:id`), the internal regex's incorrectly shifted parameter indices resulting in incorrect parameter extraction + - Consecutive optional static segments (e.g. `/one?/two?`) were only partially handled +- `react-router` - Preserve navigation blocker state through a revalidation ([#15246](https://github.com/remix-run/react-router/pull/15246)) +- `react-router` - Fix route ranking for dynamic parameters with static extension suffixes ([#15273](https://github.com/remix-run/react-router/pull/15273)) + - These were not being detected as dynamic param segments and instead got incorrectly scored higher as a static segment + - This meant they could potentially tie truly static routes like `/sitemap.xml` and outrank them based on definition order + - These are now correctly identified as dynamic parameter segments and scored correctly +- `react-router` - Use ReactFormState types instead of unknown ([#15263](https://github.com/remix-run/react-router/pull/15263)) +- `@react-router/dev` - Detect user `rolldownOptions` config in Vite 8+ ([#15278](https://github.com/remix-run/react-router/pull/15278)) + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- `@react-router/dev` - Add the [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` ([#15290](https://github.com/remix-run/react-router/pull/15290)) + - This flag has no effect if you have your own `entry.server.tsx` + +**Full Changelog**: [`v8.1.0...v8.2.0`](https://github.com/remix-run/react-router/compare/react-router@8.1.0...react-router@8.2.0) + ## v8.1.0 Date: 2026-06-29 diff --git a/packages/create-react-router/.changes/minor.detect-nub-package-manager.md b/packages/create-react-router/.changes/minor.detect-nub-package-manager.md deleted file mode 100644 index 5505c4838a..0000000000 --- a/packages/create-react-router/.changes/minor.detect-nub-package-manager.md +++ /dev/null @@ -1 +0,0 @@ -Detect `nub` as a supported package manager when creating new projects diff --git a/packages/create-react-router/CHANGELOG.md b/packages/create-react-router/CHANGELOG.md index 9a3996c9e4..ac095d86de 100644 --- a/packages/create-react-router/CHANGELOG.md +++ b/packages/create-react-router/CHANGELOG.md @@ -1,5 +1,11 @@ # `create-react-router` +## v8.2.0 + +### Minor Changes + +- Detect `nub` as a supported package manager when creating new projects ([#15276](https://github.com/remix-run/react-router/pull/15276)) + ## v8.1.0 ### Minor Changes diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json index a282395c79..ca37d076e5 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.1.0", + "version": "8.2.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 ebedd043d6..9801e8cae5 100644 --- a/packages/react-router-architect/CHANGELOG.md +++ b/packages/react-router-architect/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/architect` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json index d21551ec8a..0cc770f96b 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.1.0", + "version": "8.2.0", "description": "Architect server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-cloudflare/CHANGELOG.md b/packages/react-router-cloudflare/CHANGELOG.md index 1e06837081..40637211dc 100644 --- a/packages/react-router-cloudflare/CHANGELOG.md +++ b/packages/react-router-cloudflare/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/cloudflare` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json index 5eeddace80..44b10eb9c3 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.1.0", + "version": "8.2.0", "description": "Cloudflare platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md b/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md deleted file mode 100644 index abb3720576..0000000000 --- a/packages/react-router-dev/.changes/minor.default-server-entry-runtime.md +++ /dev/null @@ -1,5 +0,0 @@ -Add a Web Streams default server entry for non-Node Framework mode apps - -- Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry -- Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry -- Non-Node apps with their own `entry.server.tsx` may be able to remove it in favor of the default if it is not doing anything custom diff --git a/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md b/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md deleted file mode 100644 index 5b29d3753c..0000000000 --- a/packages/react-router-dev/.changes/minor.detect-nub-package-manager.md +++ /dev/null @@ -1 +0,0 @@ -Detect `nub` as a supported package manager when installing framework dependencies diff --git a/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md b/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md deleted file mode 100644 index c3185b4763..0000000000 --- a/packages/react-router-dev/.changes/patch.properly-detect-user-rolldownoptions-config-vite.md +++ /dev/null @@ -1 +0,0 @@ -Detect user `rolldownOptions` config in Vite 8+ diff --git a/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md b/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md deleted file mode 100644 index deefbae636..0000000000 --- a/packages/react-router-dev/.changes/unstable.node-readable-stream-future-flag.md +++ /dev/null @@ -1,3 +0,0 @@ -Add the [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` - -- This flag has no effect if you have your own `entry.server.tsx` diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md index 83213fd938..f2032f1de7 100644 --- a/packages/react-router-dev/CHANGELOG.md +++ b/packages/react-router-dev/CHANGELOG.md @@ -1,5 +1,30 @@ # `@react-router/dev` +## v8.2.0 + +### Minor Changes + +- Add a Web Streams default server entry for non-Node Framework mode apps ([#15290](https://github.com/remix-run/react-router/pull/15290)) + - Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry + - Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry + - Non-Node apps with their own `entry.server.tsx` may be able to remove it in favor of the default if it is not doing anything custom +- Detect `nub` as a supported package manager when installing framework dependencies ([#15276](https://github.com/remix-run/react-router/pull/15276)) + +### Patch Changes + +- Detect user `rolldownOptions` config in Vite 8+ ([#15278](https://github.com/remix-run/react-router/pull/15278)) + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- Add the [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` ([#15290](https://github.com/remix-run/react-router/pull/15290)) + - This flag has no effect if you have your own `entry.server.tsx` +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0) + - [`@react-router/serve@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json index 381d4e83f3..b3e986e566 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.1.0", + "version": "8.2.0", "description": "Dev tools and CLI for React Router", "keywords": [ "react", diff --git a/packages/react-router-express/CHANGELOG.md b/packages/react-router-express/CHANGELOG.md index ef65397247..9140efdf0b 100644 --- a/packages/react-router-express/CHANGELOG.md +++ b/packages/react-router-express/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/express` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json index 34c8fd1db7..92d36d8ece 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.1.0", + "version": "8.2.0", "description": "Express server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-fs-routes/CHANGELOG.md b/packages/react-router-fs-routes/CHANGELOG.md index 9fa7fbbb79..c5a54a047d 100644 --- a/packages/react-router-fs-routes/CHANGELOG.md +++ b/packages/react-router-fs-routes/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/fs-routes` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`@react-router/dev@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json index b4b63af1b4..d220813406 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.1.0", + "version": "8.2.0", "description": "File system routing conventions for React Router, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-node/CHANGELOG.md b/packages/react-router-node/CHANGELOG.md index baa5cbdb92..efe82bbc88 100644 --- a/packages/react-router-node/CHANGELOG.md +++ b/packages/react-router-node/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/node` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index bb824bcd1c..27f09bfe2a 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.1.0", + "version": "8.2.0", "description": "Node.js platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md index 917d97f27a..f1dcea567c 100644 --- a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md +++ b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/remix-config-routes-adapter` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`@react-router/dev@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@8.2.0) + ## v8.1.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 444d452930..bad639636c 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.1.0", + "version": "8.2.0", "description": "Adapter for Remix's \"routes\" config option, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-serve/CHANGELOG.md b/packages/react-router-serve/CHANGELOG.md index 7fbde4acd1..446ea33f1e 100644 --- a/packages/react-router-serve/CHANGELOG.md +++ b/packages/react-router-serve/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/serve` +## v8.2.0 + +### Patch Changes + +- Updated dependencies: + - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0) + - [`@react-router/express@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/express@8.2.0) + - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0) + ## v8.1.0 ### Patch Changes diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json index 49c3e8732e..e4bb702106 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.1.0", + "version": "8.2.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-href-params.md b/packages/react-router/.changes/patch.encode-href-params.md deleted file mode 100644 index c810872df0..0000000000 --- a/packages/react-router/.changes/patch.encode-href-params.md +++ /dev/null @@ -1,3 +0,0 @@ -Fix `href()` to properly stringify and URL-encode param values, matching `generatePath()` - -- splat params preserve path separators while encoding each segment individually diff --git a/packages/react-router/.changes/patch.fix-optional-static-segment-params.md b/packages/react-router/.changes/patch.fix-optional-static-segment-params.md deleted file mode 100644 index b4e3758cce..0000000000 --- a/packages/react-router/.changes/patch.fix-optional-static-segment-params.md +++ /dev/null @@ -1,4 +0,0 @@ -Fix dynamic param extraction for routes with optional static segments - -- When a route path contains optional static segments (e.g. `/school?/user/:id`), the internal regex's incorrectly shifted parameter indices resulting in incorrect parameter extraction -- Consecutive optional static segments (e.g. `/one?/two?`) were only partially handled diff --git a/packages/react-router/.changes/patch.preserve-navigation-blocker-state-through-revalidation.md b/packages/react-router/.changes/patch.preserve-navigation-blocker-state-through-revalidation.md deleted file mode 100644 index 634adafd2d..0000000000 --- a/packages/react-router/.changes/patch.preserve-navigation-blocker-state-through-revalidation.md +++ /dev/null @@ -1 +0,0 @@ -Preserve navigation blocker state through a revalidation diff --git a/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md b/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md deleted file mode 100644 index ddac5333f9..0000000000 --- a/packages/react-router/.changes/patch.rank-static-routes-above-suffixed-params.md +++ /dev/null @@ -1,5 +0,0 @@ -Fix route ranking for dynamic parameters with static extension suffixes - -- These were not being detected as dynamic param segments and instead got incorrectly scored higher as a static segment -- This meant they could potentially tie truly static routes like `/sitemap.xml` and outrank them based on definition order -- These are now correctly identified as dynamic parameter segments and scored correctly diff --git a/packages/react-router/.changes/patch.rsc-form-state.md b/packages/react-router/.changes/patch.rsc-form-state.md deleted file mode 100644 index d17a9820a6..0000000000 --- a/packages/react-router/.changes/patch.rsc-form-state.md +++ /dev/null @@ -1 +0,0 @@ -Use ReactFormState types instead of unknown diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 14c58666f2..2883614256 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,21 @@ # `react-router` +## v8.2.0 + +### Patch Changes + +- Fix `href()` to properly stringify and URL-encode param values, matching `generatePath()` ([#15277](https://github.com/remix-run/react-router/pull/15277)) + - splat params preserve path separators while encoding each segment individually +- Fix dynamic param extraction for routes with optional static segments ([#15200](https://github.com/remix-run/react-router/pull/15200)) + - When a route path contains optional static segments (e.g. `/school?/user/:id`), the internal regex's incorrectly shifted parameter indices resulting in incorrect parameter extraction + - Consecutive optional static segments (e.g. `/one?/two?`) were only partially handled +- Preserve navigation blocker state through a revalidation ([#15246](https://github.com/remix-run/react-router/pull/15246)) +- Fix route ranking for dynamic parameters with static extension suffixes ([#15273](https://github.com/remix-run/react-router/pull/15273)) + - These were not being detected as dynamic param segments and instead got incorrectly scored higher as a static segment + - This meant they could potentially tie truly static routes like `/sitemap.xml` and outrank them based on definition order + - These are now correctly identified as dynamic parameter segments and scored correctly +- Use ReactFormState types instead of unknown ([#15263](https://github.com/remix-run/react-router/pull/15263)) + ## v8.1.0 ### Minor Changes diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 9fd6c022e1..db9c66f667 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,7 +1,7 @@ { "name": "react-router", "type": "module", - "version": "8.1.0", + "version": "8.2.0", "description": "Declarative routing for React", "keywords": [ "react", diff --git a/scripts/changes/whats-changed.md b/scripts/changes/whats-changed.md deleted file mode 100644 index b8339688ea..0000000000 --- a/scripts/changes/whats-changed.md +++ /dev/null @@ -1,19 +0,0 @@ -#### Web Streams Default Server Entry - -Non-Node runtime Framework Mode apps no longer need a custom `entry.server.tsx` file using React's `renderToReadableStream` API. Apps with `@react-router/{node,express,serve}` dependencies will continue to default to `renderToPipeableStream`, while non-Node apps default to `renderToReadableStream`. - -Because Web Streams are stable in Node 22+, Node apps can also opt-into the Web Streams default entry with the new `future.unstable_enableNodeReadableStream` flag: - -```ts filename=react-router.config.ts -import type { Config } from "@react-router/dev/config"; - -export default { - future: { - unstable_enableNodeReadableStream: true, - }, -} satisfies Config; -``` - -This flag has no effect if you have a custom `entry.server.tsx` keep using their custom entry file. It only applies to the default entry used if one doesn't exist. - -Node apps opting-into the Web Streams API _might_ even see a small performance boost because React Router already uses Web Streams internally, so this avoids additional conversions between Web/Node streams. If you see perf changes one way or another upon adopting this flag, please let us know!