NextJS+next-intl中间件IP拦截重定向报错及API封禁咨询
解决方案
一、修复next-intl报错并实现API驱动的IP封禁
1. 核心修改点
- 将middleware改为异步函数以支持API请求
- 所有路由(包括封禁重定向)必须经过
intlMiddleware处理,确保locale上下文初始化 - 替换原有的国家封禁逻辑为API查询IP状态
- 处理API请求失败的fallback逻辑
2. 修改后的完整middleware代码
import createMiddleware from 'next-intl/middleware'; import { defaultLocale, locales, pathnames } from "./config/lang-config"; import withAuth from 'next-auth/middleware'; import { NextRequest, NextResponse } from 'next/server'; import { getProfile } from './services/authQueries'; const privatePages = [ '/profile', '/settings', '/settings/*', '/wallet', '/wallet/*', '/auth/logout', ]; const intlMiddleware = createMiddleware({ defaultLocale, locales, localePrefix: "as-needed", pathnames, localeDetection: true, }); const authMiddleware = withAuth( async function onSuccess(req) { return intlMiddleware(req); }, { callbacks: { authorized: async ({ token }: { token: any }) => { const accessToken = token?.tokenData?.token || token?.token as string; if (token) { try { const res = await getProfile(accessToken as string, 'en'); if (res.isSucceed) { token.user = res.data; return true; } else { throw new Error(res.message); } } catch (error) { if (error instanceof Error) { console.error(error.message); } else { console.error('An unknown error occurred'); } return false; } } else { return false; } }, }, pages: { signIn: '/', } } ); export default async function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; // 生产环境下检查IP封禁(排除/blocked页面避免循环重定向) if (process.env.NODE_ENV !== 'development' && pathname !== '/blocked') { // 获取用户真实IP(适配不同部署环境) const ip = req.ip || req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.headers.get('x-real-ip'); if (ip) { try { // 调用封禁检查API(替换为你的实际API地址) const blockResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/check-blocked-ip`, { method: 'POST', headers: { 'Content-Type': 'application/json', // 内部API认证:添加密钥防止恶意调用 'Authorization': `Bearer ${process.env.INTERNAL_API_SECRET}` }, body: JSON.stringify({ ip }) }); if (!blockResponse.ok) throw new Error('封禁检查API响应异常'); const blockData = await blockResponse.json(); if (blockData.isBlocked) { // 先通过intlMiddleware初始化locale,再重定向到带locale的封禁页面 const intlResponse = intlMiddleware(req); const baseUrl = new URL(req.url); // 生成带正确locale的/blocked路径 const blockedPath = intlResponse.headers.get('location') ? `${intlResponse.headers.get('location')}/blocked`.replace(/\/+/g, '/') : `/${defaultLocale}/blocked`; intlResponse.headers.set('location', new URL(blockedPath, baseUrl).toString()); return intlResponse; } } catch (error) { console.error('IP封禁检查失败:', error); // 可选:API故障时的 fallback 逻辑,比如默认允许访问或临时封禁 // return NextResponse.redirect(new URL('/blocked', req.url)); } } } // 私有页面/公开页面路由处理 const privatePathnameRegex = RegExp( `^(/(${locales.join('|')}))?(${privatePages .flatMap((p) => p.replace(/\*/g, '.*')) .map((p) => p === '/' ? ['', '/'] : p) .join('|')})/?$`, 'i' ); const isPrivatePage = privatePathnameRegex.test(req.nextUrl.pathname); if (!isPrivatePage) { const country = req.geo?.country || req.headers.get('cloudfront-viewer-country') || req.headers.get('x-vercel-ip-country'); const response = intlMiddleware(req); // 修正拼写错误:client-counry → client-country response.cookies.set("client-country", country || ''); return response; } else { // 异步处理authMiddleware return await (authMiddleware as any)(req); } } export const config = { matcher: [ '/', '/(en-US)/:path*', '/((?!api|_next|_vercel|.*\\..*).*)' ] };
二、API管控封禁用户的建议
- API设计:
- 接口接收
ip参数,返回{ isBlocked: boolean, reason?: string }格式的响应 - 加入API密钥认证,仅允许内部服务调用
- 接口接收
- 性能优化:
- 用Redis缓存封禁IP的结果,设置15-30分钟的过期时间,减少API请求量
- 容错机制:
- API请求失败时,根据业务需求选择默认放行或临时封禁,避免影响正常用户访问
- 日志记录:
- 在middleware和API中记录封禁事件(IP、时间、原因),用于后续分析和合规检查
- 封禁页面适配:
- 确保
/blocked页面支持多语言,在intl的pathnames配置中添加该页面的路由映射
- 确保
内容的提问来源于stack exchange,提问作者Furkan Tombaş
相关产品推荐
相关产品推荐

