Skip to content

VitePress 视频与图片管理

本文是 CmdWise 网站文档系统的幕后故事,记录我们怎样把本地 assets/ 里的图片、视频自动上传到 R2,然后把文档里的相对路径替换为公共域名。

✨ 为什么要做?

  1. 仓库体积不断膨胀
    VitePress 文档会插入大量截图、演示视频,长期累积后 Git 仓库和 Cloudflare Pages 部署包都变得臃肿。
  2. 部署有文件数量 & 体积上限
    Pages 免费额度在对象数和总大小上有限制,上传超量会失败。
  3. 想充分利用 R2 的低成本存储 + CDN
    同在 Cloudflare 生态,域名与缓存配置天然契合。

🛠️ 方案概览

核心要点:

  • 原路径 & 原文件名:云端使用 docs/assets/<本地子目录>/<文件名>,不再使用哈希重命名,目录结构与仓库完全对应,利于 SEO / 排错。
  • 增量上传:脚本为每个文件计算 sha1 存入 .asset-map.json。只有当文件内容发生变化或新增时才调用 R2 API,其余直接 skip
  • 内容哈希仅作对比:虽然不改文件名,但仍用 hash 检测是否需要上传。
  • 低侵入:作者照常写相对路径,构建阶段自动替换为 CDN URL。

关键脚本

路径:packages/docs/scripts/upload-to-r2.js

bash
# 查看帮助
node scripts/upload-to-r2.js --help

# 干跑,打印计划不执行
pnpm run assets:sync --dry

# 真正同步
pnpm run assets:sync

脚本使用 @aws-sdk/client-s3 直连 R2,兼容 S3 协议。

🧰 环境 & 配置

  1. Node ≥18,包管理器 pnpm / npm。
  2. Cloudflare R2 bucket:cmdwise-assets
  3. 在项目 packages/docs 目录准备 .env
ini
R2_ACCOUNT_ID=xxxxxxxxxxxxxxxx
R2_ACCESS_KEY_ID=xxxxxxxxxxxx
R2_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
R2_BUCKET=cmdwise-assets
R2_PUBLIC_BASE_URL=https://assets.cmdwise.app    # 或先用原生 URL

.env 请勿提交仓库,确保在 .gitignore 中排除。

🌐 CNAME 配置指南

  1. Cloudflare Dashboard → R2 › Custom DomainsAdd custom domain
  2. 填入 assets.cmdwise.app,选择存储桶 cmdwise-assets,确认。
  3. 系统会在同账号 DNS 里自动新增一条 CNAME
    txt
    类型: CNAME
    名称: assets
    内容: <accountid>.r2.cloudflarestorage.com
    Proxy Status: Proxied
  4. 等待 DNS 生效(通常 < 1 分钟)。
  5. .env / Secrets 中把 R2_PUBLIC_BASE_URL 设置成 https://assets.cmdwise.app

若想切换域名,只需改 R2_PUBLIC_BASE_URL 并重跑 assets:sync,脚本会批量替换。

🔍 更多技术细节

主题说明
S3Client 配置forcePathStyle: true,避免虚拟主机风格子域名证书不匹配导致 TLS 握手失败
运行参数--dry 仅打印操作,--env .env.prod 指定额外环境文件
目录前缀统一上传到 docs/assets/,后续若有 website 资源,可改为 website/assets/ 不影响现有逻辑
未来规划预留图片压缩(WebP)/ 视频转码(WebM)钩子,在上传前处理 buf 即可

Cloudflare操作清单

  • [x] 获取 R2_ACCOUNT_ID。 cf-account-id
  • [x] 创建 R2 存储桶 cmdwise-assets
  • [x] 创建 R2 存储桶 cmdwise-assets 的访问密钥,获取R2_ACCESS_KEY_ID和R2_SECRET_ACCESS_KEY。 r2-create-access-key

✅ 本地验证检查清单

  • [x] pnpm i 成功,依赖无报错。
  • [x] pnpm run assets:sync --dry 输出同步计划。
  • [x] pnpm run assets:sync 后生成/更新 .asset-map.json
  • [x] Markdown 中的 ./assets/… 链接已被替换为 https://assets.cmdwise.app/…
  • [x] 运行 pnpm run dev,页面中的图片 / 视频正常加载。
  • [x] R2 Dashboard 能看到新上传的对象。

🚀 CI 集成片段(GitHub Actions)

yaml
- name: Sync docs assets to R2
  run: pnpm --filter @cmdwise/docs run assets:sync
  env:
    R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
    R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
    R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
    R2_BUCKET: cmdwise-asstets
    R2_PUBLIC_BASE_URL: https://assets.cmdwise.app

放在 vitepress build 之前执行即可。

🩺 典型问题排查

现象日志/报错排查 & 解决
EPROTO / sslv3 alert handshake failureCLI 报错确认脚本已设置 forcePathStyle: true,并检查 endpoint 域名与证书是否匹配
Missing credentials运行时退出检查 .env 或 Secrets 中 4 个 R2 变量是否填写正确
图片 404浏览器加载失败1) 检查 .asset-map.json 是否含 URL 2) 查看 R2 Dashboard 对象 key 是否正确
上传非常慢HeadObject/PutObject 大量调用通常是增量失效,检查仓库是否清除了 .asset-map.json 或更换了路径前缀
CDN 未刷新资源改名后仍旧是旧图因采用同名覆盖,Edge 可能缓存;可加版本号目录或使用哈希命名策略

⚖️ 方案对比与选择

维度当前方案(原目录+文件名)哈希文件名方案Workers 代理方案
SEO 友好✅ URL 可读❌ 不可读✅ 保留原路径
CDN 缓存中等,文件名不变需短缓存或版本目录极佳,可长期缓存同当前方案
实现复杂度中(需改写文件名+Map)高(需编写 Workers)
本地作者体验原路径即可写相对路径但最终 URL 变化原路径即可
增量上传实现已支持需结合 Map仍需 Map

综合考虑 CmdWise 文档体量及可维护性,当前方案最平衡。如后续大规模改图或对缓存命中要求更高,可再评估哈希文件名方案。