
用 Cloudflare 免费套餐搭一个 AI 时代的个人博客
“Workers + D1 + R2 + Hono,服务端渲染、TDD 开发,零成本上线。”
这个博客本身就是一次「和 AI 助手一起做项目」的练习:需求用中文描述,AI 助手写代码、跑测试、部署,我负责提需求和验收。下面记录它的技术选型和实现要点。
为什么选 Cloudflare 免费套餐
个人博客的流量很小,最怕的是维护成本。Cloudflare 免费套餐把需要的东西都包了:
| 组件 | 作用 | 免费额度(大致) |
|---|---|---|
| Workers | 运行代码、渲染页面 | 每天 10 万次请求 |
| D1 | SQLite 数据库,存文章/项目/关于 | 每天 500 万行读 |
| R2 | 对象存储,存图片 | 10 GB,出站流量免费 |
不需要服务器,也不需要自己续 HTTPS 证书,先用免费的 *.workers.dev 域名,以后再绑定自己的域名。
架构
整个站点是一个 Worker:
- Hono 负责路由,页面全部在服务端渲染成 HTML;
- D1 存内容,Markdown 在保存时就渲染成 HTML,读文章只查一次库;
- R2 存后台上传的图片,
/images/...带一年的immutable缓存; - 后台
/admin用一个 Worker Secret 作为口令,登录态是 HMAC 签名的 Cookie。
// wrangler.jsonc(节选)
{
"name": "pf-blog",
"main": "src/index.ts",
"d1_databases": [{ "binding": "DB", "database_name": "pf-blog-db" }],
"r2_buckets": [{ "binding": "BUCKET", "bucket_name": "pf-blog-media" }]
}
快和轻
- 前台没有框架运行时,唯一的脚本是不到 1 KB 的深浅色切换;
- CSS 内联,配合 ETag,重复访问直接 304;
- CSP 只允许同源脚本,Markdown 里的原始 HTML 一律转义。
搜索
数据量不大,搜索直接用 D1 的 LIKE 查标题、摘要和正文,注意把用户输入里的 % 和 _ 转义掉:
const pattern = "%" + q.replace(/[\\%_]/g, (c) => "\\" + c) + "%";
db.prepare(
"SELECT slug, title FROM posts WHERE status='published' AND title LIKE ?1 ESCAPE '\\'",
).bind(pattern);
用 TDD 约束 AI 写代码
让 AI 写代码,最大的风险是「看起来能跑」。所以每次改动都先写失败的测试,再让实现通过:
- 用
@cloudflare/vitest-plugin在真实的 workerd 运行时里跑测试,D1/R2 都是本地模拟; - 测试覆盖页面、草稿不可见、登录、CSRF、图片上传限制、搜索转义等;
- 每次部署前跑一遍测试和
tsc --noEmit。
it("draft posts are 404 publicly", async () => {
const res = await get(`/posts/${draftSlug}`);
expect(res.status).toBe(404);
});
测试也是给 AI 的「验收标准」:需求说不清的地方,用测试说清楚。
部署
部署脚本把一次性的事情都做了:创建 D1、创建 R2 桶、执行迁移、发布。R2 第一次使用需要先在控制台开通;没开通时脚本会先不绑定 R2 部署,开通后再跑一次即可。
npx wrangler d1 migrations apply pf-blog-db --remote
npx wrangler deploy
样式
界面参考了开源主题 astro-theme-pure(Apache-2.0),在 Hono 模板里用手写 CSS 重新实现,只保留文章、项目、关于和搜索。
用 Cloudflare 免费套餐搭一个 AI 时代的个人博客https://pfzhou.xyz/posts/cloudflare-ai-blog
