How to use multiple middlewares in Next.js using the middleware.ts file?
Asked Answered
I

3

39

I'm working on a Next.js project and trying to implement multiple middleware in my application. While I've found examples of using a single middleware in Next.js using the next-connect package, I prefer to achieve this without relying on any external packages.

I have a middleware.ts file where I would like to define and use multiple middlewares. Here's the code snippet from my middleware.ts file:

import { NextResponse, NextRequest } from 'next/server';

export function middleware(request: NextRequest) {

  const userId = request.cookies.get('userId')

  if (!userId) {
    return NextResponse.redirect(new URL('/auth/login', request.nextUrl.origin).href);
  }
  return NextResponse.next();
}

export const config = {
    matcher:'/profile/:path*',
};

Her's what i tried:

import { NextResponse, NextRequest } from "next/server";

export function profileMiddleware(request: NextRequest) {
  const userId = request.cookies.get("userId");

  if (!userId) {
    return NextResponse.redirect(
      new URL("/auth/login", request.nextUrl.origin).href
    );
  }
  return NextResponse.next();
}

export function authMiddleware(request: NextRequest) {
  const userId = request.cookies.get("userId");

  if (userId) {
    return NextResponse.redirect(
      new URL("/", request.nextUrl.origin).href
    );
  }
  return NextResponse.next();
}

export default {
  middleware: [
    {
      matcher: '/profile/:path*',
      handler: profileMiddleware,
    },
    {
      matcher: '/auth/:path*',
      handler: authMiddleware,
    },
  ],
};
Immerge answered 3/7, 2023 at 9:10 Comment(4)
According to this article: How to Chain Multiple Middleware Functions in NextJS, you need to write your own middleware that will stack the other middleware, there is no builtin feature to combine middlewares.Nonalcoholic
nextjs.org/docs/messages/middleware-upgrade-guideVillain
Hey, I have two middlewares and two configs, how to combine them?Tanatanach
Check this answer out:Convolve
C
30

You can use middleware chaining for this purpose. Here's how you can achieve this:

  1. Create a folder called middlewares in your src folder.
  2. Create a file called stackHandler.ts in the middlewares folder and paste this content into it:

import {
  NextMiddleware,
  NextResponse
} from "next/server";

export function stackMiddlewares(functions: MiddlewareFactory[] = [], index = 0): NextMiddleware {
  const current = functions[index];
  if (current) {
    const next = stackMiddlewares(functions, index + 1);
    return current(next);
  }
  return () => NextResponse.next();
}
  1. Create another file called withUser.ts and paste this content into it:

import {
  NextFetchEvent,
  NextRequest,
  NextResponse
} from "next/server";

function getSearchParam(param: string, url: any) {
  return url.searchParams.get(param);
}

export const withUser: MiddlewareFactory = (next) => {
  return async(request: NextRequest, _next: NextFetchEvent) => {
    const pathname = request.nextUrl.pathname;

    if (["/profile"]?.some((path) => pathname.startsWith(path))) {
      const userId = request.cookies.get("userId");
      if (!userId) {
        const url = new URL(`/auth/login`, request.url);
        return NextResponse.redirect(url);
      }
    }
    return next(request, _next);
  };
};
  1. Finally in your middleware.ts file in the src folder paste this content:

import {
  stackMiddlewares
} from "@/middlewares/stackHandler";
import {
  withUser
} from "@/middlewares/withUser";

const middlewares = [withUser];
export default stackMiddlewares(middlewares);

Note

Define the MiddlewareFactory type as below (ignore this if not using Typescript):

import {
  NextMiddleware
} from "next/server";

export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware;

Note

You can create as many middlewares like withUser as you want by creating them and then importing them into middlewares.ts and pushing them in the middlewares array.

Convolve answered 4/10, 2023 at 13:49 Comment(6)
this approach does not support defining export const config = {...} to handle URL matcher, do you have any idea how to incorporate that into it?Boz
@Boz the ["/profile"].some((path) => pathname.startsWith(path)) is a custom matcher which you will need to have in each middleware functions if you need to run the middleware function for route/path subset. The declarative next.js middleware config/matcher would limit all the middlewares which probably is undesired for people which look for multiple middlewares. Its funny that express had good middleware 10years ago and next only supports one. Maybe its because it would be harder for the to specify execution order?Heirship
Does a solution exist for combining two middlewares via OR operation?Barny
There's no way Next.js doesn't support this out of the boxThrill
This approach also does not allow a middleware to short circuit the chain when middleware produces a response bodyThrill
Does this answer support multiple middlewares modifying the response object, setting cookies, etc?Thrill
A
1

So here is my solution for composing multiple nextjs middleware together in one /middleware.ts file:

const composeMiddlewares = (middlewares: {
  [key: string]: ((req: NextRequest) => NextResponse | Promise<NextResponse>)
}) => {
  return (req: NextRequest) => {
    const parsedMiddlewares = Object.entries(middlewares);
    const initialResponse = Promise.resolve(NextResponse.next());

    return parsedMiddlewares.reduce((prevPromise, [middlewareName, middleware]) => {
      return prevPromise.then((res) => {
        if (res?.status >= 300 && res?.status < 400) {
          console.debug(`[middleware][skip][redirect] - ${middlewareName}`);
          return res;
        } else {
          console.debug(`[middleware] - ${middlewareName}`);
          return middleware(req);
        }
      })
    }, initialResponse);
  }
};

Now, you /middleware.ts file you can do something like:

export default composeMiddlewares({
  middlewareA: genMiddlewareA({ options: 1 }),
  middlewareB: genMiddlewareB({ options: 1 }),
});
Ascidium answered 2/5 at 18:10 Comment(0)
P
-2

From the official documentation:

Declare your Middleware in the root folder and use NextRequest parsed URL to define which path the Middleware should be executed for.

I would therefore suggest preferring this approach over the one described in the most upvoted answer.

Pneumatophore answered 27/1 at 19:8 Comment(1)
I think you should add some examples as the docs itself doesn't provide any (at the time of this comment)Energumen

© 2022 - 2024 — McMap. All rights reserved.