Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
92d54fd
Merge branch 'release' into dev
brophdawg11 May 14, 2026
4b464a9
chore: format
remix-run-bot May 14, 2026
c35008f
feat(@react-router/dev): log future flag warnings for upcoming v8 fla…
brophdawg11 May 14, 2026
16eb79e
test: fix typo in test description (#15053)
dfedoryshchev May 15, 2026
cfb5fb0
Disable manifest handler when lazy route discovery is disabled (#15068)
brophdawg11 May 18, 2026
dc996ea
fix(router): pass configured window to createBrowserURLImpl (#15066)
jeevan6996 May 18, 2026
04dcb50
Move change file workflow to 2-step run (#15069)
brophdawg11 May 18, 2026
55b1d99
Consolidate pr workflows (#15070)
brophdawg11 May 18, 2026
50511dc
fix(node): honor writable backpressure when streaming to Node writabl…
jacob-ebey May 18, 2026
0d636fe
Cleanup workflows
brophdawg11 May 18, 2026
5b2e59f
Fix preview build typo
brophdawg11 May 19, 2026
e96962b
fix: remove stale unpkg field from react-router-dom (#15075)
brophdawg11 May 19, 2026
98e8b3b
fix(serve): normalize assetsBuildDirectory separators in react-router…
jeevan6996 May 20, 2026
3ed77af
chore: format
remix-run-bot May 20, 2026
d7980d2
test: fix duplicate "route" in fetcher-layout test descriptions (#15094)
dfedoryshchev May 25, 2026
4cde90b
Support array of objects for `script:ld+json` meta descriptor (#15082)
apple-yagi May 26, 2026
6e21b63
fix: keep useNavigation() return type a discriminated union (#15095)
csamuel May 26, 2026
c503313
chore: generate markdown docs from jsdocs
remix-run-bot May 26, 2026
a4e8a7f
Inline support-requests GH action logic and drop external action (#15…
brophdawg11 May 26, 2026
d71025d
Stabilize trailing slash aware data requests (#15098)
brophdawg11 May 26, 2026
d96da32
Merge branch 'main' into release
brophdawg11 May 27, 2026
09ecb37
Ignore writes after Express responses close (#15107)
jacob-ebey May 28, 2026
8984d23
Release v7.16.0 (#15105)
ryanflorence May 28, 2026
eae9f3f
Merge branch 'release'
brophdawg11 May 28, 2026
4099277
chore: format
remix-run-bot May 28, 2026
731cd8f
Update changelog
brophdawg11 May 28, 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
39 changes: 39 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.16.0](#v7160)
- [v7.15.1](#v7151)
- [v7.15.0](#v7150)
- [Stabilizations](#stabilizations)
Expand Down Expand Up @@ -173,6 +174,44 @@ We manage release notes in this file instead of the paginated Github Releases Pa

</details>

## v7.16.0

Date: 2026-05-28

### What's Changed

#### Stabilized `future.v8_trailingSlashAwareDataRequests`

We've stabilized this flag in preparation for the upcoming v8 release. Unless you are doing specific path-inspection on `.data` requests or have specific CDN/caching/pre-rendering logic around `.data` requests, this should largely be a zero-code-change adoptions. Please see the [docs](https://reactrouter.com/upgrading/future#futurev8_trailingslashawaredatarequests) for more info.

#### Pre-v8 Future Flag Warnings

In preparation for the upcoming v8 release, `7.16.0` begins logging console warnings during builds for future flags that you have not enabled yet. If you have all future flags enabled, the v8 upgrade should be mostly non-breaking (short of some underlying dependency minimum versions bumps - React 19, Node 22.12, Vite 7). You can suppress these warnings until you are ready top adopt flags by setting an explicit `false` in your `react-router.config.ts`.

### Minor Changes

- `react-router` - Stabilize `future.unstable_trailingSlashAwareDataRequests` as `future.v8_trailingSlashAwareDataRequests` ([#15098](https://github.com/remix-run/react-router/pull/15098))
- `@react-router/dev` - Log future flag warnings for upcoming React Router v8 flags ([#15029](https://github.com/remix-run/react-router/pull/15029))
- `v8_middleware`, `v8_splitRouteModules`, `v8_viteEnvironmentApi`, `v8_passThroughRequests`, `v8_trailingSlashAwareDataRequests`

### Patch Changes

- `react-router` - Disable manifest path when lazy route discovery is disabled ([#15068](https://github.com/remix-run/react-router/pull/15068))
- `react-router` - Fix browser URL creation to use the configured history `window` instead of the global `window` ([#15066](https://github.com/remix-run/react-router/pull/15066))
- Pass the history/router window through to `createBrowserURLImpl` so custom window contexts keep the correct URL origin.
- `react-router` - Fix `useNavigation()` return type to preserve discriminated union across navigation states ([#15095](https://github.com/remix-run/react-router/pull/15095))
- `react-router` - Widen `MetaDescriptor` `script:ld+json` type from `LdJsonObject` to `LdJsonObject | LdJsonObject[]` to permit multiple JSON-LD schemas in a single `<script type="application/ld+json">` tag emitted by `<Meta />` ([#15082](https://github.com/remix-run/react-router/pull/15082))
- `react-router-dom` - Remove stale/invalid `unpkg` field from `package.json` ([#15075](https://github.com/remix-run/react-router/pull/15075))
- This was removed from other packages with the release of v7 but missed in the `react-router-dom` re-export package
- `@react-router/express` - Ignore writes after Express responses close ([#15107](https://github.com/remix-run/react-router/pull/15107))
- Avoid surfacing client disconnects as adapter errors when the response stream has already been destroyed or ended
- `@react-router/node` - Honor Node writable backpressure in `writeReadableStreamToWritable` and `writeAsyncIterableToWritable` ([#15071](https://github.com/remix-run/react-router/pull/15071))
- Await `'drain'` when `writable.write()` returns `false` instead of letting chunks accumulate in the writable's internal buffer
- Reject (rather than hang) if the writable errors or closes mid-stream
- `@react-router/serve` - Normalize `assetsBuildDirectory` path separators in `react-router-serve` so Windows-built server artifacts can serve `/assets/*` correctly when run on Linux ([#14982](https://github.com/remix-run/react-router/pull/14982))

**Full Changelog**: [`v7.15.1...v7.16.0`](https://github.com/remix-run/react-router/compare/react-router@7.15.1...react-router@7.16.0)

## v7.15.1

Date: 2026-05-14
Expand Down
4 changes: 4 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
- codeape2
- coolport
- coryhouse
- csamuel
- ctnelson1997
- cvbuelow
- dadamssg
Expand Down Expand Up @@ -164,6 +165,7 @@
- harucn
- HelpMe-Pls
- HenriqueLimas
- henryqdineen
- hernanif1
- HeyyyNeo
- hi-ogawa
Expand All @@ -174,6 +176,7 @@
- hsbtr
- hyesungoh
- iamnishanth
- ianduvall
- ianflynnwork
- IbraRouisDev
- igniscyan
Expand Down Expand Up @@ -202,6 +205,7 @@
- jb-1980
- jclarkin
- jdufresne
- jeevan6996
- jenseng
- JeraldVin
- JesusTheHun
Expand Down
2 changes: 1 addition & 1 deletion docs/api/hooks/useNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function SomeComponent() {
## Signature

```tsx
function useNavigation(): Omit<Navigation, "matches" | "historyAction">
function useNavigation(): UseNavigationResult
```

## Returns
Expand Down
53 changes: 53 additions & 0 deletions docs/upgrading/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,59 @@ export async function loader({
}
```

## `future.v8_trailingSlashAwareDataRequests`

[MODES: framework]

<br/>
<br/>

**Background**

React Router serves Framework mode data requests from `.data` URLs. Previously, data requests for routes with and without trailing slashes could map to the same `.data` URL because trailing slashes were not considered during URL generation. This flag preserves trailing slash semantics for data request URLs to avoid ambiguity when your app distinguishes between trailing-slash and non-trailing-slash URLs.

Currently, your HTTP and `request` pathnames would be as follows for `/a/b/c` and `/a/b/c/`

| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
| ------------ | ----------------- | ----------------------- |
| **Document** | `/a/b/c` | `/a/b/c` ✅ |
| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |

| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
| ------------- | ----------------- | ----------------------- |
| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
| **Data** | `/a/b/c.data` | `/a/b/c` ⚠️ |

With this flag enabled, these pathnames will be made consistent though a new `_.data` format for client-side `.data` requests:

| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
| ------------ | ----------------- | ----------------------- |
| **Document** | `/a/b/c` | `/a/b/c` ✅ |
| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |

| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
| ------------- | ------------------ | ----------------------- |
| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
| **Data** | `/a/b/c/_.data` ⬅️ | `/a/b/c/` ✅ |

This flag also aligns the root data request to match this behavior by changing it from `/_root.data` to `/_.data`.

👉 **Enable the Flag**

```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
future: {
v8_trailingSlashAwareDataRequests: true,
},
} satisfies Config;
```

**Update your Code**

If you have custom app, CDN, cache, or rewrite logic that matches `.data` request URLs, update it to handle the new trailing-slash-aware `/_.data` format.

## Unstable Future Flags (Optional)

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 having breaking changes patch/minor releases - adopt with caution!
Expand Down
12 changes: 11 additions & 1 deletion integration/cli-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,17 @@ test.describe("cli", () => {
test("routes", async () => {
const cwd = await createProject();
let { stdout, stderr, status } = run(["routes"], { cwd });
expect(stdout.toString().trim()).toBe(dedent`

// Filter out future flag warnings for the format:
// ⚠️ Future Flag Warning: Route module splitting behavior is changing in React Router v8.
// You can use the `future.v8_splitRouteModules` flag to opt in early.
// -> https://reactrouter.com/upgrading/future-flags#v8_splitRouteModules
let filteredStdOut = stdout.toString().split("\n");
while (filteredStdOut[0]?.includes("Future Flag Warning:")) {
filteredStdOut.splice(0, 3);
}

expect(filteredStdOut.join("\n").trim()).toBe(dedent`
<Routes>
<Route file="root.tsx">
<Route index file="routes/_index.tsx" />
Expand Down
4 changes: 2 additions & 2 deletions integration/fetcher-layout-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ test("fetcher calls layout route loader when at parameterized route", async ({
expect(dataElement.text()).toBe("layout loader data");
});

test("fetcher calls parameterized route route action", async ({ page }) => {
test("fetcher calls parameterized route action", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-action/foo");
await app.clickElement("#param-fetcher");
Expand All @@ -266,7 +266,7 @@ test("fetcher calls parameterized route route action", async ({ page }) => {
expect(dataElement.text()).toBe("foo");
});

test("fetcher calls parameterized route route loader", async ({ page }) => {
test("fetcher calls parameterized route loader", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-loader/foo");
await app.clickElement("#param-fetcher");
Expand Down
6 changes: 3 additions & 3 deletions integration/single-fetch-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4567,7 +4567,7 @@ test.describe("single-fetch", () => {
expect(await app.getHtml("h1")).toMatch("It worked!");
});

test("always uses /{path}.data without future.unstable_trailingSlashAwareDataRequests flag", async ({
test("always uses /{path}.data without future.v8_trailingSlashAwareDataRequests flag", async ({
page,
}) => {
let fixture = await createFixture({
Expand Down Expand Up @@ -4659,15 +4659,15 @@ test.describe("single-fetch", () => {
requests = [];
});

test("uses {path}.data or {path}/_.data depending on trailing slash with future.unstable_trailingSlashAwareDataRequests flag", async ({
test("uses {path}.data or {path}/_.data depending on trailing slash with future.v8_trailingSlashAwareDataRequests flag", async ({
page,
}) => {
let fixture = await createFixture({
files: {
...files,
"react-router.config.ts": reactRouterConfig({
future: {
unstable_trailingSlashAwareDataRequests: true,
v8_trailingSlashAwareDataRequests: true,
},
}),
"app/routes/_index.tsx": js`
Expand Down
2 changes: 1 addition & 1 deletion integration/vite-presets-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ test.describe("Vite / presets", async () => {
// Ensure future flags from presets are properly merged
expect(buildEndArgsMeta.futureFlags).toEqual({
unstable_optimizeDeps: true,
unstable_trailingSlashAwareDataRequests: false,
v8_trailingSlashAwareDataRequests: false,
unstable_previewServerPrerendering: false,
v8_middleware: true,
v8_passThroughRequests: false,
Expand Down
6 changes: 6 additions & 0 deletions packages/create-react-router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# `create-react-router`

## v7.16.0

### Patch Changes

- _No changes_

## v7.15.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/create-react-router/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-react-router",
"version": "7.15.1",
"version": "7.16.0",
"description": "Create a new React Router app",
"homepage": "https://reactrouter.com",
"bugs": {
Expand Down
8 changes: 8 additions & 0 deletions packages/react-router-architect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# `@react-router/architect`

## v7.16.0

### Patch Changes

- Updated dependencies:
- [`react-router@7.16.0`](https://github.com/remix-run/react-router/releases/tag/react-router@7.16.0)
- [`@react-router/node@7.16.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.16.0)

## v7.15.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router-architect/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@react-router/architect",
"version": "7.15.1",
"version": "7.16.0",
"description": "Architect server request handler for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
Expand Down
7 changes: 7 additions & 0 deletions packages/react-router-cloudflare/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# `@react-router/cloudflare`

## v7.16.0

### Patch Changes

- Updated dependencies:
- [`react-router@7.16.0`](https://github.com/remix-run/react-router/releases/tag/react-router@7.16.0)

## v7.15.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@react-router/cloudflare",
"version": "7.15.1",
"version": "7.16.0",
"description": "Cloudflare platform abstractions for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
Expand Down
17 changes: 17 additions & 0 deletions packages/react-router-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# `@react-router/dev`

## v7.16.0

### Minor Changes

- Stabilize `future.unstable_trailingSlashAwareDataRequests` as `future.v8_trailingSlashAwareDataRequests` ([#15098](https://github.com/remix-run/react-router/pull/15098))
- The unstable flag is no longer supported and will error during config resolution

- Log future flag warnings for upcoming React Router v8 flags ([#15029](https://github.com/remix-run/react-router/pull/15029))
- `v8_middleware`, `v8_splitRouteModules`, `v8_viteEnvironmentApi`, `v8_passThroughRequests`, `v8_trailingSlashAwareDataRequests`

### Patch Changes

- Updated dependencies:
- [`react-router@7.16.0`](https://github.com/remix-run/react-router/releases/tag/react-router@7.16.0)
- [`@react-router/node@7.16.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.16.0)
- [`@react-router/serve@7.16.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@7.16.0)

## v7.15.1

### Patch Changes
Expand Down
66 changes: 66 additions & 0 deletions packages/react-router-dev/__tests__/future-flags-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { logFutureFlagWarnings } from "../config/config";

describe("logFutureFlagWarnings", () => {
let logSpy: jest.SpyInstance;

beforeEach(() => {
logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
});

afterEach(() => {
logSpy.mockRestore();
});

it("warns about all stable v8_ flags when none are set", () => {
logFutureFlagWarnings({});

expect(logSpy).toHaveBeenCalledTimes(5);
expect(logSpy.mock.calls[0][0]).toContain("v8_middleware");
expect(logSpy.mock.calls[1][0]).toContain("v8_splitRouteModules");
expect(logSpy.mock.calls[2][0]).toContain("v8_viteEnvironmentApi");
expect(logSpy.mock.calls[3][0]).toContain("v8_passThroughRequests");
expect(logSpy.mock.calls[4][0]).toContain(
"v8_trailingSlashAwareDataRequests",
);
});

it("does not warn about flags that are already opted in (true)", () => {
logFutureFlagWarnings({
v8_middleware: true,
v8_splitRouteModules: true,
v8_viteEnvironmentApi: true,
v8_passThroughRequests: true,
v8_trailingSlashAwareDataRequests: true,
});

expect(logSpy).not.toHaveBeenCalled();
});

it("does not warn about flags that are explicitly opted out (false)", () => {
logFutureFlagWarnings({
v8_middleware: false,
v8_splitRouteModules: false,
v8_viteEnvironmentApi: false,
v8_passThroughRequests: false,
v8_trailingSlashAwareDataRequests: false,
});

expect(logSpy).not.toHaveBeenCalled();
});

it("only warns about flags that are not yet set", () => {
logFutureFlagWarnings({
v8_middleware: true,
v8_passThroughRequests: false,
});

// Only v8_splitRouteModules, v8_viteEnvironmentApi, and
// v8_trailingSlashAwareDataRequests are missing
expect(logSpy).toHaveBeenCalledTimes(3);
expect(logSpy.mock.calls[0][0]).toContain("v8_splitRouteModules");
expect(logSpy.mock.calls[1][0]).toContain("v8_viteEnvironmentApi");
expect(logSpy.mock.calls[2][0]).toContain(
"v8_trailingSlashAwareDataRequests",
);
});
});
Loading