54

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

6 Answers 6

44

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.

Sign up to request clarification or add additional context in comments.

7 Comments

this approach does not support defining export const config = {...} to handle URL matcher, do you have any idea how to incorporate that into it?
@crollywood 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?
Does a solution exist for combining two middlewares via OR operation?
There's no way Next.js doesn't support this out of the box
This approach also does not allow a middleware to short circuit the chain when middleware produces a response body
|
3

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

Comments

3

This is a simple way I used to work with multiple middlewares on different routes. I made a folder called middlware and implemented every middleware on seperate file, I imported them on my main middleware and I made sure wish middleware can work on wish route pattern like this:

middlewares/guest.ts:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { cookies } from "next/headers";

//middleware example
export function guestMiddleware(request: NextRequest) {
  const cookieStore = cookies();
  const authenticated = cookieStore.get("authenticated");
  console.log(authenticated, authenticated?.value === "true");
  if (authenticated?.value === "true")
    return NextResponse.redirect(new URL("/home", request.url));
}

middleware.ts

import type { NextRequest } from "next/server";
import { guestMiddleware } from "./middlewares/guest";
import { authenticatedMiddleware } from "./middlewares/authenticated";
import { adminMiddleware } from "./middlewares/admin";
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  switch (true) {
    // this will work on /register and /login
    case /^\/(register|login)$/.test(pathname):
      return guestMiddleware(request);
    case /^\/dashboard/.test(pathname):
      return authenticatedMiddleware(request);
    /* 
    This middleware is going to work on /admin
    and any subpath eg. /admin/dashboard, 
    /admin/users/create ...
    */
    case /^\/admin(\/.*)?$/.test(pathname):
      return adminMiddleware(request); 
   
  }
}


// here I made sure that the middleware works on every route
export const config = {
  matcher: "/((?!api|_next|static|public|favicon.ico).*)",
};

UPDATE: The problem with this approch is that u cannot use more than one middleware on the same route. I found this Library called rescale/nemo that help u chain ur middleware easily:

npm install @rescale/nemo

This is the updated code:

import type { NextRequest } from "next/server";
import { guestMiddleware } from "./middlewares/guest";
import { authenticatedMiddleware } from "./middlewares/authenticated";
import { adminMiddleware } from "./middlewares/admin";
import { createMiddleware } from "@rescale/nemo";

const middlewares = {
  "/register": guestMiddleware,
  "/login": guestMiddleware,
  "/dashboard": authenticatedMiddleware,
  "regex:^/admin/\\d+$": [authenticatedMiddleware,adminMiddleware],
};


export const middleware = createMiddleware(middlewares);


export const config = {
  matcher: "/((?!api/|_next/|_static|_vercel|[\\w-]+\\.\\w+).*)",
};

Comments

1

Here a code inspired of the work of Aidid Alam

His article: https://medium.com/@aididalam/approach-to-multiple-middleware-and-auth-guard-in-next-js-app-routing-bbb641401477

//src/utils/multipleMiddlewares.ts

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

export const multipleMiddlewares =
  (
    middlewares: ((
      req: NextRequest,
      event?: NextFetchEvent,
      response?: NextResponse
    ) => NextResponse | Promise<NextResponse>)[]
  ) =>
  async (req: NextRequest, event?: NextFetchEvent, response?: NextResponse) => {
    // Array to store middleware headers
    const middlewareHeader = [];

    // Loop through middleware functions
    for (const middleware of middlewares) {
      // Execute middleware function and await the result
      const result = await middleware(req, event, response);

      // Check if the result is not okay and return it
      if (!result.ok) {
        return result;
      }

      // Push middleware headers to the array
      middlewareHeader.push(result.headers);
    }

    // Merge all the headers to check if there is a redirection or rewrite
    const mergedHeaders = new Headers();

    // Merge all the custom headers added by the middlewares
    const transmittedHeaders = new Headers();

    // Merge
    middlewareHeader.forEach((header) => {
      for (const [key, value] of header.entries()) {
        mergedHeaders.append(key, value);

        // check if its a custom header added by one of the middlewares
        if (key.startsWith('x-middleware-request-')) {
          // remove the prefix to get the original key
          const fixedKey = key.replace('x-middleware-request-', '');

          // add the original key to the transmitted headers
          transmittedHeaders.append(fixedKey, value);
        }
      }
    });

    // Look for the 'x-middleware-request-redirect' header
    const redirect = mergedHeaders.get('x-middleware-request-redirect');

    // If a redirection is required based on the middleware headers
    if (redirect) {
      // Perform the redirection
      return NextResponse.redirect(new URL(redirect, req.url), {
        status: 307, // Use the appropriate HTTP status code for the redirect
      });
    }

    // Look for the 'x-middleware-rewrite' header
    const rewrite = mergedHeaders.get('x-middleware-rewrite');
    if (rewrite) {
      // Perform the rewrite
      return NextResponse.rewrite(new URL(rewrite, req.url), {
        request: {
          headers: transmittedHeaders,
        },
      });
    }

    // Initialize a NextResponse object
    return NextResponse.next({
      request: {
        headers: transmittedHeaders,
      },
    });
  };

Usage:

// src/middleware.ts

import { multipleMiddlewares } from '@utils/multipleMiddlewares';

export const middleware = multipleMiddlewares([
  middleware1,
  middleware2,
]);

// Middleware can also be exported as a default:
// Example:
// export default multipleMiddlewares([middleware1, middleware2]);

// applies this middleware only to files in the app directory
export const config = {
  matcher:
    '/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)',
};

See example here

Comments

0

I have done it by splitting different work into different functions, and pass request and response as parameter to every middleware.

interface MiddleWare {
  (req: NextRequest, res: NextResponse): NextResponse;
}



const middlewarelist: MiddleWare[] = [
  mobileAgentMiddleware,
  authMiddleware,
]


export function middleware(req: NextRequest) {
  let res = NextResponse.next();

  // sequence of middleware functions
  for (const middlewareFunc of middlewarelist) {
    res = middlewareFunc(req, res);
  }

  return res;
}

and here is the code for the two middlewares

1. authmiddleware.ts

export const authMiddleware = (req: NextRequest, res: NextResponse): NextResponse => {
    console.log(' inside authMiddleware');
    const token = req.cookies.get('token')?.value;
    const res_token = res.cookies.get('token')?.value;

    if (!token && !res_token) {
        res.cookies.delete('token');
        res.cookies.delete('user');
        return NextResponse.redirect(new URL('/login', req.url))
    }
    return res;
}

2. mobileAgentMiddleware.ts

export const mobileAgentMiddleware = (req: NextRequest, res: NextResponse): NextResponse => {

    const userAgent = req.headers.get('user-agent') || '';
    // Check if request is coming from a mobile device
    const isMobile = [/android/i].some(item => item.test(userAgent));

    if (!isMobile) {
        return res;
    }

    const authorizationHeader = req.headers.get('authorization'); // Bearer token
    //  const headerUser = req.headers.get('user'); // ! this is not implemented yet 
    if (isMobile && authorizationHeader?.startsWith('Bearer ')) {
        const token = authorizationHeader.split(' ')[1];

        res.cookies.set({
            name: 'token',
            value: token,
            httpOnly: false,
            maxAge: 60 * 60 * 24 * 7, // 7 days
        });

    }
    return res;
}

Now both of the middlewares are smoothly working.

Comments

-3

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.

1 Comment

I think you should add some examples as the docs itself doesn't provide any (at the time of this comment)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.