为什么写这篇
这个仓库不只是用来存放博客源码。它也记录了我希望这个博客如何被开发、构建和发布:源码应该放在哪里,Cloudflare 应该构建什么,以及哪些文件不应该成为事实来源。
这个项目的目标很简单:
- 搭建完成后,我可以专注于写文章。
- 不使用复杂编辑器,使用 Markdown。
- 速度是关键。不使用数据库,全部静态化。
- 将源代码保存在私有位置。
- 发布到快速、可靠的服务商,例如 Cloudflare。
- 通过一次提交保持部署流程可重复执行。
仓库结构
这个项目没有直接放在仓库根目录,而是放在仓库的 src 目录下。
重要结构大概是这样:
repo-root/
├── README.md
├── .gitignore
└── src/
├── package.json
├── pnpm-lock.yaml
├── astro.config.ts
├── astro-paper.config.ts
├── public/
└── src/
├── content/
│ ├── pages/
│ └── posts/
│ ├── en/
│ │ └── blog.md
│ └── zh/
│ └── blog.md
├── pages/
├── layouts/
└── components/
这里会看到一个看起来有点奇怪的 src/src。但它其实是两个不同层级。
第一个 src 是 Cloudflare Pages 看到的项目根目录。它里面有 package.json、astro.config.ts 和 lockfile。第二个 src 是站点源码目录,里面放页面、布局、组件和内容。
所以部署时,Cloudflare 应该进入:
repo-root/src
然后构建流程会把静态网站生成到:
repo-root/src/dist
因为 Cloudflare Pages 的项目根目录已经是 src,所以 Cloudflare 里的构建输出目录应该填写:
dist
而不是:
src/dist
这个区别很小,但它正是那种会让部署失败的细节。
源码和构建产物要分开
我希望 Git 跟踪的是网站源码,而不是已经生成出来的网站。
源码包括 Markdown 文章、页面、布局、组件、配置文件和 lockfile。构建产物则是执行 build 之后生成的 dist 目录。
所以这些目录不应该提交到 Git:
node_modules/
src/node_modules/
dist/
src/dist/
.wrangler/
根目录的 .gitignore 也保留了对旧构建输出目录的忽略,例如:
deploy/cloudflare/website/
这样仓库会更干净。如果需要重新发布网站,应该从源码重新构建,而不是把某次旧的构建产物当成事实来源。
本地开发
本地开发时,我会先进入应用目录:
cd src
pnpm install
pnpm dev
开发服务器会运行在:
http://localhost:4321/
这是我写文章、改布局、检查页面效果时的主要反馈循环。
本地构建
部署之前,我可以先在本地跑一次生产构建:
cd src
pnpm build
构建完成后会生成:
src/dist/
这个目录就是最终可以发布的静态网站。里面会包含 HTML、静态资源、RSS、sitemap、搜索索引,以及其他构建生成的文件。
为什么选择 Cloudflare Pages
一开始我思考的是:“怎样把静态文件放到 Cloudflare 上?”如果从这个角度出发,很容易走到 Worker 的方案。Worker 也确实可以托管静态资源。
但对这个博客来说,Cloudflare Pages 更合适。
它的心智模型更简单:
GitHub 仓库
Cloudflare Pages 构建
生成静态网站
这个项目本质上就是一个静态博客。我不需要为了说明它而额外维护一个 Worker 应用。Pages 对这种项目更直接。
两种部署方式
我为这个项目保留了两种部署方式。它们适合不同场景。
方式一:本地构建后用 Wrangler 上传
这种方式适合我想在本机完成构建,然后手动上传生成的 dist 目录。
命令流程是:
cd src
pnpm install
pnpm build
npx wrangler login
npx wrangler pages deploy dist --project-name=victorbi-preview --branch=main
这里使用 dist 是正确的,因为命令是在 src 这个应用目录里执行的。
这里的 --branch 选择的是 Cloudflare Pages 的部署环境上下文。它不一定等同于本地 Git 分支名。对这个仓库来说,手动上传路径指向的是 victorbi-preview 这个 Pages 项目。
方式二:让 Cloudflare 从 GitHub 构建
这是更适合作为长期方案的工作流。
发布流程变成:
撰写或修改文章
提交源码
推送到 GitHub
Cloudflare Pages 构建静态网站
Cloudflare Pages 部署 dist
这个仓库在 Cloudflare Pages 里的配置应该是:
Root directory:
src
Build command:
pnpm build
Build output directory:
dist
Node.js version:
22.12.0 or newer
Environment variable:
NODE_VERSION=22.12.0
Node 版本很重要,因为 src/package.json 里声明了这个要求:
{
"engines": {
"node": ">=22.12.0"
}
}
如果 Cloudflare 使用更旧的 Node 版本构建,即使本地构建正常,线上构建也可能失败。
域名这个细节
站点配置里当前使用的是:
https://victorbi.pages.dev/
这个值在:
src/astro-paper.config.ts
手动部署目标是:
victorbi-preview
这个预览项目发布到:
https://victorbi-preview.pages.dev/
这里最重要的规则是:site.url 应该和我希望搜索引擎、RSS、sitemap、页面元数据使用的正式域名一致。如果生产环境的 Cloudflare Pages 域名变了,我也应该同步更新 src/astro-paper.config.ts。
我如何组织双语文章
这个博客是双语的。英文文章放在:
src/src/content/posts/en/
中文文章放在:
src/src/content/posts/zh/
两种语言版本通过同一个 translationKey 关联:
translationKey: "deploy-blog"
每篇文章也会声明自己的语言:
lang: "en"
或者:
lang: "zh"
这样站点可以把英文和中文文章当作两个独立的 Markdown 文件处理,同时又知道它们属于同一篇文章的两个语言版本。
我的发布检查清单
当我修改内容时,检查流程应该尽量短:
cd src
pnpm build
如果构建成功,再提交并推送:
git add .
git commit -m "Update blog post"
git push
之后 Cloudflare Pages 可以从 GitHub 自动构建并部署。如果需要手动部署,也可以使用上面的 Wrangler 流程。
参考资料
这套部署会用到的 Cloudflare 文档包括:
对这个项目来说,这些链接是辅助资料。真正的实践来源仍然是仓库本身:src/package.json 说明脚本和 Node 版本要求,src/astro-paper.config.ts 说明站点元数据,Cloudflare Pages 项目设置说明实际部署行为。