Back

A Beginner's Guide to Middleware in React Router

A Beginner's Guide to Middleware in React Router

If you’ve ever needed to check authentication before every protected route, log requests across your application, or share data between parent and child routes in React Router, you’ve likely written the same code multiple times. React Router middleware solves this repetition problem by letting you intercept and process requests before they reach your loaders and actions.

This guide covers everything you need to implement React Router middleware in version 7.9+, from enabling the v8_middleware flag to creating your first authentication middleware with the new middleware API.

Key Takeaways

  • React Router 7 introduces middleware for handling cross-cutting concerns like authentication and logging
  • Middleware runs sequentially in a nested chain, enabling efficient data sharing between routes
  • The new context API provides type-safe data sharing without naming conflicts
  • Both server and client middleware are supported with different execution behaviors

What is React Router Middleware?

React Router 7 middleware is a feature that runs code before and after your route handlers execute. Think of it as a processing pipeline where each middleware can inspect requests, add data to the context, or even short-circuit the request with a redirect.

Unlike the parallel execution of loaders in React Router 6, middleware runs sequentially in a nested chain. Parent middleware executes before child middleware on the way down, then in reverse order on the way up after generating a response.

Parent middleware → Child middleware → Route handler → Child middleware → Parent middleware

This sequential execution enables patterns that were previously impossible, like sharing authenticated user data from a parent route to all child routes without redundant database calls.

Enabling Middleware in Your Project

To start using middleware, first ensure you’re running React Router 7.9.0 or later (or 7.3.0+ with the middleware flag enabled). Then enable the feature flag in your configuration:

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

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config;

This flag enables stable middleware support and the new context API. The context parameter in your loaders and actions now provides access to middleware-set data through a type-safe API.

Understanding the Context API

The new context API in React Router 7 provides a type-safe Map-like interface for sharing data between middleware and route handlers. Instead of attaching properties directly to a context object, you now use context.set() and context.get() with typed context keys:

import { createContext } from "react-router";

// Create a typed context key
const userContext = createContext<User>();

// In middleware
context.set(userContext, user);

// In loader/action
const user = context.get(userContext); // Type-safe User object

This approach eliminates TypeScript type assertions and prevents naming conflicts between different middleware.

Creating Your First Middleware

Let’s build an authentication middleware that protects routes and shares user data:

// app/middleware/auth.ts
import { redirect, createContext } from "react-router";
import type { Route } from "./+types/root";

export const userContext = createContext<User | null>();

export const authMiddleware: Route.Middleware = async ({ 
  request, 
  context,
  next
}) => {
  const user = await getUserFromSession(request);
  
  if (!user) {
    throw redirect("/login");
  }
  
  context.set(userContext, user);
  return next();
};

Apply this middleware to protected routes:

// app/routes/dashboard.tsx
import { authMiddleware, userContext } from "~/middleware/auth";

export  const  middleware: Route.MiddlewareFunction[] = [authMiddleware];

export async function loader({ context }: Route.LoaderArgs) {
  const user = context.get(userContext); // Guaranteed to exist
  const profile = await getProfile(user.id);
  return { profile };
}

The middleware runs before the loader, ensuring authenticated access and providing the user object without additional database queries.

Server Middleware vs Client Middleware

React Router supports both server and client middleware with slightly different behaviors:

Server middleware runs on the server and must return a Response:

export const middleware: Route.MiddlewareFunction[] = [
  async ({ request }, next) => {
    console.log(`Server: ${request.method} ${request.url}`);
    const response = await next();
    response.headers.set("X-Custom-Header", "value");
    return response; // Required
  };
];

Client middleware runs in the browser during client-side navigation:

export const middleware: Route.ClientMiddlewareFunction[] = [
  async ({ next }) => {
    const start = performance.now();
    await next();
    console.log(`Navigation took ${performance.now() - start}ms`);
  },
];

Common Middleware Patterns

Logging Middleware

export const loggingMiddleware: Route.Middleware = async ({ request, next }) => {
  const requestId = crypto.randomUUID();
  console.log(`[${requestId}] ${request.method} ${request.url}`);
  
  const response = await next();
  
  console.log(`[${requestId}] Response ${response.status}`);
  return response;
};

Composing Multiple Middleware

You can chain multiple middleware functions together:

// app/routes/admin.tsx
export const middleware = [
  loggingMiddleware,
  authMiddleware,
  adminRoleMiddleware
];

Each middleware executes in order, and any can short-circuit the chain by throwing a redirect or error response.

Conclusion

React Router middleware transforms how you handle cross-cutting concerns in your application. By enabling the v8_middleware flag and adopting the new context API, you can eliminate code duplication, share data efficiently between routes, and implement authentication, logging, and other patterns in a clean, reusable way.

Start with simple middleware like authentication or logging, then explore advanced patterns as your application grows. The sequential execution model and type-safe context API make middleware a powerful addition to your React Router toolkit.

FAQs

No, middleware is a new feature introduced behind a flag in React Router 7.3, stabilized in 7.9 via `future.v8_middleware`. Earlier versions require implementing similar functionality through wrapper components or custom loader logic.

Middleware can improve performance by reducing redundant operations. Since middleware runs sequentially and can share data through context, you avoid duplicate database queries or API calls that might occur with parallel loaders.

When middleware throws an error, React Router handles it like any other route error. The error bubbles up to the nearest error boundary, and subsequent middleware in the chain won't execute.

Middleware cannot directly modify the request object, but it can add data to the context that loaders and actions can access. For request modifications, create a new Request object and pass it to the next middleware.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay