Next.js 13中如何链式调用next-auth与多语言中间件?
解决Next.js 13.4.16多中间件链式调用(withLocale + next-auth)问题
核心问题分析
- matcher配置冲突:
withLocale的匹配规则覆盖大部分非静态资源路径,而middleware.ts仅针对带语言前缀的特定路由,两者范围不一致导致部分路径未被正确处理。 - withAuth调用方式错误:
withAuth是高阶函数,返回的是中间件函数而非直接处理请求的结果,直接await withAuth(...)无法正确执行鉴权逻辑。
解决方案步骤
1. 统一全局matcher配置
将两个中间件的匹配规则合并到middleware.ts的config中,确保所有需要处理的路径都被覆盖:
- 保留
withLocale需要处理的排除规则(排除静态资源、API等) - 加入需要鉴权的带语言前缀的路由规则
2. 正确实现中间件链式调用
先执行withLocale处理多语言逻辑,若返回重定向响应(3xx状态码)则直接返回;若为NextResponse.next(),则将请求传递给withAuth生成的中间件执行鉴权。
修改后的完整代码
middleware.ts
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { withAuth } from "next-auth/middleware" import withLocale from "@/app/middlewares/withLocale"; // 合并matcher规则:覆盖withLocale需要处理的路径 + 需要鉴权的路由 export const config = { matcher: [ // 匹配withLocale的处理范围(排除静态资源、API等) '/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js|manifest.json).*)', // 叠加需要鉴权的路由 '/(en|es|ru)/(chat|intake-form|home|settings|stories|words|admin|me)' ], }; // 预先生成withAuth的中间件实例 const authMiddleware = withAuth({ callbacks: { authorized({ req, token }) { // `/admin`路由需要admin角色 if (req.nextUrl.pathname.includes("/admin")) { return token?.userRole === "admin"; } // 指定路由仅需用户已登录 if (req.nextUrl.pathname.includes("/me") || ['/chat', '/intake-form', '/home', '/settings', '/stories', '/words'].some(path => req.nextUrl.pathname.includes(path))) { return !!token; } // 其他路径默认放行 return true; }, }, }) export default async function middleware(req: NextRequest) { // 第一步:执行多语言中间件 const localeResponse = withLocale(req); // 若多语言中间件返回重定向/非next响应,直接返回 if (localeResponse.status !== 200) { return localeResponse; } // 第二步:执行鉴权中间件 return authMiddleware(req); }
withLocale.ts(移除独立matcher)
删除withLocale.ts中的export const config,避免与全局matcher冲突:
import { NextResponse, NextRequest } from 'next/server'; import acceptLanguage from 'accept-language'; import { fallbackLng, languages } from '@/app/i18n/settings'; import { PATH_LOGIN } from '@/config/path'; acceptLanguage.languages(languages); const cookieName = 'i18next'; export function withLocale(req: NextRequest) { if ( req.nextUrl.pathname.indexOf('icon') > -1 || req.nextUrl.pathname.indexOf('chrome') > -1 ) return NextResponse.next(); let lng: string | null = ''; 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; // 排除特定路由不进行重定向 const excludedRoutes = ['/welcome', PATH_LOGIN]; // 如果路径中的语言不支持则重定向 if ( !languages.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}`)) && !req.nextUrl.pathname.startsWith('/_next') && req.nextUrl.pathname !== '/' && !excludedRoutes.includes(req.nextUrl.pathname) ) { 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(); } export default withLocale;
关键说明
- matcher合并逻辑:数组形式的matcher会匹配任意符合条件的路径,确保所有需要多语言处理和鉴权的路径都被覆盖。
- 执行顺序:先处理多语言路径重定向,再执行鉴权,避免鉴权时路径不规范。
- withAuth正确用法:预先生成authMiddleware实例,再在主中间件中调用,确保鉴权逻辑正常执行。
内容的提问来源于stack exchange,提问作者in43sh
相关产品推荐
相关产品推荐

