Combining Several Middleware Functions in NextJs 13 for Authentication and Localization

Important Note

If you are interested in exploring my code further, you can find the repository here. To access the specific code for the ott-platform, navigate to apps/ott-platform. Please make sure to create an account on Clerk and input your Clerk key in the .env file.

Issue at Hand

I am currently working on a Next.js app (version 13.4.19) with features like app-dir-routing, TypeScript, and Turbo. My goal is to implement authentication using Clerk and internationalization using i18next. However, I am facing a challenge with integrating multiple middleware functions, as it is not supported by default. After some research, I discovered a way to create a chain middleware function by splitting my middleware functions into separate files for better organization (there is also a helpful YouTube tutorial available).

Despite my efforts, I am encountering the following error message:

./middleware.ts:5:39
Type error: Type '(req: NextRequest) => NextResponse<unknown>' is not assignable to type 'MiddlewareFactory'.
Types of parameters 'req' and 'middleware' are incompatible.
Type 'NextMiddleware' is not assignable to type 'NextRequest'.

While the Authentication aspect is functioning correctly, I seem to be facing difficulties with the Internationalization component. It is possible that I made an error during the integration process. The middleware function for Internationalization was sourced from the example repository provided by i18next.


Here is the content of middleware.ts:

import { chain } from '@/middlewares/chain'
import { Internationalization } from '@/middlewares/internationalization';
import { Authentication } from '@/middlewares/authentication';

export default chain([Authentication, Internationalization])

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

Here is the content of chain.ts:


import { NextResponse } from 'next/server'
import type { NextMiddleware } from 'next/server'

type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware

export function chain(
  functions: MiddlewareFactory[],
  index = 0
): NextMiddleware {
  const current = functions[index]

  if (current) {
    const next = chain(functions, index + 1)
    return current(next)
  }

  return () => NextResponse.next()
}

Here is the content of authentication.ts:

import { authMiddleware } from "@clerk/nextjs";

export const Authentication = () => {
  return authMiddleware({
    publicRoutes: [
      "/(.*)",
      "/signin(.*)",
      "/signup(.*)",
      "/sso-callback(.*)",
    ],
  });
};

Here is the content of internationalization.ts:

import { NextResponse, NextRequest } from "next/server";
import acceptLanguage from "accept-language";
import { fallbackLng, languages, cookieName } from "@/app/i18n/settings";

acceptLanguage.languages(languages);

export function Internationalization(req: NextRequest) {
  if (
    req.nextUrl.pathname.indexOf("icon") > -1 ||
    req.nextUrl.pathname.indexOf("chrome") > -1
  )
    return NextResponse.next();
  let lng: string;
  if (req.cookies.has(cookieName))
    lng = acceptLanguage.get(req.cookies.get(cookieName).value);
  if (!lng) lng = acceptLanguage.get(req.headers.get("Accept-Language"));
  if (!lng) lng = fallbackLng;

  // Redirect if lng in path is not supported
  if (
    !languages.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}`)) &&
    !req.nextUrl.pathname.startsWith("/_next")
  ) {
    return NextResponse.redirect(
      new URL(`/${lng}${req.nextUrl.pathname}`, req.url),
    );
  }

  if (req.headers.has("referer")) {
    const refererUrl = new URL(req.headers.get("referer"));
    const lngInReferer = languages.find((l) =>
      refererUrl.pathname.startsWith(`/${l}`),
    );
    const response = NextResponse.next();
    if (lngInReferer) response.cookies.set(cookieName, lngInReferer);
    return response;
  }

  return NextResponse.next();
}

My goal is to create a middlware.ts file that can handle multiple middleware functions. I have also attempted to address this issue by following the guidance provided in this thread, but unfortunately, the solution did not work as expected.

Answer №1

Approach Implementation

Encountering a similar challenge recently, I devised and executed the following approach:

src/middleware.ts

The script provided contains the essential imports and establishes the final middleware chain along with configuration settings.

import { middleware1 } from './middlewares/middleware1';
import { middleware2 } from './middlewares/middleware2';
import { middlewareHandler } from './middlewares/middlewareHandler';

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

export const config = {
  matcher: ['/((?!api|fonts|_next/static|_next/image|favicon.ico).*)'],
};

src/middlewares/middlewareHandler.ts

In this section, indispensable types and interfaces are imported. The function middlewareHandler links multiple middleware wrappers seamlessly.

import type { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
import type { MiddlewareWrapperType, ChainMiddlewareType } from './middlewareTypes';

export function middlewareHandler(middlewares: Array<MiddlewareWrapperType>, i = 0): ChainMiddlewareType {
  const current = middlewares[i];

  if (current) {
    const next = middlewareHandler(middlewares, i + 1);

    return current(next);
  }

  return (_req: NextRequest, _evt: NextFetchEvent, res: NextResponse) => {
    return res;
  };
}

src/middlewares/middlewareTypes.ts

Create a fresh file for unified type definitions.

import type { NextFetchEvent, NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import type { NextMiddlewareResult } from 'next/dist/server/web/types';

export type MiddlewareWrapperType = (middleware: ChainMiddlewareType) => ChainMiddlewareType;

export type ChainMiddlewareType = (
  request: NextRequest,
  event: NextFetchEvent,
  response: NextResponse
) => NextMiddlewareResult | Promise<NextMiddlewareResult>;

src/middlewares/middleware1.ts

This segment introduces middleware1, which formulates a default NextResponse and transfers command to the subsequent middleware.

import type { NextRequest, NextFetchEvent } from 'next/server';
import { NextResponse } from 'next/server';
import type { MiddlewareWrapperType, ChainMiddlewareType } from './middlewareTypes';

export const middleware1: MiddlewareWrapperType = (next) => {
  return (req, evt) => {
    const res = NextResponse.next();

    return next(req, evt, res);
  };
};

src/middlewares/middleware2.ts

This file defines middleware2, which appends a personalized header to the response. Nevertheless, adjustments can be made in accordance with project requirements.

import type { MiddlewareWrapperType, ChainMiddlewareType } from './middlewareTypes';

export const middleware2: MiddlewareWrapperType = (next) => {
  return (req, evt, res) => {
    res.headers.set('x-middleware', 'custom header');

    return next(req, evt, res);
  };
};

Testing

src/middleware/middlewareHandler.test.ts

import type { NextFetchEvent } from 'next/server';
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
import type { MiddlewareWrapperType as MiddlewareWrapperType } from 'middleware/middlewareHandler';
import { middlewareHandler } from 'middleware/middlewareHandler';

describe('middlewareHandler', () => {
  const mockRequest = new Request('https://example.com');
  const mockFetchEvent = {} as NextFetchEvent;
  const mockNextRequest = new NextRequest(mockRequest);
  const mockResponse = new NextResponse();

  it('should engage each middleware in the chain precisely once', () => {
    const mockMiddleware1 = jest.fn((next) => next);
    const mockMiddleware2 = jest.fn((next) => next);
    const chained = middlewareHandler([mockMiddleware1, mockMiddleware2]);

    chained(mockNextRequest, mockFetchEvent, mockResponse);

    expect(mockMiddleware1).toHaveBeenCalledTimes(1);
    expect(mockMiddleware2).toHaveBeenCalledTimes(1);
  });

  it('should transfer authority to the subsequent middleware in the chain', async () => {
    const mockMiddleware1: MiddlewareWrapperType = jest.fn((next) => {
      return (req, evt, resp) => {
        return next(req, evt, resp);
      };
    });

    const mockMiddleware2: MiddlewareWrapperType = jest.fn(() => {
      return () => {
        return new NextResponse(JSON.stringify({ hello: 'world' }));
      };
    });

    const chained = middlewareHandler([mockMiddleware1, mockMiddleware2]);
    const result = await chained(mockNextRequest, mockFetchEvent, mockResponse);
    const json = await result?.json();

    expect(json).toEqual({ hello: 'world' });
  });

  it('should deliver the response in the absence of provided middleware', async () => {
    const chained = middlewareHandler([]);
    const result = await chained(mockNextRequest, mockFetchEvent, mockResponse);

    expect(result).toEqual(mockResponse);
  });

  it('should handle asynchronous middleware accurately', async () => {
    const asyncMiddleware: MiddlewareWrapperType = jest.fn((next) => {
      return async (req, evt, resp) => {
        await new Promise((resolve) => setTimeout(resolve, 100));
        return next(req, evt, resp);
      };
    });

    const mockMiddleware = jest.fn((next) => next);
    const chained = middlewareHandler([asyncMiddleware, mockMiddleware]);
    const time = performance.now();

    await chained(mockNextRequest, mockFetchEvent, mockResponse);

    expect(performance.now() - time).toBeGreaterThanOrEqual(100);
    expect(asyncMiddleware).toHaveBeenCalledTimes(1);
    expect(mockMiddleware).toHaveBeenCalledTimes(1);
  });
});

Note: Credit to Hamed Bahram for influencing my solution.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ensuring the accurate usage of key-value pairs in a returned object through type-checking

After generating a type definition for possible response bodies, I am looking to create a function that returns objects shaped as { code, body }, which are validated against the typing provided. My current solution looks like this: type Codes<Bodies> ...

Best practices for organizing an array of objects in JavaScript

I have an array of objects with nested arrays inside, and I need to restructure it according to my API requirements. [{ containerId: 'c12', containerNumber: '4321dkjkfdj', goods: [{ w ...

Encountering a "Module parse failed" error with type annotations in Nextjs while using Yarn Workspaces

I decided to experiment with transitioning a project from using Vite and React to Next.js and React. After reviewing the documentation on this page: https://nextjs.org/learn-pages-router/foundations/from-react-to-nextjs/getting-started-with-nextjs I made t ...

Despite its presence, @Input() remains undefined

Currently, I am engrossed in a project that aims to display information about movies. By utilizing @Input(), I am establishing a connection between the movie details from the parent component (movies) and the child component (movie-detail). Movies Parent ...

Managing Observable<Person[]> in ng-bootstrap typeahead instead of Observable<string[]>: a complete guide

I'm new to Angular/Typescript and have a question. I recently tried out the example from Wikipedia in ng-bootstrap typeahead. Instead of using the Wikipedia call, I decided to use a custom REST service that has the following GET endpoint: GET /pers ...

When I define a type in TypeScript, it displays "any" instead

Imagine a scenario where we have a basic abstract class that represents a piece in a board game such as chess or checkers. export abstract class Piece<Tags, Move, Position = Vector2> { public constructor(public position: Position, public tags = nul ...

Setting up Scss and purgeCss configuration in Next.js custom postCSS configuration: A step-by-step guide

My current project is using Scss in combination with Bootstrap for design. I have implemented purgeCss to remove unused Css, and customized my postcss.config.js file as follows: module.exports = { plugins: [ "postcss-flexbugs-fixes", [ " ...

Is it possible to integrate React Bootstrap with Next.js?

Is it possible to integrate react-bootstrap with Next.js? I have the latest versions of both, but none of my react-bootstrap components are showing up in my app. I've searched online for solutions, but haven't found anything helpful. If needed, I ...

There seems to be an issue with running the build command in next.js, resulting in an npm run

Whenever I try to execute npm run build, I encounter an error. Despite all pages functioning correctly without any errors, the problem arises when I attempt to make API calls using getServerSideProps. Error: Error: connect ECONNREFUSED 127.0.0.1:3000 ...

Does a <Navigate> exist in the react-router-dom library?

Within the parent component import LoginPage from "pages/admin"; export function Home() { return <LoginPage />; } Inside the child component import { useRouter } from "next/router"; export default function LoginPage() { co ...

Next.js - NextAuth consistently delivers a successful status code every time

In my Next.js project, I am utilizing NextAuth. The issue I'm encountering is that NextAuth consistently returns a 200 status code and updates the session to authenticated, even when the username or password doesn't match. I've attempted thr ...

Can you explain the purpose of the MomentInput type in ReactJS when using TypeScript?

I am currently facing an issue where I need to distinguish between a moment date input (material-ui-pickers) and a normal text input for an event in my project. const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const i ...

The variable "$" cannot be found within the current context - encountering TypeScript and jQuery within a module

Whenever I attempt to utilize jQuery within a class or module, I encounter an error: /// <reference path="../jquery.d.ts" /> element: jQuery; // all is good elementou: $; // all is fine class buggers{ private element: jQuery; // The nam ...

Creating chained fetch API calls with dependencies in Next.js

I am a novice who is delving into the world of API calls. My goal is to utilize a bible api to retrieve all books, followed by making another call to the same api with a specific book number in order to fetch all chapters for that particular book. After ...

Data binding in Angular 2: Connecting components

Is it possible to establish a connection between two components that are working with related objects? One of the components is dedicated to filtering, while the other displays the data results. By applying filters such as checkboxes, the displayed data ...

What is the process for having "export default" convert to "module.exports" during compilation?

In my TypeScript project set to compile to CommonJS, using export default results in it compiling into exports.default instead of module.exports. I am creating an NPM package and need this issue resolved. How can I fix this? I have the tsconfig.json file ...

Typescript enhances React Native's Pressable component with a pressed property

I'm currently diving into the world of typescript with React, and I've encountered an issue where I can't utilize the pressed prop from Pressable in a React Native app while using typescript. To work around this, I am leveraging styled comp ...

Troubles with styling <Image /> components in Next.js using tailwind CSS

Currently, I am in the process of building a blog site following a tutorial. However, I have encountered a problem with formatting an <Image /> in Next.js. Despite closely following the steps outlined in the video tutorial, it appears that the video ...

What is the best way to utilize v-model with an array of strings in a Vuex store when using v-for

Encountered an issue while trying to set a value in an Array within the Vuex Store: VueCompilerError: v-model cannot be used on v-for or v-slot scope variables because they are not writable. Seeking alternatives to achieve this without creating a local co ...

When using ngFor, a conversion from a string literal type to a regular string occurs, resulting in an error that states: "Element implicitly has an 'any' type because an expression of type 'string' cannot be utilized..."

When utilizing the iterator *ngFor, it converts a string union literal type ("apple" | "banana") to a string type. However, when attempting to use it as an index of an array expecting the correct string union literal type, an error occu ...