Next.js Edge Middleware 实现 i18n 自动本地化重定向:edge-middleware/i18n 模板源码解析
2026/9/18 23:02:03 网站建设 项目流程

Next.js Edge Middleware 实现 i18n 自动本地化重定向:edge-middleware/i18n 模板源码解析

【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples

本文围绕当前仓库中的 edge-middleware/i18n 示例,深入讲解如何在 Next.js 中利用 Edge Middleware 结合accept-language请求头与geolocation地理信息,将首页请求自动重写到对应的本地化页面(/[locale]/[country])。读完本文,你将掌握基于 Edge Middleware 的 i18n 重定向完整实现思路、字典数据层设计,以及它与静态生成(SSG)页面协同工作的实战方案。

示例解决的问题

当一个全球化的网站只有一个首页时,不同语言、不同国家的用户访问到的应该是"自己的版本"。传统做法是在服务端渲染时读取请求头做条件渲染,而在本示例中,这一判断被前移到Edge Middleware(边缘中间件):它在请求到达页面之前执行,直接改写 URL 路径,让 Next.js 把请求路由到对应的本地化页面。

从 middleware.ts 的注释可以看到,该示例的设计目标是:

  • 仅对首页(路径/)生效,通过matcher: '/'精确匹配;
  • 依据accept-language请求头解析用户首选语言(locale);
  • 依据 Vercel Edge Network 提供的geolocation请求头推断用户所在国家(country);
  • 将路径重写为/{locale}/{country},由静态页面承接渲染。

其核心价值在于:本地化路由的动态决策发生在边缘层,页面本身可以是完全静态的,兼顾了用户体验(自动适配语言)与性能(静态页面全球 CDN 缓存)。

边缘中间件核心实现

匹配规则与中间件入口

edge-middleware/i18n/middleware.ts 首先通过export const config声明只匹配根路径:

import { geolocation } from '@vercel/functions' import { type NextRequest, NextResponse } from 'next/server' // only run middleware on home page export const config = { matcher: '/', }

matcher: '/'意味着中间件仅在用户访问站点首页时触发,其他路径直接放行,避免对全站请求造成不必要的边缘执行开销。

读取请求头并执行重写

中间件默认导出函数的核心逻辑位于 middleware.ts:

export default function middleware(req: NextRequest) { const country = geolocation(req).country?.toLowerCase() || 'us' const locale = req.headers.get('accept-language')?.split(',')?.[0] || 'en-US' // Rewrite the path (`/`) to the localized page (pages/[locale]/[country]) req.nextUrl.pathname = `/${locale}/${country}` return NextResponse.rewrite(req.nextUrl) }

两行关键代码的含义:

  1. 国家推断geolocation(req)来自@vercel/functions包,读取 Vercel 边缘网络注入的地理位置请求头(如x-vercel-ip-country),取country字段并转为小写;若无法识别(例如本地开发环境缺少相关请求头),则回退为默认值us
  2. 语言推断req.headers.get('accept-language')取浏览器发送的语言协商请求头,split(',')[0]拿到客户端优先级最高的语言标签(如zh-CNen-USes-ES),缺失时回退为en-US

随后通过NextResponse.rewrite(req.nextUrl)将 URL 路径原地改写为/{locale}/{country}。注意这里使用的是rewrite(重写)而非 redirect(重定向):浏览器地址栏仍显示/,但内部请求被路由到本地化页面,用户无感知,也不产生额外的网络往返。

本地化字典数据层

中间件只负责"路由决策",实际的文案翻译由一套轻量的字典模块提供,位于 lib 目录下,由三个文件组成。

类型定义

lib/types.ts 定义了字典的 TypeScript 接口,保证所有语言的文案结构一致:

export interface Dictionary { title: string subtitle: string link: string greet: string }

四个字段分别对应页面上的标题、副标题、文档链接文案与问候语。

字典常量

lib/constants.ts 以Record<string, Dictionary>的形式维护了五套语言文案:default(兜底英语)、en(英语)、es(西班牙语)、fr(法语)、cn(简体中文):

export const DICTIONARIES: Record<string, Dictionary> = { default: { title: 'i18n Example', greet: 'Hello!, we could not detect your locale so we defaulted to english.', subtitle: 'Localized text based on geolocation headers', link: 'See headers documentation', }, // en / es / fr / cn ... }

其中default键承担兜底职责:当请求中的 locale 不在字典范围内时,页面使用该套文案并明确提示用户"未能检测到你的语言设置,已回退到英语"。从仓库实现看,新增语言只需在DICTIONARIES中追加一个键值对,无需改动路由逻辑。

字典获取 API

lib/api.ts 封装了字典的获取接口,屏蔽了数据来源细节:

const api = { dictionaries: { fetch: async (locale): Promise<Dictionary> => DICTIONARIES[locale] || DICTIONARIES['default'], }, } export default api

fetch在键不存在时回退到DICTIONARIES['default'],与middleware.ts中的||兜底逻辑形成双保险:中间件层兜底 locale 格式,数据层兜底字典内容。

本地化页面的静态渲染

动态路由结构

页面位于 pages/[locale]/[country].tsx,采用 Next.js Pages Router 的两级动态路由。由于国家数量众多(仓库 public/flags 目录下包含了 250+ 个国家的旗帜 SVG),示例刻意不预生成所有路径,而是使用fallback: 'blocking'

export const getStaticPaths: GetStaticPaths = async () => { // We don't want to specify all possible countries as we get those from the headers return { paths: [], fallback: 'blocking', } }

这表示:任何/{locale}/{country}路径首次被访问时,Next.js 会在服务端阻塞式生成该静态页面并缓存,后续访问直接命中缓存——既覆盖了中间件可能产生的任意国家组合,又避免了海量预构建。

按语言取字典

getStaticProps 中通过api.dictionaries.fetch(locale)按路由参数获取对应语言的字典,并设置revalidate: false保持纯静态:

export const getStaticProps: GetStaticProps<unknown, Params> = async ({ params: { country, locale }, }) => { // Get dictionary const dictionary = await api.dictionaries.fetch(locale) return { props: { country, dictionary, locale, }, revalidate: false, } }

页面内容渲染

组件接收countrylocaledictionary三个 props,渲染内容包括:字典驱动的标题与副标题、/flags/${country.toLowerCase()}.svg动态加载的国家旗帜,以及展示当前locale值的调试信息块(便于验证中间件的语言推断结果)。布局通过CountryPage.Layout = Layout挂载@vercel/examples-ui提供的页面外壳。

在 pages/_app.tsx 中,应用级布局会读取pageProps.dictionary动态设置页面标题与描述,使浏览器标签页也随语言变化。

目录结构与运行方式

关键文件一览

文件职责
middleware.ts边缘层 i18n 决策:解析 locale/country 并 rewrite 到本地化路径
lib/constants.ts多语言字典常量(含 default 兜底)
lib/types.ts字典类型定义
lib/api.ts字典获取接口封装
pages/[locale]/[country].tsx本地化落地页(SSG + blocking fallback)
pages/_app.tsx全局布局与动态标题
public/flags各国旗帜 SVG 资源
package.json依赖与脚本(next@vercel/functions等)

克隆与本地运行

仓库 README 提供了两种使用方式,其中本地开发推荐通过create-next-app以该模板为蓝本初始化项目:

pnpm create next-app --example https://github.com/vercel/examples/tree/main/edge-middleware/i18n i18n

随后进入项目目录启动开发服务器:

pnpm dev

需要说明的是:geolocation依赖 Vercel Edge Network 注入的地理位置请求头,本地开发时该请求头通常缺失,因此中间件会回退到默认值usaccept-language则来自本机浏览器/请求设置,可在本地直接验证语言切换效果。若需本地调试国家逻辑,可在中间件中临时注入测试请求头观察重写结果。

生产构建与启动命令定义在 package.json 中:

pnpm build # next build pnpm start # next start

云端部署

README 同时提供一键部署方式(点击 Vercel Deploy 按钮即可将模板部署到云端)。仓库根部的 vercel.json 声明了部署相关配置:

{ "buildCommand": "pnpm turbo build", "ignoreCommand": "pnpm dlx turbo-ignore" }

即云端构建使用 Turborepo 流水线(对应 turbo.json 中的build/lintpipeline),并通过turbo-ignore实现基于变更的构建跳过。部署到 Vercel 后,geolocation请求头即真实生效,不同地区的用户访问/会自动获得对应国家的本地化页面。

扩展思路与注意事项

  • 中间件仅匹配首页matcher: '/'限定了本示例只对根路径做 i18n 决策。若要让全站 URL 都携带 locale 前缀(如/es/about),需要扩展 matcher 规则并在中间件中处理相对路径拼接,避免破坏静态资源与 API 路由。
  • locale 归一化:示例直接使用accept-language的首个标签作为字典键,而浏览器可能发送zh-CNzh-Hans等复合标签;生产环境通常需要做"语言标签 → 支持语言"的归一化映射(如提取主语言码、配置别名表),否则会频繁落入default兜底。
  • 改写 vs 重定向:rewrite 对用户透明且无额外跳转,利于 SEO 与体验;若希望地址栏展示规范化的 locale 路径(便于分享链接),可改用NextResponse.redirect,但要注意避免重定向循环。
  • 静态生成与边缘决策的分工:本示例展示了"边缘层做动态路由决策、页面层保持纯静态"的经典组合,fallback: 'blocking'让任意国家组合都能按需生成并缓存,是这类模式落地时值得复用的关键配置。

总体而言,edge-middleware/i18n 以不足 20 行的中间件代码,完整演示了"请求头驱动 + 边缘重写 + 静态页面承接"的轻量 i18n 方案,是理解 Edge Middleware 在真实业务场景中价值的优秀参考实现。

【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询