Next Compose Middleware
next-compose-middleware is a library designed to simplify the creation of complex and declarative middleware for Next.js applications, particularly leveraging Next.js's Edge Runtime middleware. It enables developers to construct highly readable and maintainable middleware logic by composing multiple functions. The current stable version is 2.0.4, indicating active development with incremental releases. Key differentiators include its path-based middleware execution, allowing for "Nested Middleware" behavior, and the ability to compose functions with early exit mechanisms (`breakAll`, `breakOnce`) for fine-grained control over execution flow. This approach helps in organizing middleware into logical, reusable units, enhancing maintainability for applications with intricate authorization, authentication, or request transformation requirements in a single `middleware.ts` file.
Common errors
-
TypeError: next_compose_middleware_1.composeMiddleware is not a function
cause Attempting to import `composeMiddleware` using CommonJS `require()` syntax in an ESM context, or an incorrect named import.fixEnsure you are using ESM `import { composeMiddleware } from 'next-compose-middleware';` syntax, especially in Next.js middleware files which are typically ESM. Verify the symbol name is correct and not a default export. -
TypeError: Cannot read properties of undefined (reading 'cookies') or 'headers' is undefined
cause A middleware function is not returning a valid `NextResponse` object, causing subsequent middleware to receive an undefined or invalid response object.fixAlways ensure your `ComposableMiddleware` functions, especially those implementing early exit logic, explicitly return a `NextResponse` instance (e.g., `return res;`, `return breakAll(res);`, or `return breakOnce(res);`).
Warnings
- gotcha This library requires Next.js v12.2.0 or higher for stable middleware support. Older versions of Next.js do not fully support the middleware API that this library leverages, especially regarding nested middleware, which was deprecated in favor of a single root middleware file.
- gotcha Misunderstanding 'breakAll' vs 'breakOnce' in early exit scenarios can lead to unexpected middleware execution flow. 'breakAll' completely halts all subsequent middleware in the entire composition, while 'breakOnce' only stops subsequent middleware at the current nesting level, allowing parent or sibling middleware branches to continue.
- gotcha Next.js Middleware functions (and thus ComposableMiddleware) run in the Edge Runtime, which has limitations compared to Node.js environments. This means certain Node.js APIs and global objects are unavailable, and environment variables are often resolved at build time, not runtime.
Install
-
npm install next-compose-middleware -
yarn add next-compose-middleware -
pnpm add next-compose-middleware
Imports
- composeMiddleware
const composeMiddleware = require('next-compose-middleware');import { composeMiddleware } from 'next-compose-middleware'; - ComposableMiddleware
import type { ComposableMiddleware } from 'next-compose-middleware';
Quickstart
import { NextRequest, NextResponse } from 'next/server';
import { composeMiddleware, ComposableMiddleware } from 'next-compose-middleware';
// Example middleware functions
const root1: ComposableMiddleware = async (req, res) => {
console.log('Executing root1 for path:', req.nextUrl.pathname);
// Example: Modify response headers or cookies
res.headers.set('X-Root-Middleware-1', 'processed');
return res;
};
const root2: ComposableMiddleware = async (req, res) => {
console.log('Executing root2 for path:', req.nextUrl.pathname);
return res;
};
const foo: ComposableMiddleware = async (req, res) => {
console.log('Executing foo for path:', req.nextUrl.pathname);
return res;
};
const fooBar: ComposableMiddleware = async (req, res) => {
console.log('Executing fooBar for path:', req.nextUrl.pathname);
// Early exit example: if a condition is met, stop further middleware
if (req.nextUrl.searchParams.has('exit')) {
console.log('Early exiting from fooBar!');
return res; // Returning res without breakAll/breakOnce continues chain, but no further changes here.
}
return res;
};
const fooId: ComposableMiddleware = async (req, res) => {
const id = req.nextUrl.pathname.split('/')[2];
console.log('Executing fooId for path:', req.nextUrl.pathname, 'ID:', id);
return res;
};
const fooIdBaz: ComposableMiddleware = async (req, res) => {
console.log('Executing fooIdBaz for path:', req.nextUrl.pathname);
return res;
};
const fooQux: ComposableMiddleware = async (req, res) => {
console.log('Executing fooQux for path:', req.nextUrl.pathname);
return res;
};
export default async function middleware(req: NextRequest) {
console.log(`\n--- Incoming Request for ${req.nextUrl.pathname} ---`);
// Compose middleware based on paths
return composeMiddleware(req, NextResponse.next(), {
scripts: [root1, root2], // Applied to all matching paths
'/foo': {
scripts: [foo], // Applied to /foo and its children
'/bar': {
scripts: [fooBar], // Applied to /foo/bar and its children
},
'/[id]': { // Dynamic segment
scripts: [fooId], // Applied to /foo/:id and its children
'/baz': [fooIdBaz] // Applied to /foo/:id/baz
},
'/qux': [fooQux] // Applied to /foo/qux
}
});
}