You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .changeset/middleware.md
+65-22Lines changed: 65 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,9 +2,30 @@
2
2
"react-router": patch
3
3
---
4
4
5
-
Support `middleware` on routes (unstable)
5
+
Support middleware on routes (unstable)
6
6
7
-
Routes can now define an array of middleware functions that will run sequentially before route handlers run. These functions accept the same arguments as `loader`/`action` plus an additional `next` function to run the remaining data pipeline. This allows middlewares to perform logic before and after handlers execute.
7
+
Middleware is implemented behind a `future.unstable_middleware` flag. To enable, you must enable the flag and the types in your `react-router-config.ts` file:
⚠️ Enabling middleware contains a breaking change to the `context` parameter passed to your `loader`/`action` functions - see below for more information.
27
+
28
+
Once enabled, routes can define an array of middleware functions that will run sequentially before route handlers run. These functions accept the same parameters as `loader`/`action` plus an additional `next` parameter to run the remaining data pipeline. This allows middlewares to perform logic before and after handlers execute.
8
29
9
30
```tsx
10
31
// Framework mode
@@ -25,33 +46,23 @@ const routes = [
25
46
Here's a simple example of a client-side logging middleware that can be placed on the root route:
26
47
27
48
```tsx
28
-
asyncfunction clientLogger({
29
-
request,
30
-
params,
31
-
context,
32
-
next,
33
-
}:Route.ClientMiddlewareArgs) {
49
+
asyncfunction clientLogger({ request }, next) {
34
50
let start =performance.now();
35
51
36
52
// Run the remaining middlewares and all route loaders
37
53
awaitnext();
38
54
39
55
let duration =performance.now() -start;
40
56
console.log(`Navigated to ${request.url} (${duration}ms)`);
41
-
}
57
+
}satisfiesRoute.ClientMiddlewareFunction;
42
58
```
43
59
44
60
Note that in the above example, the `next`/`middleware` functions don't return anything. This is by design as on the client there is no "response" to send over the network like there would be for middlewares running on the server. The data is all handled behind the scenes by the stateful `router`.
45
61
46
62
For a server-side middleware, the `next` function will return the HTTP `Response` that React Router will be sending across the wire, thus giving you a chance to make changes as needed. You may throw a new response to short circuit and respond immediately, or you may return a new or altered response to override the default returned by `next()`.
You can throw a `redirect` from a middleware to short circuit any remaining processing:
69
80
70
81
```tsx
71
-
function serverAuth({ request, params, context, next }:Route.MiddlewareArgs) {
72
-
let user =context.session.get("user");
82
+
import { sessionContext } from"../context";
83
+
function serverAuth({ request, params, context }, next) {
84
+
let session =context.get(sessionContext);
85
+
let user =session.get("user");
73
86
if (!user) {
74
-
context.session.set("returnTo", request.url);
87
+
session.set("returnTo", request.url);
75
88
throwredirect("/login", 302);
76
89
}
77
-
context.user=user;
78
-
// No need to call next() if you don't need to do any post processing
79
-
}
90
+
} satisfiesRoute.MiddlewareFunction;
80
91
```
81
92
82
93
_Note that in cases like this where you don't need to do any post-processing you don't need to call the `next` function or return a `Response`._
@@ -100,3 +111,35 @@ async function redirects({ request, next }: Route.MiddlewareArgs) {
100
111
returnres;
101
112
}
102
113
```
114
+
115
+
**`context` parameter**
116
+
117
+
When middleware is enabled, your application wil use a different type of `context` parameter in your loaders and actions to provide better type safety. `context` will now be an instance of `ContextProvider` that you use with type-safe contexts (similar to `React.createContext`):
Your application `loader` and `action` functionsopn the client will now receive a `context` parameter. This is an instance of `ContextProvider` that you use with type-safe contexts (similar to `React.createContext`) and is most useful with the corresponding `middleware`/`clientMiddleware` API's:
Similar to server-side requests, a fresh `context` will be created per navigation (or `fetcher` call). If you have initial data you'd like to populate in the context for every request, you can provide an `unstable_getContext` function at the root oy your app:
Copy file name to clipboardExpand all lines: decisions/0014-context-middleware.md
+15-33Lines changed: 15 additions & 33 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,64 +22,46 @@ We've done a lot of work since then to get us to a place where we could ship a m
22
22
23
23
## Decision
24
24
25
-
### Lean on existing `context`parameter for initial implementation
25
+
### Leverage a new type-safe `context`API
26
26
27
-
During our experiments we realized that we could offload type-safe context to an external package. This would result in a simpler implementation within React Router and avoid the need to try to patch on type-safety to our existing `context`API which was designed as a quick escape hatch to cross the bridge from your server (i.e., `express``req`/`res`) to the Remix handlers.
27
+
We originally considered leaning on our existing `context` value we pass to server-side `loader` and `action` functions, and implementing a similar client-side equivalent for parity. However, the type story around `AppLoadContext` isn't great, so that would mean implementing a new API client side that we knew we weren't happy with from day one. And then likely replacing it with a better version fairly soon after.
28
28
29
-
Therefore, our recommendation for proper type-safe context for usage within middlewares and route handlers will be the [`@ryanflorence/async-provider`][async-provider] package.
30
-
31
-
If you don't want to adopt an extra package, or don't care about 100% type-safety and are happy with the module augmentation approach used by `AppLoadContext`, then you can use the existing `context` parameter passed to loaders and actions.
29
+
Instead, when the flag is enabled, we'll be removing `AppLoadContext` in favor of a type-safe `context` API that is similar in usage to the `React.createContext` API:
32
30
33
31
```ts
34
-
declaremodule"react-router" {
35
-
interfaceAppLoadContext {
36
-
user:User|null;
37
-
}
38
-
}
32
+
let userContext =createContext<User>();
39
33
40
-
// root.tsx
41
-
function userMiddleware({ request, context }:Route.MiddlewareArgs) {
In order to support the same API on the client, we will need to add support for a client-side `context` value which is already a [long requested feature][client-context]. We can do so just like the server and let users use module augmentation to define their context shape. This will default to an empty object like the server, and can be passed to `createBrowserRouter` in library mode or `<HydratedRouter>` in framework mode to provide up-front singleton values.
50
+
In order to support the same API on the client, we will also add support for a client-side `context` value which is already a [long requested feature][client-context]. If you need to provide initial values (similar to `getLoadContext` on the server), you can do so with a new `getContext` method which returns a `Map<RouterContext, unknown>`:
// Singleton fields that don't change and are always available
67
-
let globalContext:RouterContext= { logger: getLogger() };
68
-
69
57
// library mode
70
-
let router =createBrowserRouter(routes, { context: globalContext });
58
+
let router =createBrowserRouter(routes, { getContext })
71
59
72
60
// framework mode
73
-
return <HydratedRoutercontext={globalContext}>
74
-
```
75
-
76
-
`context` on the server has the advantage of auto-cleanup since it's scoped to a request and thus automatically cleaned up after the request completes. In order to mimic this behavior on the client, we'll create a new object per navigation/fetch and spread in the properties from the global singleton context. Therefore, the context object you receive in your handlers will be:
77
-
78
-
```ts
79
-
let scopedContext = { ...globalContext };
61
+
return <HydratedRoutergetContext={getContext}>
80
62
```
81
63
82
-
This way, singleton values will always be there, but any new fields added to that object in middleware will only exist for that specific navigation or fetcher call and it will disappear once the request is complete.
64
+
`context` on the server has the advantage of auto-cleanup since it's scoped to a request and thus automatically cleaned up after the request completes. In order to mimic this behavior on the client, we'll create a new object per navigation/fetch.
0 commit comments