CDN 地理路由配置与 Edge 语言检测
核心问题:如何用 CDN 加速多语言网站?如何在 Edge 层根据用户位置自动跳转到对应语言版本?
真实场景
你的 Next.js 应用部署在美国,中国用户访问速度慢(RTT 200ms+),还有语言检测需要。通过 Cloudflare 或 CloudFront 的地理路由,可以在最近的 Edge 节点响应请求,并自动根据用户 IP 所在国家重定向到对应语言版本。
CDN 在多语言场景的作用
graph TB
User1["🇨🇳 中国用户"] --> Edge1["Cloudflare\n香港 PoP"]
User2["🇺🇸 美国用户"] --> Edge2["Cloudflare\n纽约 PoP"]
User3["🇩🇪 德国用户"] --> Edge3["Cloudflare\n法兰克福 PoP"]
Edge1 --> |"检测到 CN → zh-CN"| Origin["源站\n纽约"]
Edge2 --> |"检测到 US → en-US"| Origin
Edge3 --> |"检测到 DE → de-DE"| Origin
Edge1 --> Cache1["缓存\nzh-CN 内容"]
Edge2 --> Cache2["缓存\nen-US 内容"]
Cloudflare Workers(Edge Function)
Cloudflare Workers 运行在全球 200+ 边缘节点,可以在 Edge 层处理语言检测和重定向。
语言检测 Worker
// workers/i18n-router.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// 已有语言前缀,直接透传
const SUPPORTED_LOCALES = ['zh-CN', 'en-US', 'de-DE', 'ja-JP', 'pt-BR'];
const firstSegment = url.pathname.split('/')[1];
if (SUPPORTED_LOCALES.includes(firstSegment)) {
return fetch(request);
}
// 语言检测优先级
const locale = detectLocale(request);
// 重定向到对应语言版本(仅重定向首次访问的根路径)
if (url.pathname === '/' || url.pathname === '') {
const targetUrl = new URL(url);
targetUrl.pathname = `/${locale}${url.pathname === '/' ? '' : url.pathname}`;
return Response.redirect(targetUrl.toString(), 302);
}
return fetch(request);
},
};
function detectLocale(request: Request): string {
const SUPPORTED_LOCALES = ['zh-CN', 'en-US', 'de-DE', 'ja-JP', 'pt-BR'];
const DEFAULT_LOCALE = 'zh-CN';
// 1. Cookie 中保存的用户偏好(最高优先级)
const cookies = request.headers.get('Cookie') ?? '';
const cookieLocale = cookies.match(/locale=([^;]+)/)?.[1];
if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale)) {
return cookieLocale;
}
// 2. Cloudflare 提供的 CF-IPCountry 头(地理位置)
const country = request.headers.get('CF-IPCountry') ?? '';
const countryToLocale: Record<string, string> = {
CN: 'zh-CN', TW: 'zh-TW', HK: 'zh-HK',
US: 'en-US', CA: 'en-US', AU: 'en-US', GB: 'en-US',
DE: 'de-DE', AT: 'de-DE', CH: 'de-DE',
JP: 'ja-JP',
BR: 'pt-BR', PT: 'pt-PT',
SA: 'ar-SA', AE: 'ar-SA', EG: 'ar-SA',
};
const countryLocale = countryToLocale[country];
if (countryLocale && SUPPORTED_LOCALES.includes(countryLocale)) {
return countryLocale;
}
// 3. Accept-Language 头
const acceptLanguage = request.headers.get('Accept-Language') ?? '';
const parsedLocale = parseAcceptLanguage(acceptLanguage, SUPPORTED_LOCALES);
if (parsedLocale) return parsedLocale;
return DEFAULT_LOCALE;
}
function parseAcceptLanguage(header: string, supported: string[]): string | null {
const parts = header.split(',').map(part => {
const [lang, q] = part.trim().split(';q=');
return { lang: lang.trim(), q: parseFloat(q ?? '1') };
}).sort((a, b) => b.q - a.q);
for (const { lang } of parts) {
if (supported.includes(lang)) return lang;
const prefix = lang.split('-')[0];
const match = supported.find(s => s.startsWith(prefix + '-'));
if (match) return match;
}
return null;
}
Cloudflare Workers 配置
# wrangler.toml
name = "i18n-router"
main = "src/workers/i18n-router.ts"
compatibility_date = "2024-01-01"
[build]
command = "npm run build:worker"
[[routes]]
pattern = "example.com/*"
zone_name = "example.com"
# 部署
wrangler deploy
# 本地测试
wrangler dev
Cloudflare Pages + Edge 语言检测
对于 Next.js 应用部署到 Cloudflare Pages:
// next.config.ts
const nextConfig = {
// 启用 Edge Runtime(支持 Cloudflare Workers)
experimental: {
runtime: 'edge',
},
};
// src/middleware.ts(Next.js 中间件运行在 Edge)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 获取 Cloudflare 地理信息
// Cloudflare 会注入 request.geo 或 CF-IPCountry 头
const country = request.headers.get('CF-IPCountry') ??
request.geo?.country ?? '';
// 检查是否已有语言前缀
const locales = ['zh-CN', 'en-US', 'de-DE', 'ja-JP'];
const hasLocale = locales.some(l => pathname.startsWith(`/${l}`));
if (!hasLocale && pathname === '/') {
// 地理位置 → locale 映射
const geoLocaleMap: Record<string, string> = {
CN: 'zh-CN', JP: 'ja-JP', DE: 'de-DE',
US: 'en-US', GB: 'en-US',
};
const locale = geoLocaleMap[country] ?? 'zh-CN';
return NextResponse.redirect(new URL(`/${locale}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next|favicon.ico).*)'],
};
AWS CloudFront + Lambda@Edge
// lambda-edge/origin-request.js
// 在 Origin Request 阶段根据地理位置重写 URL
'use strict';
const COUNTRY_TO_LOCALE = {
CN: 'zh-CN', TW: 'zh-TW', HK: 'zh-HK',
US: 'en-US', CA: 'en-US', GB: 'en-US', AU: 'en-US',
DE: 'de-DE', AT: 'de-DE',
JP: 'ja-JP',
BR: 'pt-BR',
};
const SUPPORTED_LOCALES = new Set([
'zh-CN', 'zh-TW', 'zh-HK', 'en-US', 'de-DE', 'ja-JP', 'pt-BR'
]);
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
// CloudFront 注入的地理位置头
const countryCode = headers['cloudfront-viewer-country']?.[0]?.value ?? 'US';
// 检查请求路径是否已有语言前缀
const pathParts = request.uri.split('/').filter(Boolean);
if (pathParts.length > 0 && SUPPORTED_LOCALES.has(pathParts[0])) {
return request; // 已有语言前缀,不处理
}
// 从 Cookie 获取用户偏好
const cookies = headers['cookie']?.[0]?.value ?? '';
const cookieLocale = cookies.match(/locale=([^;]+)/)?.[1];
if (cookieLocale && SUPPORTED_LOCALES.has(cookieLocale)) {
request.uri = `/${cookieLocale}${request.uri}`;
return request;
}
// 根据国家选择 locale
const locale = COUNTRY_TO_LOCALE[countryCode] ?? 'en-US';
request.uri = `/${locale}${request.uri === '/' ? '' : request.uri}`;
return request;
};
# CloudFront 配置(AWS CDK 或 Terraform)
# 将 Lambda@Edge 关联到 CloudFront Distribution
resource "aws_cloudfront_distribution" "main" {
# ...
default_cache_behavior {
# ...
lambda_function_association {
event_type = "origin-request"
lambda_arn = aws_lambda_function.origin_request.qualified_arn
}
}
}
CDN 缓存策略
多语言网站的缓存需要按语言分开,否则中文用户会看到英文缓存。
Cache Key 设计
// Cloudflare Cache Rules 配置
// 按 locale 路径分别缓存(路径已包含语言,无需额外设置)
// example.com/zh-CN/products 和 example.com/en-US/products 是不同 URL,自动分开缓存
// 但如果 URL 相同、只用 Accept-Language 区分,需要设置 Vary
// 或使用 Cloudflare Cache Rules 根据 Cookie 变化缓存
# Nginx 按语言分别缓存
proxy_cache_key "$scheme$request_method$host$request_uri";
# 如果使用 Accept-Language 协商(不推荐),需要加入 key
proxy_cache_key "$scheme$request_method$host$request_uri$http_accept_language";
# 更好的做法:URL 中包含语言,缓存 key 自然区分
静态资源多语言分离
// 翻译文件的 CDN 缓存
// URL 包含内容哈希,实现长期缓存 + 自动失效
// locales/zh-CN.abc123.json → Cache-Control: max-age=31536000 immutable
// locales/en-US.def456.json → Cache-Control: max-age=31536000 immutable
// Vite/Webpack 输出(已有哈希):
// dist/locales/zh-CN-abc123.json
// dist/locales/en-US-def456.json
// 非翻译文件(按业务场景):
// Cache-Control: public, max-age=300, stale-while-revalidate=60
// Vary: Accept-Language (如果内容按语言不同)
性能监控:多语言 Core Web Vitals
// 按语言分别追踪 Web Vitals
import { onCLS, onFID, onLCP } from 'web-vitals';
const locale = document.documentElement.lang;
function sendToAnalytics({ name, value, id }) {
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
metric: name,
value,
id,
locale, // 按语言维度分析
page: location.pathname,
}),
});
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
// 监控重点:
// - 翻译文件加载时间(各语言的 TTFB 差异)
// - RTL 页面的 CLS(布局稳定性)
// - 字体加载(CJK 字体通常更大)
完整的多语言部署架构总结
graph TB
subgraph Users["用户层"]
U1["🇨🇳 中国用户"]
U2["🇺🇸 美国用户"]
U3["🇩🇪 德国用户"]
end
subgraph CDN["CDN 层(Cloudflare)"]
E1["香港 Edge\n语言检测→zh-CN"]
E2["纽约 Edge\n语言检测→en-US"]
E3["法兰克福 Edge\n语言检测→de-DE"]
Cache["翻译文件缓存\n按 locale 分开"]
end
subgraph App["应用层"]
Next["Next.js\nApp Router\n[locale] 动态路由"]
API["API\nAccept-Language\n语言协商"]
DB["数据库\n多语言内容表"]
end
subgraph SEO["SEO 层"]
Sitemap["多语言 Sitemap\nhreflang 声明"]
SC["Google Search Console\n国际化定向报告"]
end
U1 --> E1
U2 --> E2
U3 --> E3
E1 --> Next
E2 --> Next
E3 --> Next
E1 --> Cache
E2 --> Cache
E3 --> Cache
Next --> API
API --> DB
Next --> Sitemap
Sitemap --> SC
全书技能总结
| 章节 | 核心技能 |
|---|---|
| Ch01 | Unicode/UTF-8 编码,BCP 47 语言标签,locale 敏感行为 |
| Ch02 | next-intl/next-i18next/vue-i18n 配置,翻译懒加载 |
| Ch03 | Accept-Language 协商,多语言数据库设计,邮件模板 i18n |
| Ch04 | Intl API,ICU 消息格式/复数规则,货币/时区处理 |
| Ch05 | JSON/PO/XLIFF 格式,namespace 规划,CI 自动检测 |
| Ch06 | 人工翻译工作流,DeepL/Google MT 集成,TMS 平台 |
| Ch07 | CSS 逻辑属性,RTL 布局,图标镜像,文化适配 |
| Ch08 | 域名策略,hreflang 标签,CDN 地理路由 |
下一步
完成本书后,推荐继续阅读:
seo-guide:深化多语言关键词研究、多区域 SEO 策略、本地化内链建设dns-guide:多区域域名 DNS 配置、CDN 更深入的配置(WAF、DDoS 防护)devops-guide:容器化多语言应用的部署(Docker + Kubernetes 多区域集群)
掌握了 i18n 工程,你的产品就可以真正服务全球用户,而不只是翻译了一遍文字。