Next.js 13 App Router中如何在多个路由处理器间共享逻辑?
Next.js 13 App Router的路由处理器(Route Handler)目前没有官方提供的类似Layout的共享逻辑机制,官方文档也未提及相关功能。针对你提到的通用登录验证需求,这里有几个简洁实用的替代方案:
方案1:封装通用验证函数,在路由处理器中显式调用
这是最直接的方式,把登录验证、Cookie读取、错误返回的逻辑封装成独立函数,在需要的API路由里直接调用。
示例代码:
首先创建lib/auth.ts:
import { cookies } from 'next/headers'; import { NextResponse } from 'next/server'; export async function validateUserSession() { const cookieStore = cookies(); const authToken = cookieStore.get('auth_token')?.value; // 模拟验证逻辑:检查token是否存在、是否过期 if (!authToken) { return NextResponse.json({ error: '未登录' }, { status: 401 }); } if (isTokenExpired(authToken)) { // 假设你有自己的过期检查函数 return NextResponse.json({ error: '登录已过期' }, { status: 401 }); } // 验证通过,返回用户信息(如果需要) const user = await fetchUserInfoByToken(authToken); // 假设你有获取用户信息的逻辑 return { user }; } // 辅助函数示例 function isTokenExpired(token: string) { // 实际的过期检查逻辑 return false; } async function fetchUserInfoByToken(token: string) { // 实际的用户信息获取逻辑 return { id: 1, name: 'test' }; }
然后在需要验证的API路由(比如app/api/profile/route.ts)中使用:
import { validateUserSession } from '@/lib/auth'; import { NextResponse } from 'next/server'; export async function GET(request: Request) { const validationResult = await validateUserSession(); // 如果返回的是NextResponse,说明验证失败,直接返回错误响应 if (validationResult instanceof NextResponse) { return validationResult; } // 验证通过,执行API逻辑 return NextResponse.json({ user: validationResult.user }); }
优点:逻辑清晰,无需额外配置,适合少量需要验证的API;缺点:每个需要验证的路由都要手动调用函数,存在少量重复代码。
方案2:使用高阶函数(HOC)包裹路由处理器
把验证逻辑封装成高阶函数,用它来包裹需要验证的路由处理器,实现逻辑的自动注入,减少重复代码。
示例代码:
在lib/auth.ts中添加高阶函数:
import { cookies } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; // 定义路由处理器的类型 type RouteHandler = (req: NextRequest) => Promise<NextResponse>; export function withAuth(handler: RouteHandler): RouteHandler { return async (req: NextRequest) => { const cookieStore = cookies(); const authToken = cookieStore.get('auth_token')?.value; // 验证逻辑和之前一致 if (!authToken) { return NextResponse.json({ error: '未登录' }, { status: 401 }); } if (isTokenExpired(authToken)) { return NextResponse.json({ error: '登录已过期' }, { status: 401 }); } // 验证通过,执行原处理器 return handler(req); }; } // 复用之前的辅助函数 function isTokenExpired(token: string) { return false; }
然后在API路由中使用:
import { withAuth } from '@/lib/auth'; import { NextResponse } from 'next/server'; // 用withAuth包裹原处理器 export const GET = withAuth(async (request) => { // 这里直接写验证通过后的API逻辑 return NextResponse.json({ message: '获取用户信息成功' }); });
优点:路由处理器代码更简洁,验证逻辑统一维护;缺点:如果需要传递用户信息到原处理器,需要额外处理(比如通过request对象挂载,或者修改高阶函数返回值)。
方案3:路由分组+Middleware(优化维护性)
你提到Middleware维护性不佳,主要是因为要检查所有API URL。可以通过路由分组把需要验证的API归类到同一组,然后Middleware只针对该分组路径做验证,避免全局匹配。
步骤:
- 创建路由分组,比如把需要验证的API放在
app/api/auth-protected/(routes)/目录下,比如app/api/auth-protected/(routes)/profile/route.ts、app/api/auth-protected/(routes)/orders/route.ts。 - 在
middleware.ts中只匹配该分组的路径:
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // 只针对auth-protected分组下的API做验证 if (request.nextUrl.pathname.startsWith('/api/auth-protected/')) { const authToken = request.cookies.get('auth_token')?.value; if (!authToken || isTokenExpired(authToken)) { return NextResponse.json({ error: '未登录或登录过期' }, { status: 401 }); } } // 其他路径直接放行 return NextResponse.next(); } // 配置Middleware的匹配规则,只匹配auth-protected分组 export const config = { matcher: '/api/auth-protected/:path*', }; // 辅助函数示例 function isTokenExpired(token: string) { return false; }
优点:批量保护一组API,无需在每个路由中写验证逻辑;缺点:需要调整路由结构,且Middleware中无法直接使用App Router的cookies()函数(只能使用request.cookies),如果需要复杂的服务器端逻辑,可能不如前两个方案灵活。
内容的提问来源于stack exchange,提问作者Lee KyuSung

