Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8c9c59a
Changes: add PR number or git sha link to changelogs
brophdawg11 Apr 13, 2026
b22a142
Merge branch 'release' into dev
brophdawg11 Apr 13, 2026
5427531
Update release finish script
brophdawg11 Apr 13, 2026
b34d1e5
Fix scripts type error
brophdawg11 Apr 13, 2026
b88d3f7
Add PR Preview builds (#14975)
brophdawg11 Apr 14, 2026
46321cf
Remove changesets (#14977)
brophdawg11 Apr 14, 2026
a224b8d
Update migrate changesets script to include SHA/PR number
brophdawg11 Apr 14, 2026
c54ebc1
chore: deduplicate `pnpm-lock.yaml`
remix-run-bot Apr 14, 2026
e4837ba
Reduce to an h3 in check-pr comment
brophdawg11 Apr 14, 2026
c3ac5dd
Re-enable experimental releases
brophdawg11 Apr 14, 2026
bb9433b
fix: RouterProviderProps already omits flushSync (#14874)
zeroqs Apr 14, 2026
142c703
Fix fetcher loader redirects from parent middleware (#14974)
brophdawg11 Apr 14, 2026
29b28e0
Improved types for `generatePath`s `params` arg (#14984)
pcattori Apr 15, 2026
8b9a55c
chore: format
remix-run-bot Apr 15, 2026
03b8b34
chore: generate markdown docs from jsdocs
remix-run-bot Apr 15, 2026
20045ff
Bring release-comments script into the repo (#14985)
brophdawg11 Apr 15, 2026
1a9f356
Remove stop worods from change file slugs
brophdawg11 Apr 15, 2026
2032c58
chore: format
remix-run-bot Apr 15, 2026
1941310
Fix typegen for pathless layouts with only nested layout children (#1…
nyxsky404 Apr 16, 2026
f9fd874
chore: format
remix-run-bot Apr 16, 2026
5981192
remove the un-documented custom error serialization logic (#14986)
jacob-ebey Apr 20, 2026
9248834
Add hasOwnProperty to build-time env check
brophdawg11 Apr 16, 2026
184bebe
chore: format
remix-run-bot Apr 20, 2026
cf06394
Update experimental release branch name
brophdawg11 Apr 20, 2026
689d760
Add description to experimental workflow dispatch input
brophdawg11 Apr 20, 2026
c6e324b
rsc: split route modules (#14965)
jacob-ebey Apr 20, 2026
ce48dee
chore: deduplicate `pnpm-lock.yaml`
remix-run-bot Apr 20, 2026
bc77b32
Adjust internal error serialization logic (#14992)
brophdawg11 Apr 20, 2026
e102c27
Migrate changeset -> change file
brophdawg11 Apr 20, 2026
0c1a0ff
Update release finish script
brophdawg11 Apr 20, 2026
aabf4a1
Merge branch 'main' into release
brophdawg11 Apr 20, 2026
d819650
Update PR preview comment for easier copy/pasting
brophdawg11 Apr 21, 2026
cf1d250
Release v7.14.2 (#14993)
ryanflorence Apr 21, 2026
56a6481
Merge branch 'release'
brophdawg11 Apr 21, 2026
8b1a110
chore: format
remix-run-bot Apr 21, 2026
fb7a2a6
Update release notes
brophdawg11 Apr 21, 2026
b861e71
Update GH actions, fix release notes PR annotations
brophdawg11 Apr 21, 2026
8d79c82
Move release comments to a standalone workflow for manual dispatch
brophdawg11 Apr 21, 2026
415847e
Fix release comment command escaping
brophdawg11 Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions .github/workflows/release-comment-manual.yml

This file was deleted.

39 changes: 39 additions & 0 deletions .github/workflows/release-comments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: 💬 Release Comments

on:
workflow_call:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
comment:
name: 📝 Comment on released issues/pull requests
runs-on: ubuntu-latest
permissions:
issues: write # enable commenting on released issues
pull-requests: write # enable commenting on released pull requests
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: 📦 Setup pnpm
uses: pnpm/action-setup@v4

- name: ⎔ Setup node
uses: actions/setup-node@v6
with:
node-version: 24 # Needed for node TS support
cache: "pnpm"

- name: 📥 Install deps
run: pnpm install --frozen-lockfile

- name: 📝 Comment on released issues and pull requests
env:
GH_TOKEN: ${{ github.token }}
run: pnpm run release-comments
19 changes: 13 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
inputs:
branch:
required: true
description: Experimental release branch

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -32,7 +33,7 @@ jobs:
has_change_files: ${{ steps.check.outputs.has_change_files }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Check for change files
id: check
Expand All @@ -51,22 +52,23 @@ jobs:
pull_request:
name: Open pull request
needs: check
if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'true'
if: needs.check.outputs.has_change_files == 'true'
runs-on: ubuntu-latest
permissions:
contents: write # enable pushing changes to the origin
pull-requests: write # enable opening a PR for the release
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.FORMAT_PAT }}

- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5

- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 24 # Needed to run typescript scripts directly
cache: pnpm
Expand All @@ -82,7 +84,7 @@ jobs:
publish:
name: Publish
needs: check
if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'false'
if: needs.check.outputs.has_change_files == 'false'
runs-on: ubuntu-latest
permissions:
contents: write # enable pushing changes to the origin
Expand Down Expand Up @@ -111,6 +113,11 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

comment:
name: 📝 Comment on released issues/pull requests
needs: publish
uses: ./.github/workflows/release-comments.yml

experimental-release:
name: 🧪 Experimental Release
if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch'
Expand Down
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We manage release notes in this file instead of the paginated Github Releases Pa
<summary>Table of Contents</summary>

- [React Router Releases](#react-router-releases)
- [v7.14.2](#v7142)
- [v7.14.1](#v7141)
- [v7.14.0](#v7140)
- [v7.13.2](#v7132)
Expand Down Expand Up @@ -168,6 +169,63 @@ We manage release notes in this file instead of the paginated Github Releases Pa

</details>

## v7.14.2

Date: 2026-04-21

### Patch Changes

- `react-router` - Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). ([#14992](https://github.com/remix-run/react-router/pull/14992))
- `react-router` - Properly handle parent middleware redirects during `fetcher.load` ([#14974](https://github.com/remix-run/react-router/pull/14974))
- `react-router` - Remove redundant `Omit<RouterProviderProps, "flushSync">` from `react-router/dom` `RouterProvider` ([#14874](https://github.com/remix-run/react-router/pull/14874))
- `react-router` - Improved types for `generatePath`'s `param` arg ([#14984](https://github.com/remix-run/react-router/pull/14984))
- Type errors when required params are omitted:

```ts
// Before
// Passes type checks, but throws at runtime 💥
generatePath(":required", { required: null });

// After
generatePath(":required", { required: null });
// ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322)
```

- Allow omission of optional params:

```ts
// Before
generatePath(":optional?", {});
// ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741)

// After
generatePath(":optional?", {});
```

- Allows extra keys:

```ts
// Before
generatePath(":a", { a: "1", b: "2" });
// ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353)

// After
generatePath(":a", { a: "1", b: "2" });
```

- `@react-router/dev` - Fix typegen for layouts without pages ([#14875](https://github.com/remix-run/react-router/pull/14875))
- Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages
- Now, `pages: never;` is correctly generated for those cases

### Unstable Changes

⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_

- `@react-router/dev` - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules ([#14965](https://github.com/remix-run/react-router/pull/14965))
- ⚠️ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array.

**Full Changelog**: [`v7.14.1...v7.14.2`](https://github.com/remix-run/react-router/compare/react-router@7.14.1...react-router@7.14.2)

## v7.14.1

Date: 2026-04-13
Expand Down
2 changes: 2 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
- nowells
- Nurai1
- nwleedev
- nyxsky404
- Obi-Dann
- okalil
- OlegDev1
Expand Down Expand Up @@ -486,5 +487,6 @@
- yuri-poliantsev
- zeevick10
- zeromask1337
- zeroqs
- zheng-chuang
- zxTomw
4 changes: 2 additions & 2 deletions docs/api/hooks/useMatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ This is useful for components that need to know "active" state, e.g.
## Signature

```tsx
function useMatch<ParamKey extends ParamParseKey<Path>, Path extends string>(
function useMatch<Path extends string>(
pattern: PathPattern<Path> | Path,
): PathMatch<ParamKey> | null
): PathMatch<ParamParseKey<Path>> | null
```

## Params
Expand Down
4 changes: 1 addition & 3 deletions docs/api/utils/generatePath.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ generatePath("/users/:id", { id: "123" }); // "/users/123"
```tsx
function generatePath<Path extends string>(
originalPath: Path,
params: {
[key in PathParam<Path>]: string | null;
} = as any,
params: GeneratePathParams<Path> = as any,
): string {}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/api/utils/matchPath.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ the match.
## Signature

```tsx
function matchPath<ParamKey extends ParamParseKey<Path>, Path extends string>(
function matchPath<Path extends string>(
pattern: PathPattern<Path> | Path,
pathname: string,
): PathMatch<ParamKey> | null
): PathMatch<ParamParseKey<Path>> | null
```

## Params
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"allowJs": true,
"skipLibCheck": true,
"baseUrl": ".",
"noEmit": true
"noEmit": true,
"rootDirs": [".", ".react-router/types/"]
}
}
20 changes: 0 additions & 20 deletions integration/helpers/playwright-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,26 +347,6 @@ async function doAndWait(
}
await networkSettledPromise;

// I wish I knew why but Safari seems to get all screwed up without this.
// When you run doAndWait (via clicking a blink or submitting a form) and
// then waitForSelector(). It finds the selector element but thinks it's
// hidden for some unknown reason. It's intermittent, but waiting for the
// next animation frame delaying slightly before the waitForSelector() calls
// seems to fix it 🤷‍♂️
//
// Test timeout of 30000ms exceeded.
//
// Error: page.waitForSelector: Target closed
// =========================== logs ===========================
// waiting for locator('text=ROOT_BOUNDARY_TEXT') to be visible
// locator resolved to hidden <div id="root-boundary">ROOT_BOUNDARY_TEXT</div>
// locator resolved to hidden <div id="root-boundary">ROOT_BOUNDARY_TEXT</div>
// ... and so on until the test times out
let userAgent = await page.evaluate(() => navigator.userAgent);
if (/Safari\//i.test(userAgent) && !/Chrome\//i.test(userAgent)) {
await page.evaluate(() => new Promise((r) => requestAnimationFrame(r)));
}

if (DEBUG) {
console.log(`action done, network settled`);
}
Expand Down
1 change: 1 addition & 0 deletions integration/helpers/rsc-vite-framework/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"include": ["**/*.ts", "**/*.tsx", "./.react-router/types/**/*"],
"exclude": ["vite.config*"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"strict": true,
Expand Down
2 changes: 2 additions & 0 deletions integration/helpers/rsc-vite-framework/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { defineConfig } from "vite";
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
import react from "@vitejs/plugin-react";
import rsc from "@vitejs/plugin-rsc";

export default defineConfig({
plugins: [
// @ts-ignore
reactRouterRSC({ __runningWithinTheReactRouterMonoRepo: true }),
react(),
rsc(),
],
});
5 changes: 3 additions & 2 deletions integration/helpers/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const viteConfig = {
? "import { reactRouter } from '@react-router/dev/vite';"
: [
"import { unstable_reactRouterRSC as reactRouterRSC } from '@react-router/dev/vite';",
"import react from '@vitejs/plugin-react';",
"import rsc from '@vitejs/plugin-rsc';",
].join("\n")
}
Expand All @@ -118,10 +119,10 @@ export const viteConfig = {
let useNativeTsconfigPaths =
parseInt(vite.version.split(".")[0], 10) >= 8;
let plugins = [
${args.mdx ? "mdx()," : ""}
${args.mdx ? "{enforce: 'pre', ...mdx()}," : ""}
${args.vanillaExtract ? "vanillaExtractPlugin({ emitCssInSsr: true })," : ""}
${isRsc ? " reactRouterRSC({ __runningWithinTheReactRouterMonoRepo: true })," : "reactRouter(),"}
${isRsc ? "rsc()," : ""}
${isRsc ? "react(), rsc()," : ""}
envOnlyMacros(),
];

Expand Down
19 changes: 19 additions & 0 deletions integration/typegen-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,4 +868,23 @@ test.describe("typegen", () => {
});
});
});

test("layout without pages", async ({ edit, $ }) => {
await edit({
"app/routes.ts": tsx`
import { type RouteConfig, layout } from "@react-router/dev/routes";

export default [
layout("routes/layout.tsx", []),
] satisfies RouteConfig;
`,
"app/routes/layout.tsx": tsx`
import { Outlet } from "react-router"
export default function Component() {
return <div><Outlet /></div>
}
`,
});
await $("pnpm typecheck");
});
});
Loading
Loading