我把一个小站从搬瓦工迁到了 Cloudflare Pages + 腾讯云轻量:一次降本迁移复盘

4 阅读7分钟

我把一个小站从搬瓦工迁到了 Cloudflare Pages + 腾讯云轻量:一次降本迁移复盘

最近给自己的一个小站做了一次部署迁移。

原来的架构很简单:前端静态页面、Node.js API、SQLite 数据库、Nginx 反代,全部跑在一台搬瓦工服务器上。优点是省心,线路稳定,所有东西都在一台机器里;缺点也很明显,成本有点高。我买的是稳定线路,一个月接近 89 美元。对一个刚上线、流量还不大的小项目来说,这个价格多少有点肉疼。

所以我最后把架构拆成了两部分:

  • 前端静态站:Cloudflare Pages
  • API 后端:腾讯云轻量服务器
  • 数据库:继续用 SQLite,放在后端服务器本地
  • 入口域名:主站走 Cloudflare,API 域名单独解析到腾讯云

这篇不是教程式的“复制命令即可成功”,更像一次迁移复盘。中间踩到的坑,比架构图本身更有价值。

为什么不继续全量放在一台 VPS 上

最开始我也犹豫过。

一台高质量 VPS 当然舒服,前后端、数据库、Nginx、证书、备份全在一个地方,出问题也好排查。但对小站来说,问题是资源浪费太明显。

我的实际情况是:

  • 前端主要是静态页面
  • 后端 API 请求量不高
  • SQLite 数据量很小
  • 真正需要稳定公网访问的是少数 API
  • 大部分静态流量完全没必要打到 VPS

这种情况下,把前端交给 Cloudflare Pages,后端只保留一台轻量服务器,成本和复杂度会更均衡。

目标架构

迁移后的结构大概是这样:

用户浏览器
  |
  | 访问 https://example.com
  v
Cloudflare Pages
  |
  | JS 直接请求 API
  v
https://api.example.com/api/*
  |
  v
腾讯云轻量服务器
  |
  v
Nginx -> 127.0.0.1:3000 Node.js -> SQLite

这里我没有把 API 也强行走 Cloudflare 代理,而是让 api.example.com 直接 DNS 解析到腾讯云。

原因是后端有 IP 限流、日志分析和风控需求。如果 API 域名走 Cloudflare 代理,需要额外处理真实 IP、可信代理链、限流 key 等问题。为了减少变量,我保留了 API 直连服务器的方式。

第一步:先做预发布,不急着切流量

这次迁移里我觉得最重要的一点是:不要一上来就切正式流量。

我先做了一个临时 API 域名,比如:

api-staging.example.com

然后 Cloudflare Pages 的预览环境使用:

API_BASE_URL=https://api-staging.example.com/api

这样可以先验证:

  • 页面能否正常加载
  • API 是否跨域成功
  • Nginx 反代是否正确
  • Node.js 服务是否只监听本机
  • SQLite 数据是否正常
  • 订单查询、提交、后台接口是否可用

等预览环境确认没问题,再考虑正式域名切换。这个顺序能省掉很多心理压力。

第二步:腾讯云轻量只跑 API

腾讯云轻量服务器上主要做几件事:

  1. 安装 Node.js、Nginx、SQLite、Certbot
  2. 拉取代码
  3. 写入 /etc/project/project.env
  4. systemd 托管 Node.js 服务
  5. Nginx 监听 80/443,对外提供 API
  6. Node.js 只监听 127.0.0.1:3000

这里有个细节很关键:Node 服务不要直接暴露公网端口。

正确的方式是:

公网 -> Nginx:443 -> 127.0.0.1:3000

这样真实入口只有 Nginx,限流、安全头、证书、日志都能集中处理。Node 只接受本机反代请求,风险小很多。

第三步:Cloudflare Pages 只负责静态前端

Cloudflare Pages 的构建配置比较简单:

Build command: npm run build:frontend Output directory: frontend/dist

前端构建时写入一个运行时配置文件,例如:

window.API_BASE_URL = "https://api.example.com/api";

然后业务 JS 从这个配置里读取 API 地址。

这里比把 API 写死在代码里更灵活,因为预览环境和生产环境可以用不同的 API 地址:

Preview: https://api-staging.example.com/api Production: https://api.example.com/api

第四步:CORS 和缓存是最大坑

这次迁移后,最容易出问题的不是服务启动,而是浏览器侧行为。

我遇到过两个典型问题。

第一个是 CORS 预检请求失败。

前端原来对 GET 请求也带了 Content-Type: application/json,这会让浏览器对某些跨域 GET 发起 preflight。如果中间刚好有重定向规则,浏览器会报:

Redirect is not allowed for a preflight request

修法是:GET/HEAD 请求不要无意义地带 JSON Content-Type。只有 POST/PUT 这类真的有 body 的请求才加。

第二个是静态资源缓存。

Cloudflare Pages 很适合长缓存静态文件,但如果用户浏览器缓存了旧版 JS,就可能继续请求旧 API 路径。我的处理方式是:

<script src="/config.js"></script> <script src="/scripts/api.js?v=28"></script>

同时让 /config.js 不缓存,业务 JS 通过版本号强制刷新。

第五步:数据迁移不要直接操作生产库

我用的是 SQLite,数据库不大,所以迁移本身很快。但越是小数据库,越容易因为手工命令掉以轻心。

最后我采用的方式是:

  1. 在原服务器复制生产数据到临时目录
  2. 对复制出来的数据打包
  3. 通过临时迁移分支传输
  4. 在新服务器备份当前数据
  5. 停新服务器服务
  6. 覆盖 SQLite 文件
  7. 启动服务并做健康检查
  8. 校验订单数量和最新订单时间

注意:原生产服务器不做删除、不覆盖、不停服。只从它那里复制数据。

这样即使迁移失败,原站仍然可回滚。

第六步:正式切流量

当前端预览、后端 API、数据迁移都验证完之后,再切正式流量。

我做了几件事:

  • Cloudflare Pages 的 Production branch 改成 master
  • 主域名 CNAME 到 Pages
  • API 域名 A 记录指向腾讯云
  • www 和备用域名统一 301 到主域名
  • 重新签发正式 API 证书
  • 验证 certbot 自动续期和 Nginx reload
  • 本地和外部都 curl 验证健康检查接口

最终切换后,访问路径变成:

https://example.com -> Cloudflare Pages https://api.example.com/api -> Tencent Cloud + Nginx + Node

迁移后还要补的东西

真正上线后才发现,迁移不是 DNS 生效就结束。

我后来补了这些:

  • 每日 SQLite 备份 cron
  • certbot 自动续期后的 Nginx reload hook
  • API 健康检查
  • Cloudflare 301 规则
  • 搜索引擎 sitemap 重新提交
  • 旧缓存用户的兼容处理
  • 订单查询限流策略调整

尤其是旧缓存用户这一点,很容易被忽略。用户浏览器里可能还留着旧 JS,导致请求旧路径或触发 CORS 问题。上线初期最好保留一段兼容规则,等缓存自然过期后再移除。

总结

这次迁移最大的感受是:小站不一定需要一台很贵的全能 VPS。

如果业务形态是静态页面 + 少量 API,那么:

  • 静态前端放 Cloudflare Pages
  • API 放一台轻量服务器
  • 数据库继续用 SQLite
  • Nginx 做边界
  • systemd 管进程
  • certbot 管证书

这个组合已经够用了。

它不是最“云原生”的架构,但对小项目来说很实用:成本低、链路清楚、出了问题也能 SSH 上去排查。

当然,这套方案也不是没有代价。前后端拆开以后,CORS、缓存、DNS、证书、重定向都会变成你要关心的东西。如果项目还没流量、还在验证阶段,这个复杂度可以接受;如果已经有大量用户,迁移前最好先做完整预发布和回滚方案。

我这次的结论是:先用简单架构跑通业务,等成本或流量真的变成问题,再做这种拆分。不要为了架构好看提前折腾,但一旦 VPS 成本明显不匹配业务规模,Cloudflare Pages + 轻量服务器确实是个挺香的中间态。