How toUse middleware in React Router

If you're excited to use middleware in React Router, this guide will show you how to enable and implement them starting from version 7.3.0. You'll learn how to intercept requests and enhance loaders and actions on both the server and client.

Enable Middleware in React Router

First, upgrade every React Router package to v7.3.0. The simplest way is to change in your package.json any @react-router/* package and the react-router package itself to that version.

Then go to your React Router config file and add this code.

react-router.config.ts
import type { Config } from "@react-router/dev/config"; import "react-router"; declare module "react-router" { interface Future { unstable_middleware: true; // 👈 Enable middleware types } } export default { // ...Other options future: { unstable_middleware: true, // 👈 Enable middleware // ...Other future or unstable flags }, } satisfies Config;

The first thing is to enable unstable_middleware in the exported config object. This will enable middleware at runtime. The second thing is to add the unstable_middleware property to the Future interface in the react-router module. This will enable middleware types in your code.

Breaking Change for Context

The types are important because middleware comes with a breaking change. Currently, a loader or action receives three arguments:

  1. request
  2. params
  3. context

The request is a Request instance, params is an object with the route params like :userId, and context is an object with the interface AppLoadContext.

This AppLoadContext interface contains the values the HTTP server (e.g., Express, Hono, Cloudflare Workers, etc.) passes using getLoadContext to the React Router request handler.

When you enable middleware in your React Router application, this AppLoadContext is replaced with a new unstable_RouterContextProvider object. This is essentially a Map where middleware can store values that loaders or actions can read.

Create a Middleware

Now that you have middleware enabled, you can create a custom middleware like this.

app/middleware/session.ts
import type { unstable_MiddlewareFunction, unstable_RouterContextProvider, } from "react-router"; import { unstable_createContext } from "react-router"; const sessionContext = unstable_createContext<Session<Data, FlashData>>(); export const sessionMiddleware: unstable_MiddlewareFunction<Response> = async ( { request, context }, next ) => { let session = await sessionStorage.getSession(request.headers.get("Cookie")); context.set(sessionContext, session); let response = await next(); response.headers.append( "Set-Cookie", await sessionStorage.commitSession(session) ); return response; }; export function getSession(context: unstable_RouterContextProvider) { return context.get(sessionContext); }

This is a simplified implementation of a session middleware that uses React Router session storage to access the session and store it in the unstable_RouterContextProvider object.

Then we export a simple function to get the session from that context so we don't have to export the sessionContext object.

Finally, you can add the middleware to a route like this:

app/root.tsx
import { sessionMiddleware } from "./middleware/session"; export const unstable_middleware = [sessionMiddleware];

And access the session in a loader or action like this:

app/routes/profile.tsx
export async function loader({ context }: Route.LoaderArgs) { let user = getSession(context).get("user"); if (!user) return redirect("/login"); return { user }; }

And that's it! You can now use middleware in your React Router application.

Typed Params in Middleware

So far, we have used only request and context in the session middleware. However, it also receives the params, like a loader or action. This is not typed because the middleware we created is generic.

If we want to create a middleware tied to a specific route, we can use the Route.unstable_MiddlewareFunction type, which will have the params typed.

app/root.tsx
import type { Route } from "./+types/root"; export const unstable_middleware: Route.unstable_MiddlewareFunction[] = [ async ({ request, params, context }, next) => { // params is typed here return next(); }, ];

Client-Side Middleware

React Router middleware is not only for server loaders and actions—you can also use them for client loaders and actions.

app/routes/profile.tsx
export const unstable_clientMiddleware = [someClientMiddleware];

The unstable_clientMiddleware works like unstable_middleware, but for client loaders and actions. In this case, the type won't be Route.unstable_MiddlewareFunction but Route.unstable_ClientMiddlewareFunction.

The generic type is the same, unstable_MiddlewareFunction, but instead of unstable_MiddlewareFunction<Response>, you can just use unstable_MiddlewareFunction, since the next function in a client middleware doesn't return anything.