Skip to content

Commit 426e5ca

Browse files
authored
Stabilize Middleware and Context APIs (#14215)
1 parent 796cdff commit 426e5ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+885
-917
lines changed

.changeset/shy-lemons-boil.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@react-router/cloudflare": minor
3+
"@react-router/architect": minor
4+
"@react-router/express": minor
5+
"@react-router/node": minor
6+
"@react-router/dev": minor
7+
"react-router": minor
8+
---
9+
10+
Stabilize middleware and context APIs

docs/community/api-development-strategy.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: API Development Strategy
66

77
React Router is foundational to your application. We want to make sure that upgrading to new major versions is as smooth as possible while still allowing us to adjust and enhance the behavior and API as the React ecosystem advances.
88

9-
Our strategy and motivations are discussed in more detail in our [Future Flags][future-flags-blog-post] blog post.
9+
Our strategy and motivations are discussed in more detail in our [Future Flags][future-flags-blog-post] blog post and our [Open Governance Model][governance].
1010

1111
## Future Flags
1212

@@ -36,10 +36,9 @@ To learn about current unstable flags, keep an eye on the [CHANGELOG](../start/c
3636

3737
### Example New Feature Flow
3838

39-
The decision flow for a new feature looks something like this (note this diagram is in relation to Remix v1/v2 but applies to React Router v6/v7 as well):
39+
The decision flow for a new feature looks something like this:
4040

41-
![Flowchart of the decision process for how to introduce a new feature][feature-flowchart]
41+
<img width="400" src="https://reactrouter.com/_docs/feature-flowchart.png" alt="Flowchart of the decision process for how to introduce a new feature" />
4242

4343
[future-flags-blog-post]: https://remix.run/blog/future-flags
44-
[feature-flowchart]: https://remix.run/docs-images/feature-flowchart.png
45-
[picking-a-router]: ../routers/picking-a-router
44+
[governance]: https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process

docs/how-to/middleware.md

Lines changed: 102 additions & 104 deletions
Large diffs are not rendered by default.

docs/start/data/route-object.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,20 @@ function MyRouteComponent() {
5454
}
5555
```
5656

57-
## `unstable_middleware`
57+
## `middleware`
5858

5959
Route [middleware][middleware] runs sequentially before and after navigations. This gives you a singular place to do things like logging and authentication. The `next` function continues down the chain, and on the leaf route the `next` function executes the loaders/actions for the navigation.
6060

6161
```tsx
6262
createBrowserRouter([
6363
{
6464
path: "/",
65-
unstable_middleware: [loggingMiddleware],
65+
middleware: [loggingMiddleware],
6666
loader: rootLoader,
6767
Component: Root,
6868
children: [{
6969
path: 'auth',
70-
unstable_middleware: [authMiddleware],
70+
middleware: [authMiddleware],
7171
loader: authLoader,
7272
Component: Auth,
7373
children: [...]
@@ -84,7 +84,7 @@ async function loggingMiddleware({ request }, next) {
8484
console.log(`Navigation completed in ${duration}ms`);
8585
}
8686

87-
const userContext = unstable_createContext<User>();
87+
const userContext = createContext<User>();
8888

8989
async function authMiddleware ({ context }) {
9090
const userId = getUserId();

docs/start/framework/route-module.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function MyRouteComponent({
7878
}
7979
```
8080

81-
## `unstable_middleware`
81+
## `middleware`
8282

8383
Route [middleware][middleware] runs sequentially on the server before and after document and
8484
data requests. This gives you a singular place to do things like logging,
@@ -103,7 +103,7 @@ async function loggingMiddleware(
103103
return response;
104104
}
105105

106-
export const unstable_middleware = [loggingMiddleware];
106+
export const middleware = [loggingMiddleware];
107107
```
108108

109109
Here's an example middleware to check for logged in users and set the user in
@@ -125,19 +125,19 @@ async function authMiddleware ({
125125
context.set(userContext, user);
126126
};
127127

128-
export const unstable_middleware = [authMiddleware];
128+
export const middleware = [authMiddleware];
129129
```
130130

131131
<docs-warning>Please make sure you understand [when middleware runs][when-middleware-runs] to make sure your application will behave the way you intend when adding middleware to your routes.</docs-warning>
132132

133133
See also:
134134

135-
- [`unstable_middleware` params][middleware-params]
135+
- [`middleware` params][middleware-params]
136136
- [Middleware][middleware]
137137

138-
## `unstable_clientMiddleware`
138+
## `clientMiddleware`
139139

140-
This is the client-side equivalent of `unstable_middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server.
140+
This is the client-side equivalent of `middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server.
141141

142142
Here's an example middleware to log requests on the client:
143143

@@ -158,9 +158,7 @@ async function loggingMiddleware(
158158
// ✅ No need to return anything
159159
}
160160

161-
export const unstable_clientMiddleware = [
162-
loggingMiddleware,
163-
];
161+
export const clientMiddleware = [loggingMiddleware];
164162
```
165163

166164
See also:
@@ -502,7 +500,7 @@ export function shouldRevalidate(
502500

503501
Next: [Rendering Strategies](./rendering)
504502

505-
[middleware-params]: https://api.reactrouter.com/v7/types/react_router.unstable_MiddlewareFunction.html
503+
[middleware-params]: https://api.reactrouter.com/v7/types/react_router.MiddlewareFunction.html
506504
[middleware]: ../../how-to/middleware
507505
[when-middleware-runs]: ../../how-to/middleware#when-middleware-runs
508506
[loader-params]: https://api.reactrouter.com/v7/interfaces/react_router.LoaderFunctionArgs

docs/upgrading/future.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,43 @@ This guide walks you through the process of adopting future flags in your React
99

1010
We highly recommend you make a commit after each step and ship it instead of doing everything all at once. Most flags can be adopted in any order, with exceptions noted below.
1111

12-
<docs-warning>**There are no current future flags in React Router v7**</docs-warning>
12+
## Update to latest v7.x
13+
14+
First update to the latest minor version of v7.x to have the latest future flags. You may see a number of deprecation warnings as you upgrade, which we'll cover below.
15+
16+
👉 Update to latest v7
17+
18+
```sh
19+
npm install react-router@7 @react-router/{dev,node,etc.}@7
20+
```
21+
22+
## `future.v8_middleware`
23+
24+
[MODES: framework]
25+
26+
<br/>
27+
<br/>
28+
29+
**Background**
30+
31+
Middleware allows you to run code before and after the [`Response`][Response] generation for the matched path. This enables common patterns like authentication, logging, error handling, and data preprocessing in a reusable way. Please see the [docs](../how-to/middleware) for more information.
32+
33+
👉 **Enable the Flag**
34+
35+
```ts filename=react-router.config.ts
36+
import type { Config } from "@react-router/dev/config";
37+
38+
export default {
39+
future: {
40+
v8_middleware: true,
41+
},
42+
} satisfies Config;
43+
```
44+
45+
**Update your Code**
46+
47+
If you're using `react-router-serve`, then you should not need to make any updates to your code.
48+
49+
You should only need to update your code if you are using the `context` parameter in `loader` and `action` functions. This only applies if you have a custom server with a `getLoadContext` function. Please see the docs on the middleware [`getLoadContext` changes](../how-to/middleware#changes-to-getloadcontextapploadcontext) and the instructions to [migrate to the new API](../how-to/middleware#migration-from-apploadcontext).
50+
51+
[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response

integration/browser-entry-test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,20 @@ test("allows users to pass a client side context to HydratedRouter", async ({
8787
let fixture = await createFixture({
8888
files: {
8989
"app/entry.client.tsx": js`
90-
import { unstable_createContext, unstable_RouterContextProvider } from "react-router";
90+
import { createContext, RouterContextProvider } from "react-router";
9191
import { HydratedRouter } from "react-router/dom";
9292
import { startTransition, StrictMode } from "react";
9393
import { hydrateRoot } from "react-dom/client";
9494
95-
export const myContext = new unstable_createContext('foo');
95+
export const myContext = new createContext('foo');
9696
9797
startTransition(() => {
9898
hydrateRoot(
9999
document,
100100
<StrictMode>
101101
<HydratedRouter
102-
unstable_getContext={() => {
103-
return new unstable_RouterContextProvider([
102+
getContext={() => {
103+
return new RouterContextProvider([
104104
[myContext, 'bar']
105105
]);
106106
}}

integration/helpers/rsc-parcel/src/browser.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
setServerCallback,
1616
// @ts-expect-error - no types for this yet
1717
} from "react-server-dom-parcel/client";
18-
import { unstable_getContext } from "./config/unstable-get-context";
18+
import { getContext } from "./config/get-context";
1919

2020
// Create and set the callServer function to support post-hydration server actions.
2121
setServerCallback(
@@ -39,7 +39,7 @@ createFromReadableStream(getRSCStream()).then((payload: RSCPayload) => {
3939
<RSCHydratedRouter
4040
payload={payload}
4141
createFromReadableStream={createFromReadableStream}
42-
unstable_getContext={unstable_getContext}
42+
getContext={getContext}
4343
/>
4444
</StrictMode>,
4545
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// THIS FILE IS DESIGNED TO BE OVERRIDDEN IN TESTS IF NEEDED
2-
export const unstable_getContext = undefined;
2+
export const getContext = undefined;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// THIS FILE IS DESIGNED TO BE OVERRIDDEN IN TESTS IF NEEDED
2-
export const unstable_getContext = undefined;
2+
export const getContext = undefined;

0 commit comments

Comments
 (0)