Middleware in general
Middleware is a pattern that acts a layer between different parts of an application, processing requests and responses as they flow through the system.
Middleware typically has access to the request and response objects as long as the next function.
Each middleware can execute code, modify request/response objects, end the request-response cycle or call the next middleware.
Common use cases for middleware: Authentication, logging, error handling, CORS, validation and more.
Key advantage is you can use middleware, remove, or reorder them without modifying the core logic.
React router middleware
Middleware runs in a nested chain, executing from parent to child routes on the way "down" to your route handlers, then from your child routes back to parent routes on the way "up" after a response is generated.
It's very important to understand when your middleware will run to make sure your application is behaving as you intended.
The steps needed to create a middleware in Framework mode:
1. Create a ContextMiddleware uses a context provider instance to provide data down the middleware
chain. You can create type-safe context objects using createContext
getLoadContext function
If you're using a custom server and a getLoadContext function, you will need
to update your implementation to return an instance of RouterContextProvider,
instead of a JavaScript object.
Server side middleware
Server middleware runs in the server in framework mode for HTML Document requests
and .data requests for subsequent navigations and fetcher calls.
In a hydrated Framework Mode app, server middleware is designed in such that it prioritizes SPA behaviour and does not create a new network activity by default.
Client side middleware
Client middleware runs in the browser in framework mode and data mode for client-side navigations and fetcher calls. Client middleware differs from server middleware because there's no HTTP requests, so it doesn't have a response bubbling up.
Client middleware is simpler because since we are already on the client and are
always making a "request" to the router when navigating. Client middleware will
run on every client navigation, regardless of whether there are loaders to run.
Error handling in middleware
Middleware error handling follows the same principles as regular route loaders and actions. When an error occurs in middleware, it bubbles up the chain and can be caught by error boundaries.
Errors in middleware follow the normal React Router error flow they'll be caught by the nearest ErrorBoundary component, allowing you to display user-friendly error messages.
Middleware ordering and dependencies
The order of middleware in your export array matters because they execute sequentially. First middleware runs first, and each can modify the context or request before the next one.
Dependencies between middleware should be handled carefully. If one middleware depends on context set by another, ensure they're ordered correctly and handle missing context gracefully.
Performance considerations
Middleware adds execution overhead to every request, so keep it lightweight. Avoid expensive operations like heavy database queries or complex computations in middleware.
Good practices for performance:
Use caching for repeated operations, minimize I/O operations in middleware chains. Consider conditional middleware execution based on routes, profile middleware performance in production
Testing middleware
Middleware functions can be unit tested like any other async function. Mock the request, context, and next function to verify behavior.
Integration testing can verify the entire middleware chain works together with actual route loaders and components.
Conclusion
React Router middleware provides a powerful, composable way to handle cross-cutting concerns in your applications. By understanding the execution model, proper error handling, and performance implications, you can build robust middleware chains that enhance your application without compromising maintainability.