为什么我把博客从云服务器搬到了 Astro + Cloudflare

一台 2C2G 的云主机每年要花掉几百块,只为了跑一个访问量两位数的博客。于是我把它拆成了静态优先的 Astro + 边缘函数 + 托管数据库。

起因:一台闲着也费钱的服务器

这个博客原来的架构非常土:一台 2 核 2G 的云主机,跑着 Nginx + Docker 里的 Node 进程 + 一个自建的 SQLite 文件。功能上没什么问题,但它有三个让我越来越难受的地方:

  1. 成本与流量脱钩。一年几百块的服务器费用,访问量常年两位数。
  2. 运维是纯负债。系统要打补丁、证书要续期、磁盘满了要清日志。每次收到告警我都要想一下「这个博客到底值不值得我花时间」。
  3. 冷启动很尴尬。Node 进程偶尔被 OOM 掉,第一个访问的人要等好几秒。

搬家的核心思路只有一句话:把「必须常驻」的部分干掉。

新架构:三层拆解

拆完之后是这样:

层 原来 现在
页面渲染 常驻 Node 进程 Cloudflare Pages Functions(按需执行,毫秒级冷启动)
数据 自建 SQLite 文件 Cloudflare D1(托管 SQLite,按行读写计费)
文件 服务器本地磁盘 + Nginx 直接返回 Cloudflare R2(对象存储,出网流量免费)

关键收益是 没有常驻进程。没人访问的时候,整个系统就是零计算成本,只有 D1 里的几 KB 数据和 R2 里的几十 MB 图片在计费。

为什么是 Astro 而不是纯静态生成器

很多人第一反应是「博客嘛,Hexo / Hugo 静态生成就够了,干嘛还上 SSR」。

静态生成确实是最省的方案,但它解决不了我的三个硬需求:

  • 文章要能在后台写完立刻发布,而不是本地 hexo new + git push 等 CI。
  • 要能存草稿,而且草稿不能让搜索引擎和访客看到。
  • 要能登录,后台得有个密码门。

这三件事都需要服务端。而 Astro 的好处是它同时给你两种模式:

src/pages/index.astro        → 默认 SSR(要读 D1)
src/pages/about.astro        → 加一行 export const prerender = true 就变成静态

也就是说,我可以在同一个项目里,把「关于页」这种几乎不变的内容静态化,把「文章列表」这种要实时读库的保持动态。这个粒度控制是我最后选它的决定性原因。

代码长什么样

在 Astro 里读取 D1 绑定,语法非常朴素——绑定挂在 locals.runtime.env 上:

const { env } = Astro.locals.runtime;

const { results } = await env.DB
  .prepare('SELECT id, slug, title, summary, published_at FROM posts WHERE status = ? ORDER BY published_at DESC LIMIT ?')
  .bind('published', 10)
  .all();

env.DB 就是 wrangler.toml 里声明的那行绑定:

[[d1_databases]]
binding = "DB"
database_name = "blog-db"
database_id = "你的-database-id"

开发的时候,Astro 的 Cloudflare 适配器会通过 platformProxy 起一个本地 miniflare 环境,所以 npm run dev 读的是一份本地 SQLite 文件,跑 wrangler d1 execute --remote 才会打到线上——不用为了调试去污染生产数据。

搬家之后最爽的三件事

第一,发布变成了一件没有心理负担的事。 以前改一个错别字要经历「登录服务器 → 改文件 → reload」,现在打开 /admin 写完点保存就完了。

第二,成本曲线被压平了。 免费额度覆盖了我全部的使用量:D1 每天 500 万行读、R2 每月 1000 万次 A 类操作、Pages 每月 500 次构建。

第三,不再有「服务器在跑但没人访问」的荒诞感。

代价

当然不是白拿的,也有代价:

  • 计时精度受限于边缘环境。Worker 有 CPU 时间限制(免费版每次请求 10ms CPU),所以别指望在请求里做重量级的数据处理。
  • 没有常驻内存缓存。跨请求的状态必须放 D1 / KV / R2。
  • 本地和生产环境有差异。astro dev 的 miniflare 和真实的 Worker 运行时偶尔会在细节上不一致,上线前最好 wrangler pages dev ./dist 跑一遍。

但对我这个规模的博客来说,这些限制一个都碰不到。选型的关键从来不是「最强的方案」,而是「最不容易在半年后变成负担的方案」。

这篇之后我会陆续把 D1、R2、边缘环境下的登录认证分别写一遍,作为搬家过程的完整记录。

← 回到文章列表