TL;DR:搭个人博客,调研了 7 个框架,最终选 Astro + Cloudflare Tunnel + Docker 双环境部署,配合 Telegram Bot 审批流,写草稿→点按钮→5 分钟自动上线。本文记录完整的决策过程和踩过的坑。
为什么要自己搭
我已经有一个 Obsidian 知识库(brain vault)和 x.com 账号,但缺少一个「我的地盘」——能写长文、不受平台限制、完全控制命运的个人网站。
几个约束决定了架构方向:
- 服务器是 Mac Mini,放在家里,没有公网 IP
- 不需要买 VPS,零运维开销是底线
- Agent 友好,以后 Archie 能直接帮我写文章、发文章
- 速度不能慢,国内虽然不用看了,但海外访问要快
翻译成人话:得用 Cloudflare 隧道 + 静态网站 + Agent 自动化。
选型:7 个框架,一个答案
没急着搭,先拉了个 Excel。7 个框架,每个写了个 hello world 感受手感:
| 框架 | 语言 | 构建速度 | 生态 | 模板体验 | 综合评价 |
|---|---|---|---|---|---|
| Hugo | Go | ⚡ 10ms | ⭐⭐⭐⭐⭐ | Go template 语法丑,if else 套三层想死 | 快但折磨 |
| Astro | JS | 🟡 200ms | ⭐⭐⭐⭐ | 写 JSX/TSX/HTML 都行,灵活 | 生态好,MCP |
| Zola | Rust | ⚡ 5ms | ⭐⭐⭐ | Tera 模板简洁,但插件太少 | 小而美但局限 |
| 11ty | JS | 🟡 300ms | ⭐⭐⭐ | 模板自由度高,但文档不够好 | 灵活但小众 |
| Jekyll | Ruby | 🐢 2s+ | ⭐⭐ | Liquid 模板还行 | 老了,Gem 麻烦 |
| Hexo | JS | 🟡 500ms | ⭐⭐ | EJS 很普通 | Node 老牌,不再活跃 |
| VitePress | JS | 🟡 200ms | ⭐⭐⭐⭐ | Vue 生态,文档站首选 | 做文档好,做博客一般 |
最终选了 Astro。决定性因素是两个:
-
JS 生态 + MCP Server:Agent 开发是我现在的主方向,Astro 官方提供 MCP Server,模型可以直接查最新文档写代码。以后让 Archie 改主题、加组件、写文章,不需要培训一个新框架。
-
Islands 架构:默认零 JS,只有需要交互的部分才加载。对内容驱动的博客来说是天然优势——访问者看到的 99% 都是纯 HTML,加载速度取决于 CDN,不取决于框架。
Zola 很想选——Rust 写的,快且干净,Tera 模板也写得舒服。但生态实在是太小了,GitHub 上 200 多个主题,大部分是 3 年前的。做博客没问题,做 Agent 自动化就捉襟见肘了。
主题:Astro Paper
选了 astro-paper,Astro 生态里最流行的博客主题(4.8K stars),Bear Blog 风格,极简。
装上之后踩了三个坑:
坑 1:Vite v8 + Tailwind v4
主题依赖 vite-tsconfig-paths 在 Vite v8 上 crash。锁到 Vite v7.3.3 解决。
教训:JS 生态更新快不是好事。稳定 > 新功能。
坑 2:Google Fonts
主题默认引用了 Google Fonts CSS,国内直接超时。删掉就好了,用系统字体 stack:system-ui, -apple-system, sans-serif,反而更快。
坑 3:OG Image 生成
自带的 OG 图片生成依赖 satori,需要加载字体文件。关掉,用 <meta og:image> 静态指定就行了。少一个依赖少一个坑。
部署架构
Cloudflare CDN (橙色云)
│
│ HTTPS, 缓存, DDoS
│
└── Cloudflare Tunnel (cloudflared)
│
│ 加密通道
│
└── Mac Mini (localhost)
│
├── nginx :28080 → blog.ratio-dd.com
├── nginx :28083 → blog-dev.ratio-dd.com (预览)
└── Docker 容器隔离
为什么不直接暴露服务器
我家是 CGNAT,Mac Mini 没有公网 IP。两个选择:
- frp 内网穿透:需要公网中转服务器,不符合零运维底线
- Cloudflare Tunnel:免费,cf 边缘节点主动连到本地 cloudflared,不需要开端口
选 CF Tunnel。唯一代价是被 Cloudflare 绑定——但从第一天就用了 CF 的 DNS 和 CDN,没多绑。
nginx 做什么
- Blog prod(:28080):安全头 + 静态文件 +
Cache-Control: max-age=86400 - Blog dev(:28083):Basic Auth 保护,只有我能看预览
- 旧 URL 301 重定向:
dev.blog→blog-dev等 SSL 相关的历史问题 - 安全头:
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer
Docker 隔离
Prod 和 dev 是两个独立的 Docker 容器,分别挂载 dist/ 和 dist-draft/。
好处:
- 重启 dev 不影响 prod
- 环境变量完全隔离(dev 有 Basic Auth)
- 一键
docker restart blog-dev就推新预览
缓存策略
两层缓存:
- nginx 层
Cache-Control: max-age=86400(静态资源 24h) - Cloudflare Cache Rule:对整个
blog.ratio-dd.com/*开启 CDN 缓存
发新文章后跑 purge-cache.sh 清掉 CF 缓存,保证用户看到最新内容。CF 缓存 HIT 时响应 ~280ms,全球 CDN 分发。
审批发布流:Archie 搞定一切
传统流程:写 MD → git push → CI/CD → 等构建 → 网站更新。
我懒。Archie 帮我去掉了所有手动步骤:
我:写草稿 (draft: true)
↓
Archie:npm run build:draft → 推预览站 → 发 Telegram 消息
↓
我:看到预览链接 + 文章信息,点 ✅ 发布 或 ❌ 取消
↓
Archie:收 callback → 5 分钟倒计时 → npm run build → purge cache → pin 消息
关键设计
编译时强制审批:approval-gate.mjs 在 npm run build 之前检查 .approved-posts 文件。没审批的文章即使 draft: true 也会被拦住。
三重防护:
draft: true→ 预览站可见,线上不可见approval-gate.mjs→ build 前检查,未审批的不让过- Telegram 审批 → 只有我点 ✅ 才会写入
.approved-posts
预览站隔离:blog-dev.ratio-dd.com 独立 Docker 容器 + Basic Auth。预览站和正式站物理隔离,草稿绝不可能泄露到生产。
消息实时更新:同样的 Telegram 消息,从”预览待审批”→“已批准 倒计时”→“已取消”→”✅ 已发布”,整个生命周期的状态变化都在一条消息里呈现。
总结:哪些对了,哪些还会改
做对了的
- 选 Astro 没选 Hugo/Zola:Agent 友好度是未来,不是当下。现在投入 JS 生态是对的
- Cloudflare Tunnel 零运维:没买服务器、没配 IP、没开端口。写了一个 yml 文件就上线了
- 审批流放在 build 阶段:编译时检查比运行时检查更可靠。想绕过审批至少需要写代码
- Docker 双环境:dev 死多少次都不影响 prod
还没做的
- HTTPS 证书自动续期:目前走的是 CF 边缘证书,Origin 是 HTTP → cloudflared 加密通道,不需要自己搞证书。但如果有一天离开 Cloudflare 也要支持
- 评论系统:还没加。giscus(GitHub Issues 驱动)是最轻量的方案
- 分析统计:想加个 Umami(自建),比 Google Analytics 更对隐私友好