← 学习路线

托管平台与"一个站两处部署"

一个网站该放在哪、放几处?"顺手多部署一份"会带来什么问题?

课型:权衡·约 20 分钟·5 个术语

先看出事的那一刻

7 月 28 日,自动驾驶知识站的正式域名刚接通不久。用 curl 分别请求两个地址:后端所在的 Railway 默认域名上,页面标题是新的"E2E REVIEW · 智能驾驶技术站";正式域名上,还是旧的"端到端自动驾驶术语图鉴"。同一个网站,两个版本同时在线。

原因不复杂。前端放在 Cloudflare Pages,由它接住正式域名;后端的镜像里也打包了一整套前端,只要构建产物存在,就自动对外提供。两边各自部署、各自更新,于是分叉了。

这节课要回答的是:一个网站该放在哪、放几处?"顺手多部署一份"会带来什么问题?

装备几个词

为什么要懂

AI 替你做了

接一个托管平台只要点几下授权,AI 还会顺手让后端“也把前端托管起来”。看上去,多一份备份总没坏处。

留给你的

决定每个网站只有一个生产入口,其余的部署要么断开,要么明确只是预览。

不懂的代价

好几个版本同时在线,迟早分叉;哪个才是生产,连做它的 AI 都说错过,排查时总在错的那一份上改。

两类平台,各管一段

静态托管应用托管
代表Cloudflare Pages、VercelRailway、Render
放什么构建好的页面和文件一直在线的后端程序、数据库
怎么收费大多有很宽的免费额度按运行时长和资源计费
要不要维护几乎不用要看日志、管重启、做升级
在这里的用法个人网站、E2E 的前端E2E 的后端和数据库

一个常见的组合是前端放静态托管、后端放应用托管,中间用转发把它们接到同一个域名下(第 3 课)。这个组合本身没问题,问题出在"顺手":后端镜像里带上了前端,于是应用托管那边也多出了一个完整的网站。

一个网站,只有一个生产入口

把这两个项目所有能打开的地址列出来,是这样的:

地址是什么现在的状态
www.felixwithai.com个人网站的生产入口正常
space.felixwithai.com个人网站的旧入口301 到 www,保留路径与查询参数
web-6eg.pages.dev同一次构建的平台默认地址能打开,页面的 canonical 指向正式域名
分支名.web-6eg.pages.dev分支预览能打开,平台会标记为不收录
Vercel 上的部署每次推送多构建的一份需要登录才能看,但每次推送照样构建
e2e.felixwithai.comE2E 原来的正式域名整站 301 到 /e2e
Railway 的默认域名后端顺手托管的前端仍能打开,停在 7 月的版本

每一行都要能回答:它是生产、预览,还是该删掉?答不上来的那一行,迟早会和生产分叉,或者在某次排查时把人带偏。

判断哪个才是生产,看域名指向谁(第 1 课);而不是看哪个平台发来了"部署成功"。

深潜canonical:告诉搜索引擎哪一份是正本+

同一份内容在好几个地址上都能打开时,页面头部的 <link rel="canonical"> 告诉搜索引擎"以这个地址为准",其余地址的权重会归到它身上。

写这节课时才发现,这个网站的 canonical 一直带着 .html 后缀,比如 /blog/loop-engineering.html,而这个地址在线上会被 308 跳转到不带后缀的版本:正本指向了一个跳转。原因是构建时每页生成一个 .html 文件,页面拿到的路径也带着后缀。现在已经改成和线上地址一致。

找茬

下面是两个项目里和部署入口有关的真实代码与配置(节选)。个人网站的生产环境在 Cloudflare Pages;E2E 的正式域名由 Cloudflare Pages 接住,后端在 Railway。找出制造了多余入口、或者在为错误的平台工作的地方。

这段 JS / Dockerfile / JSON 里埋了 5 处问题。点击你觉得有问题的行,至少找出 3 处再揭晓。

查看代码与答案(5 处问题)
 1  // server/index.mjs(E2E 后端,节选)
 2  if (existsSync(WEB_DIST)) {
 3    app.use("/assets/*", serveStatic({ root: "./web/dist" }));
 4    app.get("*", serveStatic({ path: "./web/dist/index.html" }));
 5  
 6  # Dockerfile(E2E 后端镜像,节选)
 7  COPY --from=web /build/dist ./web/dist
 8  
 9  // web/public/_worker.js(前端转发脚本,节选)
10  const ORIGIN = "https://e2e-academy-production.up.railway.app";
11  
12  # 个人网站的 vercel.json(迁移时改写)
13  { "cleanUrls": true, "trailingSlash": false }
  • 第 2 行 · 中危 · 挂不挂前端,看构建产物在不在:这一行的本意是“构建过前端,就顺便托管”。可镜像里永远有构建产物,于是后端永远在对外提供一整套前端。没有任何人有意做过这个决定。 该问的话:后端为什么要托管前端?这个行为是在哪里、由谁决定打开的?
  • 第 4 行 · 低危 · 多出来的入口,对任何地址都回 200:后端的默认域名上,不存在的地址、robots.txt、sitemap.xml 全都返回首页和 200。这个入口一旦被搜索引擎发现,会被当成一整个网站收录。 该问的话:后端的默认域名上,访问一个不存在的地址会返回什么?
  • 第 7 行 · 中危 · 说是只管接口的镜像,装着整套前端:镜像构建时把前端也打包了进去。AI 后来还写过“railway up 只管 API”,可部署上去的一直是前端加后端,版本还停在 7 月。 该问的话:这个镜像里到底装了什么?部署上去的前端是哪个版本?
  • 第 10 行 · 中危 · 转发的上游,自己也是一个完整的公开网站:转发脚本把 /api/ 交给这个地址,而这个地址自己也在对外提供整个网站。两边各自部署、各自更新,7 月 28 日就真的分叉了:一边新标题,一边旧标题。 该问的话:除了正式域名,还有哪些地址能打开这个网站?它们是同一个版本吗?
  • 第 13 行 · 低危 · 在给一个不是生产的平台写配置:迁移时 AI 认真改写了 vercel.json,可生产环境是 Cloudflare Pages,根本不读它。Vercel 的连接一直没断,每次推送照样构建一份没人访问的副本。 该问的话:这个配置文件是给哪个平台的?那个平台现在还在用吗?

快测

1. 你的静态博客,应该放在哪类平台上?

查看选项与答案
  • A. 静态托管:页面构建好就不变,直接送出,几乎不用维护(正确)——构建好的页面和文件放静态托管,大多有很宽的免费额度,几乎不用维护;要一直在线的后端和数据库,才放应用托管。
  • B. 应用托管:博客也需要一个一直在线的程序来响应每一次访问——静态网站不需要一直运行的程序。放在应用托管上,要按运行时长付费,还得自己看日志、管重启、做升级。
  • C. 静态托管和应用托管各放一份,一边出问题就切换——两份就会分叉,这正是这节课的事故:两边各自部署、各自更新,同一个网站同时跑着两个版本。

按网站的性质选平台:要一直运行的放应用托管,构建好就不变的放静态托管。

2. 同一个网站在两个地址都能打开,内容一样。对搜索引擎来说,最重要的是什么?

查看选项与答案
  • A. 用 canonical 或 301,让其余地址指向正式的那一个(正确)——告诉搜索引擎以哪个地址为准,其余地址的权重会归到它身上。
  • B. 把两个地址都提交给搜索引擎,让它们各自积累权重和排名——会被当成重复内容,权重被分散在两个地址上。
  • C. 给每个地址各设一个指向自己的 canonical,各自收录——两个地址各指各的,就等于两边都自称正本,搜索引擎还是不知道以哪个为准,权重照样被分散。

多个地址时,要明确说出哪一个是正本。

3. 推送之后,你收到一条平台发来的“部署成功”。怎么确认线上真的更新了?

查看选项与答案
  • A. 打开正式域名,页面能正常显示、没有报错就说明更新了——正式域名也可能还在提供旧版本,它照样能正常打开,只是内容还是旧的。要看内容是不是新的,不能只看能不能打开。
  • B. 打开正式域名,对照标题和内容,确认已经是新版本(正确)——线上以域名指向的那个平台为准,看它实际返回了什么。
  • C. 打开发通知的平台给的默认地址,确认内容已经是新版本——发通知的平台未必是生产。这次事故里,Railway 的默认域名上是新标题,正式域名上却还是旧的。

部署成功不等于线上更新:去正式域名上确认。

判断时刻

个人网站的仓库同时连着 Vercel 和 Cloudflare Pages,每次推送两边各部署一份;域名指向 Cloudflare。AI 给了三个方案。

你会选哪一个?

三个选项各自的代价
  • A. 保持现状,多一份当备份——考察备份还是负担:什么都不用做。代价是每次推送都多一份构建、多一条可能误导人的“部署成功”。这个网站迁移时,AI 就是照着 Vercel 写的部署说明,差点只看它的结果就合并了。
  • B. 断开 Vercel,只留 Cloudflare Pages——考察一个生产入口:几分钟的后台操作,从此只有一个平台、一种通知、一份配置。唯一的代价是:将来真要换平台时,得重新接一次。
  • C. 把域名改指向 Vercel,断开 Cloudflare——考察换平台的成本:同样只剩一个入口,但要迁移构建配置、重新验证所有网址的跳转和 404、处理证书,工作量最大;而原来的平台并没有出问题,换的理由并不充分。

多一个入口,不是多一份保险,而是多一个会分叉、会误导人的版本。每个入口都要有明确的身份:生产、预览,或者该删掉。

带走

下次让 AI 做这件事时,问它

  1. 这个网站现在能从哪些地址打开?逐个列出:哪个是生产、哪个是预览、哪个该断开。
  2. 这个仓库连着哪些托管平台?每次推送会触发几个部署?
  3. 后端除了提供接口,是不是也在托管页面?这是有意的吗?
  4. 同一份内容有多个地址时,有没有用 canonical 或 301 指明哪一个是正本?

自己验证

  • 对每个入口执行 curl -sI,看状态码、server 响应头和页面标题是否一致。
  • 在代码仓库的提交记录上看检查结果,数一数每次推送触发了几个平台。
  • 查看页面源代码里的 <link rel="canonical">,确认它指向正式域名,而且不会再被跳转。

每个入口都要有明确的身份:生产、预览,或者该删掉。