reactjstypescriptnext.jsnext.js13web-frontend

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


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,
    },
  ],
};

Solution

  • 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.