我把一个小站从搬瓦工迁到了 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
腾讯云轻量服务器上主要做几件事:
- 安装 Node.js、Nginx、SQLite、Certbot
- 拉取代码
- 写入 /etc/project/project.env
- systemd 托管 Node.js 服务
- Nginx 监听 80/443,对外提供 API
- 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,数据库不大,所以迁移本身很快。但越是小数据库,越容易因为手工命令掉以轻心。
最后我采用的方式是:
- 在原服务器复制生产数据到临时目录
- 对复制出来的数据打包
- 通过临时迁移分支传输
- 在新服务器备份当前数据
- 停新服务器服务
- 覆盖 SQLite 文件
- 启动服务并做健康检查
- 校验订单数量和最新订单时间
注意:原生产服务器不做删除、不覆盖、不停服。只从它那里复制数据。
这样即使迁移失败,原站仍然可回滚。
第六步:正式切流量
当前端预览、后端 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 + 轻量服务器确实是个挺香的中间态。