Chaining middlewares is a common challenge when building Next.js applications. In this article, I’ll show you how to set up a clean, maintainable solution that avoids common pitfalls.
Base Setup
Middleware chaining isn’t a new problem, and fortunately, there are existing solutions we can build upon. This tutorial provides an excellent foundation. Follow its steps to implement a near-ideal setup as your starting point.
Identifying the Problem
After implementing the tutorial’s solution, you’ll notice that while it works, there are maintainability issues:
- Response Initialization in the First Middleware
The first middleware initializes theNextResponse
object usingNextResponse.next()
. While functional, this approach violates separation of concerns. This task should ideally be handled by the chain function itself. - Fragile Middleware Order
If you later rearrange the order of middlewares or introduce a new first middleware, you’ll need to modify both the original first middleware and the new one. This introduces unnecessary complexity and potential for bugs.
A Clean and Simple Solution
To address these issues, you can refactor your middleware chain with two simple steps:
1. Create an Initialization Middleware
The response object should be initialized in a dedicated middleware, whose sole responsibility is to create the NextResponse
object. This middleware can be added to your chain.ts
file as follows:
function initializationMiddleware(middleware: CustomMiddleware) {
return async (request: NextRequest, event: NextFetchEvent) => {
const newResponse = NextResponse.next();
return middleware(request, event, newResponse);
};
}
2. Add the Initialization Middleware at the Right Moment
Next, ensure that this initialization middleware is always called before any other middleware. You can achieve this by modifying the functions
array at the beginning of the chainMiddlewares
function, like so:
export function chainMiddlewares(
functions: MiddlewareFactory[],
index = 0
): CustomMiddleware {
if (index === 0) {
functions = [initializationMiddleware, ...functions];
}
...
}
By doing this, the initialization middleware is automatically added at the start of the chain, simplifying the process.
Benefits of This Approach
- Separation of Concerns
The initialization middleware isolates the response object creation, leaving the remaining middlewares free to focus on their specific logic. - Improved Maintainability
With this setup, you can rearrange or modify middleware order without worrying about updating response initialization code. - Reduced Code Changes
Adding new middlewares becomes straightforward, as the response initialization is handled consistently and independently.
Conclusion
By introducing an initialization middleware and ensuring it is always called first, you can significantly improve the maintainability of your Next.js application. This simple yet powerful change allows you to focus on building features without being bogged down by middleware ordering issues.