给文字造一间静态的房子
记录这个博客从 Markdown 到 Next.js 静态文件,再到 VPS 与 Caddy 的完整选择:为什么简单,以及简单如何带来可靠。
这个博客没有数据库,没有管理后台,也没有常驻的 Node.js 服务。浏览器最终拿到的是一组普通 HTML、CSS 和 JavaScript 文件。
这不是为了展示“技术上可以多么极简”,而是从目标倒推的结果:如果核心任务只是稳定地发布和阅读文章,动态系统并不会天然带来更多价值。
从目标开始,而不是从框架开始
在写第一行代码之前,我给网站设定了几条边界:
- 文章必须以 Markdown 保存;
- 草稿不能进入生产构建;
- 所有公开页面需要稳定 URL;
- RSS、Sitemap 和 robots.txt 必须是构建的一部分;
- VPS 只负责托管静态文件。
这些边界比“选哪个组件库”更重要。它们会直接决定系统能否长期维护,也能阻止项目在早期变成一个不必要的平台。
一条足够短的发布链路
网站的内容流可以画成一条很短的线:
Markdown → 内容校验 → Next.js 静态导出 → rsync → Caddy
Next.js 在构建时读取本地文章,生成文章详情页、标签页和归档页。构建结束后得到的 out/ 目录就是完整站点。部署过程只需要把它同步到服务器,并把一个符号链接切换到新版本。
为什么使用原子切换
如果直接覆盖正在服务的目录,访问者可能在同步中途拿到新旧文件的混合版本。更稳妥的方式是先上传到一个新的 release 目录,上传成功以后再切换 current 链接:
ln -sfn /var/www/ff-blog/releases/<revision> /var/www/ff-blog/current
这个动作几乎瞬间完成。旧版本仍然保留,因此回滚也只是重新指向上一个目录,不需要重新构建。
Caddy 只做它擅长的事
Caddy 负责自动申请 HTTPS 证书、压缩响应和提供静态文件。动态路由、认证和数据库都不在服务器职责里。
静态资源文件名包含内容哈希,可以设置一年的不可变缓存;HTML 与 RSS 则保持较短缓存,确保发布后能及时更新。这样的缓存策略足够清楚,也很容易检查。
构建失败应该早于部署失败
内容系统会在构建前检查 frontmatter。任何文章缺少标题、摘要、日期、标签或草稿状态,构建都会立即停止。另一个验收脚本在导出完成后检查核心页面是否存在,同时确认草稿目录没有出现在产物中。
这类校验看起来琐碎,却能把最常见的人为错误留在本地和持续集成阶段,而不是等到上线后才发现。
简单不是功能少,而是责任清楚
静态架构当然有边界。它不适合登录、实时数据或高度个性化的产品。但对于个人写作,它把几个角色分得很清楚:
- Markdown 保存知识;
- Git 保存变化;
- Next.js 生成页面;
- Caddy 交付文件;
- CDN 缓存公开内容。
每一层都可以被替换,却没有哪一层垄断内容。真正的可靠不是永远不出错,而是出了问题能够定位、回滚和迁移。
这就是我想给文字造的房子:结构不复杂,门牌长期不变,修理它不需要重新学习一整套生活方式。