UNPKG

nextjs-multi-middleware

Version:

A utility to easily chain and compose multiple middleware functions in Next.js.

161 lines (121 loc) 5.55 kB
# nextjs-multi-middleware Brings Express-style middleware chaining to Next.js. This utility lets you chain multiple middleware functions, using `path-to-regexp` for route matching. It's a simple way to organize your middleware logic into clean, reusable functions. If a middleware in the chain returns a `NextResponse`, the chain halts and the response is sent. Otherwise, the chain continues to the next middleware. ## Why `nextjs-multi-middleware`? In complex Next.js applications, a single middleware file can become bloated with conditional logic for different routes. `nextjs-multi-middleware` solves this by allowing you to split your logic into independent, composable middleware functions, each with its own route matching. This leads to cleaner, more maintainable code. It is also lightweight, has zero dependencies, and is fully compatible with the Vercel Edge Runtime. ## Installation ```bash npm install nextjs-multi-middleware ``` or ```bash yarn add nextjs-multi-middleware ``` ## Usage Example Here’s how to use `nextjs-multi-middleware` in your `middleware.ts` file. ```typescript // middleware.ts import { NextResponse, NextRequest, NextFetchEvent } from 'next/server'; import { chain, Middleware, ErrorMiddleware, NextMiddlewareMulti, StandardNextMiddleware } from 'nextjs-multi-middleware'; // 1. Define your middlewares // A simple logger const loggingMiddleware = (req: NextRequest) => { console.log(`Request to: ${req.nextUrl.pathname}`); }; // Auth middleware with a specific matcher const authMiddleware = (req: NextRequest) => { const isAuthenticated = req.cookies.get('session_token')?.value; if (!isAuthenticated) { return NextResponse.redirect(new URL('/login', req.url)); } return NextResponse.next(); }; // A middleware that might throw an error const riskyMiddleware = async (req: NextRequest) => { if (req.nextUrl.pathname.startsWith('/api/risky')) { throw new Error('This is a simulated error!'); } }; // An error handler (must have 4 arguments) const errorHandler: ErrorMiddleware = async (error, req, event, next) => { console.error('Caught error in middleware chain:', error.message); return new NextResponse( JSON.stringify({ success: false, message: 'An internal error occurred.' }), { status: 500, headers: { 'content-type': 'application/json' } } ); }; // 2. Configure the chain const allMiddlewares: Middleware[] = [ loggingMiddleware, // Runs on all paths { handler: authMiddleware, matcher: '/admin/:path*', // Uses path-to-regexp }, riskyMiddleware, // Error handlers should be last errorHandler, ]; // 3. Export the chain (debug mode is optional) export default chain(allMiddlewares, { debug: true }); // 4. Define the global matcher for Next.js export const config = { matcher: ['/admin/:path*', '/api/:path*', '/((?!_next/static|favicon.ico).*)'], }; ``` ## Features - **Execution Order**: Middlewares run in the order they are defined. If a middleware returns a `NextResponse`, the chain stops. - **Flexible API**: Pass an array of raw functions or `MiddlewareConfig` objects (`{ handler, matcher }`) to the `chain` function. - **Route Matching**: Use `path-to-regexp` strings (`/users/:id`) or a predicate function `(req: NextRequest) => boolean` for fine-grained control. If no `matcher` is provided, the middleware runs on all requests. - **Error Handling**: Create middleware with four arguments `(error, req, event, next)` to catch errors from preceding middlewares. - **Debug Mode**: Pass `{ debug: true }` to `chain` to log the execution flow and matcher results. ```typescript export default chain(allMiddlewares, { debug: true }); ``` ## Recipes Here are a few recipes for common use cases. ### Recipe: Passing Data Between Middlewares Since middleware functions don't share a direct `context` object, the recommended way to pass data is by modifying request headers. The `next()` callback in custom middlewares can accept a new `NextRequest` object to pass it down the chain. ```typescript // 1. First middleware adds data to headers const addUserToHeaders: NextMiddlewareMulti = async (req, event, next) => { // In a real app, you'd get this from a session or token const user = { id: '123', role: 'admin' }; // Clone the request headers and set a new header const headers = new Headers(req.headers); headers.set('x-user-info', JSON.stringify(user)); // Create a new request with the new headers const newReq = new NextRequest(req.url, { headers, // It's important to pass the original request object to keep its properties request: req, }); // Pass the modified request to the next middleware return await next(newReq, event); }; // 2. A later middleware or API route can read it const anotherMiddleware = (req: NextRequest) => { const userHeader = req.headers.get('x-user-info'); if (userHeader) { const user = JSON.parse(userHeader); console.log('User found in headers:', user); // { id: '123', role: 'admin' } } }; ``` ### Recipe: A/B Testing with Cookies Middleware is perfect for A/B testing by rewriting paths based on cookies. ```typescript const abTestMiddleware: StandardNextMiddleware = (req) => { const bucket = req.cookies.get('ab-bucket')?.value; const url = req.nextUrl; if (bucket === 'new-design' && url.pathname === '/products') { // Rewrite to the new page variant url.pathname = '/products-new'; return NextResponse.rewrite(url); } // Otherwise, continue to the default page }; ``` ## License MIT