博客最容易变重的地方不是文章,是图片。截图、录屏、PDF 一旦提交进 Git,仓库会以肉眼可见的速度膨胀,构建也会变慢。
所以这个站的媒体文件全部放在 Cloudflare R2,仓库里只有 Markdown。
为什么不直接把图片放仓库
- 仓库体积:Git 会永久保存历史版本,删掉图片也不会变小。
- 构建速度:每次构建都要读取这些静态资源。
- 分发质量:R2 背后是 Cloudflare 的边缘网络,缓存和带宽比 Pages 的静态资源更从容。
桶里的目录约定
整个桶叫 images,但只给博客用 blog/ 前缀,其他内容互不干扰:
images/
├── blog/
│ ├── posts/
│ │ └── 2026/
│ │ └── media-workflow/ # 每篇文章一个目录
│ ├── covers/
│ │ └── 2026/ # 文章封面,按年份归档
│ └── site/ # 头像、默认 OG 图等站点级素材
└── testFiles/ # 已有的临时文件,不动
按文章分目录的好处是:删除或迁移一篇文章的媒体只需要动一个前缀,不会误伤别的文章。
上传流程
媒体先放在本地一个临时目录(例如 ./media),运行上传脚本:
npm run r2 -- ./media --prefix blog/posts/2026/media-workflow
脚本会做四件事:
- 递归遍历目录,跳过隐藏文件和构建产物;
- 计算文件内容哈希,生成带指纹的文件名,例如
diagram-9f2c1a4b.png; - 上传到指定前缀下,并设置一年的 immutable 缓存;
- 在终端打印可直接粘贴的 URL。
得到的链接像这样:
https://store.yky.de5.net/blog/posts/2026/media-workflow/diagram-9f2c1a4b.png
把它写进文章即可:

常用参数
| 参数 | 作用 |
|---|---|
--dry-run | 只预览将要上传的文件和新链接,不真正上传 |
--force | 忽略本地清单,强制重新上传 |
--no-hash | 使用原文件名覆盖上传,适合需要固定链接的场景 |
--prefix | 指定 R2 里的目录前缀,例如 --prefix blog/posts/2026/hello-world |
默认使用带哈希的文件名。内容是全新的 URL,所以可以放心设置长期强缓存,不会出现改了图但浏览器还显示旧图的问题。
跳过重复上传
脚本在本地维护一份 .r2-manifest.json,记录每个文件的路径、大小、修改时间和哈希。文件没变就直接跳过,重复执行没有副作用。
这份清单不会提交到 Git,它只服务于本机的上传过程。换一台电脑第一次运行时会重新上传一次,属于正常现象。
安全边界
上传用的 R2 令牌只授予单个桶的 Object Read & Write,不涉及账号级管理权限;密钥只存在本地 .env,不进仓库、不进构建产物。静态博客在运行时完全不接触 R2 的凭证。