
本文介绍如何利用 next.js middleware 拦截请求,结合地理定位 api 获取用户国家代码,并将原始域名(如 abc.com)自动重写为带国家前缀的路径(如 abc.com/us),全程无需客户端跳转,提升 seo 与用户体验。
本文介绍如何利用 next.js middleware 拦截请求,结合地理定位 api 获取用户国家代码,并将原始域名(如 abc.com)自动重写为带国家前缀的路径(如 abc.com/us),全程无需客户端跳转,提升 seo 与用户体验。
在 Next.js(推荐 v13.4+ App Router)中,实现基于用户地理位置的 URL 路径重写(如 abc.com → abc.com/jp),最佳实践是使用 Middleware —— 它在请求到达路由处理器前执行,支持服务端 IP 地理定位与同步重写,避免客户端重定向带来的白屏、SEO 损失及状态丢失问题。
✅ 核心实现步骤
-
创建
middleware.ts(位于项目根目录或app/下) -
获取用户真实 IP(注意:生产环境需通过
x-forwarded-for等头解析,Vercel 自动注入geo.country) - 基于地理位置映射目标路径(推荐优先使用 Vercel 内置地理信息)
- 调用
NextResponse.rewrite()实现静默重写
⚠️ 注意:浏览器端 Geolocation API(如 Google Maps JS SDK)不适用于服务端重写——它仅在前端运行、需用户授权、且无法用于 Middleware(无 window 对象)。服务端应依赖 IP 地理库或平台能力(如 Vercel 的
req.geo)。
✅ 推荐方案:利用 Vercel 内置地理信息(零配置、高可靠)
Vercel 部署时自动为每个请求注入 req.geo 对象(含 country, region, city 等字段),无需额外 API 调用:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const { geo } = request;
const countryCode = (geo?.country || 'us').toLowerCase(); // 默认 us
const path = request.nextUrl.pathname;
// 避免重复重写:跳过已含国家码的路径(如 /us/, /jp/about)
if (path.startsWith(`/${countryCode}/`) || path === `/${countryCode}`) {
return NextResponse.next();
}
// 重写根路径及所有无国家前缀的路径
if (path === '/' || !/^[a-z]{2}\//.test(path.split('/')[1])) {
const url = request.nextUrl.clone();
url.pathname = `/${countryCode}${path}`;
return NextResponse.rewrite(url);
}
}
export const config = {
matcher: ['/((?!_next|api|static|favicon.ico).*)'], // 排除静态资源和 API
};
✅ 备选方案:自建 IP 地理服务(适用于非 Vercel 部署)
若部署在其他平台(如 Node.js 服务器),可集成轻量级库如 node-geoip 或调用免费 IP 定位 API(如 ipapi.co):
// 示例:使用 fetch 获取 IP 地理信息(需处理异步,Middleware 支持 Promise)
export async function middleware(request: NextRequest) {
const ip = request.ip || request.headers.get('x-forwarded-for')?.split(',')[0].trim();
if (!ip) return NextResponse.next();
try {
const res = await fetch(`https://ipapi.co/${ip}/json/`);
const data = await res.json();
const countryCode = (data.country_code || 'us').toLowerCase();
const url = request.nextUrl.clone();
if (url.pathname === '/' || !url.pathname.startsWith(`/${countryCode}/`)) {
url.pathname = `/${countryCode}${url.pathname}`;
return NextResponse.rewrite(url);
}
} catch (e) {
console.warn('Geo lookup failed, using default locale');
}
return NextResponse.next();
}
? 关键注意事项
-
SEO 友好性:
rewrite是服务端内部转发,浏览器 URL 栏显示/us/,但实际渲染app/layout.tsx+app/[country]/page.tsx,需确保对应动态路由存在; -
路由结构建议:在
app/目录下创建app/[country]/page.tsx和app/[country]/layout.tsx,并通过generateStaticParams预生成主流国家路径(可选); -
默认回退:始终为未知地区提供合理默认(如
us或en),避免 404; -
缓存策略:Middleware 执行本身不缓存,但可配合
Cache-Control头优化下游响应; -
开发环境模拟:本地测试时
req.geo为空,可在middleware.ts中添加process.env.NODE_ENV === 'development' && { country: 'jp' }模拟。
通过以上方式,你即可在 Next.js 中稳定、高效、SEO 友好地实现“URL 基于地理位置自动国家化”的核心体验。










