双语站只跑在一个 hosting 区域,意味着总有一边受众看到的是慢版本。解决办法是把每种语言放到自己的子域,指向离它最近的区域——en.yoursite.com 在美国,zh.yoursite.com 在香港或新加坡。配得好能给一半用户砍掉 200-400ms 的 TTFB,同时不拆 SEO 也不破坏共享资源。
问题背景
多数静态站托管(Vercel、Netlify、Cloudflare Pages)自动走全球 CDN。多数应用托管(Firebase、App Engine、fly.io、Render)只选一个源站区域。如果源站在 us-central1,中国用户拉 HTML,首字节要等 400ms,CDN 也救不了。按区域拆子域能把源站放到各自受众旁边。代价是多个活动部件:两套部署、两张 SSL 证书、两组 DNS 记录。
判断标准
- analytics 显示某种语言的 TTFB 超过 800ms,且这种语言的受众在源站的地球另一端。
- 同样内容出双语,但只有一个区域的用户抱怨速度。
- 已经在用语言前缀 URL(
/en/、/zh/),SEO 没问题但性能不平衡。 - hosting 账单显示大头流量出自某一个区域,另一个闲着。
快速结论
受众跨大洲的双语站,en.yoursite.com 指向美/欧区域,zh.yoursite.com 指向 HK/SG/东京。canonical 和 hreflang 配一致,SEO 上就是语言互译。共享构建产物。对源站较远那种语言的受众,200-400ms 延迟收益是实打实的。
DNS 和 hosting 布局
每个子域是各自区域的部署目标。DNS 记录长这样:
en.yoursite.com CNAME cname.vercel-dns.com # 美/欧区域
zh.yoursite.com CNAME cname.firebaseapp.com # asia-east1 区域
yoursite.com A 76.76.21.21 # 301 到默认语言
www.yoursite.com CNAME cname.vercel-dns.com # 镜像 en. 或跳走
裸域名和 www 通常根据 Accept-Language 301 到某个语言子域,或者跳到一个轻量语言选择页。不要让裸域和子域同时承载内容——会出现第三组 URL 和子域抢同一份内容。
跨子域的 canonical 和 hreflang
多数团队在这里出错。en.yoursite.com/articles/foo/ 上的每个页面声明自指 canonical,同时用 hreflang 指向 zh 版本:
<link rel="canonical" href="https://en.yoursite.com/articles/foo/">
<link rel="alternate" hreflang="en" href="https://en.yoursite.com/articles/foo/">
<link rel="alternate" hreflang="zh" href="https://zh.yoursite.com/articles/foo/">
<link rel="alternate" hreflang="x-default" href="https://en.yoursite.com/articles/foo/">
zh 页面也写同样的结构,自指 canonical 指向 zh.yoursite.com 的 URL,并把双向 hreflang 都写上。两个子域必须互相列对方的 URL——单向 hreflang Google 会忽略。
共享资源——挑一个 CDN 源
字体、图片、JS bundle 在两个子域都要用时,有两种模式:
- 每个子域各放一份。 简单,有重复,小 bundle 没问题。
- 共用一个资源子域(比如
cdn.yoursite.com)放资源,配好 CORS。en和zh都从cdn拉。两边共享缓存,代价是首次加载多一次 DNS。
总资源 500 KB 以下用方案 1;bundle 较大、有自定义字体,或者想让两边共享缓存就用方案 2。
验证延迟收益
整套配置工作量不小。庆祝之前先验证收益。用 WebPageTest 或 Pingdom 这类工具,从至少三个地点测每个子域——一个美国城市、一个中国城市、一个中性地点(新加坡是不错的中立点)。
关注 TTFB(源站响应时间)和总文档加载时间。如果 zh.yoursite.com 从上海测出来 TTFB 是 200ms,而原来美国单源站是 700ms,迁移就成功了。如果数字差不多,亚洲区域配错了——Firebase asia-east1 在台湾不在中国大陆,北京用户的路径可能仍然长。在宣告完工之前,换新加坡区域或 HK CDN 边缘再测一轮。
双区域部署流程
最干净的部署流程是单一来源、双目标交付。仓库里一次构建,用 regions.json(或每次部署的环境变量)选公开源站 URL 和最近的区域。
# CI:构建一次,分别部署到每个区域
npm run build
firebase deploy --only hosting:en --project en-us
firebase deploy --only hosting:zh --project zh-asia
要预案两种失败模式。第一,一个区域成功、另一个失败——CI 设成任一失败就中止并回滚,否则线上几小时是半部署状态。第二,两边都成功但时间不同步,落后那边用户看到旧内容。内容站还能接受,应用就难顶——应用要把两边钉到同一个 Git SHA 上,验证完再切 feature flag。
容易踩的坑
- 忘了给第二个子域配 SSL。站点能开但浏览器报不安全。
hreflang不对称——en列了zh但zh没列en。Google 会忽略这种单向配置。- 把两个语言子域都 canonical 到同一个(通常是
en)。Google 会把另一种语言从索引里踢掉。 - 跑两个 Search Console property 但只验证了一个——一半收录数据看不到。
- sitemap 只列了一个子域。每个子域要有自己的 sitemap,只列自己的 URL。
- cookie 或 session token 的 domain 设成单个子域而不是
.yoursite.com——在en登录的用户切到zh就被登出。
FAQ
- 不用子域用
/en/路径行吗?: 路径方案更简单(一个 Search Console property、一张 SSL 证书、一次部署)但不能给你区域级源站。延迟比简洁更重要就用子域。 - Google 会把子域当独立站点吗?: 一定程度上是。每个子域需要自己的 Search Console property,权重各自累积。互相内链有帮助,但绑定不如同域路径紧密。
- 用 ccTLD(
.cn、.com.cn)替代zh.怎么样?: ccTLD 是最强的地域信号,但要单独的域名所有权、中国 ICP 备案,成本翻倍。多数独立开发者起步用子域更便宜。 - 同一套 hosting 能给每种语言配不同区域吗?: Firebase 上:部署到两个不同的 Hosting site,每个有自己的 functions 区域。Vercel 上:部署两个项目,各自指向各自的子域。
- 每个子域要单独的 analytics property 吗?: 同一个 property,但要配跨子域追踪,让用户从
en.切到zh.时还在同一个 session。
相关阅读
标签: #独立开发 #域名 #DNS #multi-region #双语