typescript - Kedja flera mellanprogramsfunktioner i NextJs 13 för autentisering och internationalisering
Ansvarsfriskrivning
Om du vill ta en närmare titt på min kod är detta repo. Navigera till apps/ott-platform. Du måste skapa ett konto på Clerk och skriva in din Clerk-nyckel i .env.
Problem
Jag har en Next.js-app (version 13.4.19, app-dir-routing, TypeScript, Turbo). Jag vill lägga till autentisering med Clerk och internationell anpassning med i18next. Därför måste jag lägga till flera middleware-funktioner som just nu inte stöds. Jag har listat ut att det är möjligt att skapa en kedjemiddlewarefunktion, så jag delade mina middleware-funktioner i separata filer så att kedjemiddlewarefunktionen kan importera dem (det finns också en YT-handledning).
Men jag får fortfarande felmeddelandet:
./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'.
Autentisering har importerats framgångsrikt men det verkar finnas ett problem i Internationell anpassning. Kanske gjorde jag ett misstag. Jag har integrerat middleware-funktionen för Internationell anpassning från exempel-repot av i18next.
Detta är 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)(.*)"],
};
Detta är 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()
}
Detta är authentication.ts:
import { authMiddleware } from "@clerk/nextjs";
export const Authentication = () => {
return authMiddleware({
publicRoutes: [
"/(.*)",
"/signin(.*)",
"/signup(.*)",
"/sso-callback(.*)",
],
});
};
Detta är 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();
}
Jag vill ha en middlware.ts som kan hantera flera middleware-funktioner. Jag försökte också lösa det genom att följa instruktionerna i denna tråd men det fungerade inte.
Svar #1
Implementering
Jag har nyligen stött på samma utmaning, jag har implementerat följande lösning:
src/middleware.ts
Detta kodstycke inkluderar de nödvändiga importerna och ställer in den slutliga middleware-kedjan och konfigurationen.
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
Här importeras de nödvändiga typerna och gränssnitten. middlewareHandler kedjar ihop flera middleware-omslag tillsammans.
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/mellanlager/mellanlagerTyper.ts
Skapa en ny fil för gemensamma typdefinitioner.
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/mellanlager/middleware1.ts
Detta definierar middleware1, som skapar ett standard NextResponse och överlämnar kontrollen till nästa 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
Denna fil definierar middleware2, som lägger till en anpassad header i svaret. Men självklart kan du göra vad ditt projekt kräver
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);
};
};
Tester
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 call each middleware in the chain 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 pass control to the next 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 return the response if no middleware is provided', async () => {
const chained = middlewareHandler([]);
const result = await chained(mockNextRequest, mockFetchEvent, mockResponse);
expect(result).toEqual(mockResponse);
});
it('should handle asynchronous middleware correctly', 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);
});
});
P.s.: Min lösning är inspirerad av Hamed Bahram.
