Search code examples
next.jsmiddleware

Conditionally add certain Middlewares in certain routes NEXT JS


Based on the documentation, all the middlewares are added in a single file called middleware.ts.

And most of the time, the middlewares are all added to all paths

middleware1(middleware2(middleware3))

This is what I did

Middleware.ts

import { NextFetchEvent, NextResponse, type NextRequest } from "next/server";
import { updateSession } from "@/utils/supabase/middleware";
import { redirect } from "next/navigation";
import { withHeaders } from "./middleware/withHeader";
import { withLogging } from "./middleware/withLogging";
import { StackMiddleware } from "./middleware/stackMiddleware";

export async function middleware(
  request: NextRequest,
  resposne: NextResponse,
  event: NextFetchEvent
) {
  const response = NextResponse.next();
  if (request.nextUrl.pathname.startsWith("/test")) {
    // return NextResponse.rewrite(new URL("/about-2", request.url));
    const middlewares = [withLogging, withHeaders];
    StackMiddleware(middlewares);
  }
  const themePreference = response.cookies.get("theme");
  if (!themePreference) {
    response.cookies.set("theme", "dark");
  }
  await updateSession(request);
  return response;
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
};

StackMiddleware.ts

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

export function StackMiddleware(
  functions: MiddlewareFactory[] = [],
  index = 0
): NextMiddleware {
  const current = functions[index];
  //Base case
  if (current) {
    const next = StackMiddleware(functions, index + 1);
    return current(next);
  }
  return () => NextResponse.next();
}

How do I conditionally add middleware to certain routes ? In this example how do I stack middleware for /test ?

Thank you


Solution

  • Found the Solution for it.

    First define StackMiddleware.ts , this is where you can chain multiple middlewares. This function accepts an array of middleware functions and returns a single combined middleware function.

    app/Middleware/types.ts

    import { NextMiddleware } from "next/server";
    
    export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware;
    

    app/Middleware/StackMiddleware.ts

    import { NextMiddleware, NextResponse } from "next/server";
    import { MiddlewareFactory } from "./types";
    
    export function StackMiddleware(
      functions: MiddlewareFactory[] = [],
      index = 0
    ): NextMiddleware {
      const current = functions[index];
      //Base case
      if (current) {
        const next = StackMiddleware(functions, index + 1);
        return current(next);
      }
      return () => NextResponse.next();
    }
    

    app/Middleware/WithHeader.ts

    import { NextFetchEvent, NextMiddleware, NextRequest } from "next/server";
    import { MiddlewareFactory } from "./types";
    export const withHeaders: MiddlewareFactory = (next: NextMiddleware) => {
      return async (request: NextRequest, _next: NextFetchEvent) => {
        const res = await next(request, _next);
        if (res) {
          res.headers.set("x-content-type-options", "nosniff");
          res.headers.set("x-dns-prefetch-control", "false");
          res.headers.set("x-download-options", "noopen");
          res.headers.set("x-frame-options", "SAMEORIGIN");
          console.log(res.headers);
        }
        return res;
      };
    };
    

    app/Middleware/WithLogging.ts

    // middleware/withLogging.ts
    import { NextFetchEvent, NextRequest } from "next/server";
    import { MiddlewareFactory } from "./types";
    export const withLogging: MiddlewareFactory = (next) => {
      console.log("WITH LOGGING !");
      return async (request: NextRequest, _next: NextFetchEvent) => {
        console.log("Log some data here", request.nextUrl.pathname);
        return next(request, _next);
      };
    };
    

    So this is how we use it in middleware.ts

    import { NextFetchEvent, NextResponse, type NextRequest } from "next/server";
    import { withHeaders } from "./middleware/withHeader";
    import { withLogging } from "./middleware/withLogging";
    import { StackMiddleware } from "./middleware/stackMiddleware";
    import { MiddlewareFactory } from "./middleware/types";
    import { randomMiddleware } from "./middleware/randomMiddleware"; //another middleware i created
    
    //We define our routes here
    const routeMiddlewares: { [key: string]: MiddlewareFactory[] } = {
      "/test": [withLogging, withHeaders], //These are the middlewares we want the route to go through
      "/profile": [withLogging, randomMiddleware],
      // Add other routes and their specific middlewares here
    };
    
    export async function middleware(
      request: NextRequest,
      resposne: NextResponse,
      event: NextFetchEvent
    ) {
      const response = NextResponse.next();
      const path = request.nextUrl.pathname;
      for (const route in routeMiddlewares) {
        if (path.startsWith(route)) {
          const middlewares = routeMiddlewares[route];
          const stackMiddleware = StackMiddleware(middlewares);
          return stackMiddleware(request, event);
        }
      }
      return response;
    }
    
    export const config = {
      matcher: [
        /*
         * Match all request paths except for the ones starting with:
         * - _next/static (static files)
         * - _next/image (image optimization files)
         * - favicon.ico (favicon file)
         * Feel free to modify this pattern to include more paths.
         */
        "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
      ],
    };
    

    Now if we access test route, it will go through withLogging & withHeaders Middleware. If we access profile route, it will go through withLogging and randomMiddleware only but it will NOT go through withHeaders Middleware.