Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 38 additions & 18 deletions docs/how-to/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ title: Middleware
<br/>
<br/>

<docs-info>In Framework Mode, you must opt-into middleware via the [`future.v8_middleware`][future-flags] flag because it contains a minor [breaking change][getloadcontext] to the `getLoadContext` function and the loader/action `context` parameter.</docs-info>

Middleware allows you to run code before and after the [`Response`][Response] generation for the matched path. This enables [common patterns][common-patterns] like authentication, logging, error handling, and data preprocessing in a reusable way.

Middleware runs in a nested chain, executing from parent routes to child routes on the way "down" to your route handlers, then from child routes back to parent routes on the way "up" after a [`Response`][Response] is generated.
Expand Down Expand Up @@ -132,12 +130,24 @@ function getLoadContext(req, res) {

## Quick Start (Data Mode)

<docs-info>Note there is no future flag in Data Mode because you can opt-into middleware by adding it to your routes, no breaking changes exist that require a future flag.</docs-info>
### 1. TypeScript: augment `Future` for loader/action `context`

### 1. Create a context
In order to properly type your `context` param in your `loader`/`action`/`middleware` functions, you will need a small module augmentation to override the default context type of `any`:

Middleware uses a `context` provider instance to provide data down the middleware chain.
You can create type-safe context objects using [`createContext`][createContext]:
```ts
// src/react-router.d.ts
declare module "react-router" {
interface Future {
v8_middleware: true;
}
}
```

Without this, `context` stays loosely typed even when middleware is enabled at runtime.

### 2. Create a context

Middleware uses a `context` provider to pass data through the middleware chain into loaders and actions. Create typed context with [`createContext`][createContext]:

```ts
import { createContext } from "react-router";
Expand All @@ -146,10 +156,16 @@ import type { User } from "~/types";
export const userContext = createContext<User | null>(null);
```

### 2. Add middleware to your routes
### 3. Add `middleware` to route objects

Attach `middleware` arrays to your route objects:

```tsx
import { redirect } from "react-router";
import {
redirect,
useLoaderData,
type LoaderFunctionArgs,
} from "react-router";
import { userContext } from "~/context";

const routes = [
Expand All @@ -159,10 +175,10 @@ const routes = [
Component: Root,
children: [
{
path: "profile",
path: "dashboard",
middleware: [authMiddleware], // 👈
loader: profileLoader,
Component: Profile,
loader: dashboardLoader,
Component: Dashboard,
},
{
path: "login",
Expand All @@ -187,15 +203,15 @@ async function authMiddleware({ context }) {
context.set(userContext, user);
}

export async function profileLoader({
export async function dashboardLoader({
context,
}: Route.LoaderArgs) {
}: LoaderFunctionArgs) {
const user = context.get(userContext);
const profile = await getProfile(user);
return { profile };
}

export default function Profile() {
export default function Dashboard() {
let loaderData = useLoaderData();
return (
<div>
Expand All @@ -206,9 +222,9 @@ export default function Profile() {
}
```

### 3. Add a `getContext` function (optional)
### 4. Add a `getContext` function (optional)

If you wish to include a base context on all navigations/fetches, you can add an [`getContext`][getContext] function to your router. This will be called to populate a fresh context on every navigation/fetch.
To seed every navigation or fetcher call with shared values, pass [`getContext`][getContext] when creating the router:

```tsx
let sessionContext = createContext();
Expand All @@ -222,7 +238,7 @@ const router = createBrowserRouter(routes, {
});
```

<docs-info>This API exists to mirror the `getLoadContext` API on the server in Framework Mode, which exists as a way to hand off values from your HTTP server to the React Router handler. This [`getContext`][getContext] API can be used to hand off global values from the [`window`][window]/[`document`][document] to React Router, but because they're all running in the same context (the browser), you can achieve effectively the same behavior with root route middleware. Therefore, you may not need this API the same way you would on the server - but it's provided for consistency.</docs-warning>
<docs-info>This mirrors Framework mode’s server-side [`getLoadContext`][getloadcontext]. In the browser, root `middleware` can often do the same job, but `getContext` is available when you want to seed every request up front.</docs-info>

## Core Concepts

Expand Down Expand Up @@ -250,7 +266,7 @@ Client middleware runs in the browser in framework and data mode for client-side
async function clientMiddleware({ request }, next) {
console.log(request.method, request.url);
await next();
console.log(response.status, request.method, request.url);
console.log(`Finished ${request.method} ${request.url}`);
}

// Framework mode
Expand Down Expand Up @@ -720,6 +736,10 @@ export async function loader({
[common-patterns]: #common-patterns
[server-client]: #server-vs-client-middleware
[rr-config]: ../api/framework-conventions/react-router.config.ts
[create-browser-router]: ../api/data-routers/createBrowserRouter
[create-hash-router]: ../api/data-routers/createHashRouter
[create-memory-router]: ../api/data-routers/createMemoryRouter
[create-static-handler]: ../api/data-routers/createStaticHandler
[framework-action]: ../start/framework/route-module#action
[framework-loader]: ../start/framework/route-module#loader
[getloadcontext]: #changes-to-getloadcontextapploadcontext
Expand Down
21 changes: 18 additions & 3 deletions docs/upgrading/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ npm install react-router@7 @react-router/{dev,node,etc.}@7

## `future.v8_middleware`

[MODES: framework]
[MODES: framework, data]

<br/>
<br/>
Expand All @@ -32,6 +32,8 @@ Middleware allows you to run code before and after the [`Response`][Response] ge

👉 **Enable the Flag**

In Framework mode:

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

Expand All @@ -42,11 +44,24 @@ export default {
} satisfies Config;
```

In Data mode:

```ts
import { createBrowserRouter } from "react-router/dom";

const router = createBrowserRouter(routes, {
future: {
v8_middleware: true,
},
});
```

**Update your Code**

If you're using `react-router-serve`, then you should not need to make any updates to your code.
If you're using the `context` parameter in `loader` and `action` functions, you may need to update your code:

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).
- In Framework mode, if you're using `react-router-serve`, you should not need to make any updates. Otherwise, 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).
- In Data mode, add the `Future` module augmentation described in the [middleware docs](../how-to/middleware#2-typescript-augment-future-for-loaderaction-context) so `context` is typed correctly.

## `future.v8_splitRouteModules`

Expand Down