Route Middleware

Loaders and actions are per route, which leaves the cross-cutting work — authentication, timing, opening a database handle — duplicated across all of them. Middleware is an array on the route object, running parent to child before the handlers and unwinding child to parent afterwards.

Each middleware receives ({ request, params, context }, next). await next() runs the rest of the chain and the route's own loader or action: what you do before that call happens on the way down, what you do after happens on the way back up.

Timing and auth middleware sharing one typed contextJavaScript
import { createContext, redirect, RouterContextProvider } from 'react-router';
const userContext = createContext(null);
const timing = async ({ request }, next) => { const t = performance.now(); await next();
  console.log(`${request.url} — ${(performance.now() - t) | 0}ms`); };   // after next()
async function requireUser({ context }, next) {
  if (!context.get(userContext)) throw redirect('/login');   // nothing below runs
  return next();
}
const routes = [{ path: '/', middleware: [timing], Component: Root, children: [
  { path: 'admin', middleware: [requireUser], loader: adminLoader }] }];
createBrowserRouter(routes, { getContext: () => {   // a fresh store per request
  const context = new RouterContextProvider();
  context.set(userContext, readSession());
  return context;
} });

createContext(defaultValue) from react-router is not React 7,897 's createContext. It creates a typed key for the router's own per-request store, written with context.set(key, value) and read with context.get(key) further down the chain. getContext builds a fresh store per navigation and fetcher call, keeping one user's session out of another's request on the server.

This replaces the guard-route pattern of Protected Routes: the redirect is thrown before any loader runs, so protected data is never fetched. In framework mode the same arrays are route module exports — middleware runs on the server and returns the Response from next(); clientMiddleware runs in the browser.