Repository Wiki
LyraVoid/Mizuki

静态托管部署(Vercel / Netlify / GitHub Pages)

Mizuki 是一个基于 Astro 的静态博客,最终产物是纯静态的 dist/ 目录,可以托管在任意静态托管平台。本页覆盖将 Mizuki 构建产物发布到 GitHub Pages、Vercel、Netlify(以及同一机制下的 Cloudflare Pages)所需的完整配置:构建命令、输出目录、平台配置文件、CI 工作流、环境变量以及内容仓库更新时如何反向触发构建。

Purpose and Scope

本页回答"怎么把构建出来的静态站点发上线、并在内容更新后自动重新发布"这一问题,范围包括:

  • 各平台的构建参数(Build Command / Output Directory)与平台侧配置(vercel.json、可选 netlify.toml);
  • GitHub Pages 的三条 GitHub Actions 工作流(build.yml / deploy.yml / format.yml)及 Pages 分支设置;
  • prebuild 钩子驱动的统一构建入口(scripts/sync-content.js)如何让所有平台共享同一条构建命令;
  • 私有内容仓库在各平台的认证方式(GITHUB_TOKEN / SSH / PAT);
  • 内容仓库更新触发重建的三种方案(Repository Dispatch / Deploy Hook / 定时构建);
  • 部署侧故障排查与环境变量参考。

以下内容有意留给兄弟页面,本页只做引用不做展开:

  • 内容分离架构的完整原理与目录结构:见内容分离指南(docs/CONTENT_SEPARATION.md);
  • 内容仓库的组织方式与迁移:见 docs/CONTENT_REPOSITORY.md;
  • Astro 站点本身的配置(astro.config.mjs 的渲染、集成等):见站点构建相关页面。

Overview

Mizuki 的部署模型非常朴素:一次构建,处处托管。pnpm build 执行 Astro 静态构建,输出到 dist/,任何静态托管平台只要拿到这个目录就能上线。部署差异只体现在两处:

  1. 谁来跑构建 —— GitHub Pages 用仓库自带的 GitHub Actions 工作流跑;Vercel / Netlify / Cloudflare Pages 用平台自己的 Git 集成跑(连仓库、平台拉代码、平台构建)。
  2. 内容从哪来 —— 本地模式下内容就放在代码仓库里,开箱即用;内容分离模式下,构建前的 prebuild 钩子(scripts/sync-content.js)根据环境变量决定是否从远程内容仓库(如 Mizuki-Content)同步 posts/、spec/、data/、images/ 等内容到 src/content/ 与 public/images/。

这个设计的关键取舍是:构建命令对所有平台、所有模式保持统一(都是 pnpm build),模式切换完全由环境变量驱动,而不是为每个平台维护不同的构建脚本。同步失败时 prebuild 以 || true 兜底,回退到本地内容,保证构建不被内容仓库故障打断。

部署前的唯一必做步骤是更新站点 URL(用于生成 canonical / sitemap 等绝对链接):

javascript
1export default defineConfig({ 2 site: 'https://your-domain.com', // 更新为你的域名 3 // ... 4});

Source: DEPLOYMENT.md

该配置位于仓库根目录的 astro.config.mjs。

Architecture

整体部署架构

Loading diagram...

要点解读:

  • 统一的构建入口:GitHub Actions、Vercel、Netlify、Cloudflare Pages 四条路径最终都汇入同一个 prebuild → astro build 流程,差异只在"谁触发、谁执行"。
  • 两条部署通道:
    • GitHub Pages 走 CI 通道:deploy.yml 在 GitHub Actions 里构建,把 dist/ 推到 pages 分支,再由 GitHub Pages 的 "Deploy from a branch" 机制发布。
    • Vercel / Netlify / Cloudflare Pages 走 平台 Git 集成通道:平台检测到仓库更新后自己拉代码、跑 pnpm build、托管 dist/。
  • 内容仓库是旁路输入:只有 ENABLE_CONTENT_SYNC=true 时,prebuild 钩子才会去内容仓库取内容;内容仓库更新本身不会触发构建,需要额外的触发机制(见后文"内容仓库更新触发构建")。

平台部署参数对照

平台构建命令输出目录配置来源触发方式
GitHub Pagespnpm run builddist.github/workflows/deploy.ymlpush 到 main(+ 可选 repository_dispatch)
Vercelpnpm builddistvercel.json(默认)+ 平台环境变量Git 集成自动部署 / Deploy Hook
Netlifypnpm builddist可选 netlify.toml + 平台环境变量Git 集成自动部署 / Build Hook
Cloudflare Pagespnpm builddist平台环境变量Git 集成自动部署 / Deploy Hook

Source: DEPLOYMENT.md

Core Flow

统一构建链路:prebuild 钩子

所有部署平台共享同一个自动同步机制,它来自 package.json 的脚本定义:

json
1// package.json 2{ 3 "scripts": { 4 "prebuild": "node scripts/sync-content.js || true" 5 } 6}

Source: DEPLOYMENT.md

工作原理(按执行顺序):

  1. pnpm build 执行前自动运行 prebuild 钩子;
  2. scripts/sync-content.js 检查 ENABLE_CONTENT_SYNC 环境变量;
  3. 如果为 true,从 CONTENT_REPO_URL 指定的远程仓库同步内容到 src/content/ 和 public/images/;
  4. 如果为 false 或未设置,跳过同步,直接使用本地内容;
  5. || true 确保同步失败不会中断构建。

这个钩子的三个设计意图值得注意:

  • 统一构建命令:无论本地模式还是内容分离模式,无论哪个平台,命令都是 pnpm build,不需要为每种组合维护不同脚本;
  • 自动兼容所有部署模式:模式切换是运行时行为(环境变量),不是构建时行为(改代码);
  • 失败降级:内容仓库不可达时回退到本地内容,宁可发布旧内容也不让整站挂掉。

GitHub Pages:CI 通道完整时序

Loading diagram...

本地模式(默认)下无需任何配置即可使用,只需三步:推送代码到 GitHub → 仓库设置中启用 Pages(Source 选择 "Deploy from a branch",Branch 选择 pages / root)→ 等待 Actions 完成。

Source: DEPLOYMENT.md

项目包含三个工作流:

工作流触发条件功能
build.ymlPush/PR 到 mainCI 测试,检查构建
deploy.ymlPush 到 main构建并部署到 pages 分支
format.ymlPush/PR代码格式和质量检查

Source: DEPLOYMENT.md

Vercel / Netlify / Cloudflare Pages:平台 Git 集成通道

这三个平台的部署流程一致:在平台控制台连接 Git 仓库,平台在检测到推送后自行执行 pnpm build 并托管 dist/。差异主要在私有内容仓库的认证方式和对 submodule 的支持程度:

  • Vercel:Framework Preset 选 Astro,Build Command 与 Output Directory 使用默认值即可。项目包含两个配置文件 —— vercel.json(默认配置,适用于本地模式)与 vercel-with-content.json.example(内容分离示例,可选)。文档明确建议:使用默认 vercel.json,通过环境变量控制是否启用内容分离。

    Source: DEPLOYMENT.md

  • Netlify:Build command 填 pnpm build,Publish directory 填 dist。可选创建 netlify.toml 把构建参数与环境变量固化进仓库:

    toml
    1[build] 2 command = "pnpm build" 3 publish = "dist" 4 5[build.environment] 6 NODE_VERSION = "20" 7 PNPM_VERSION = "9" 8 # 如果使用内容分离 9 ENABLE_CONTENT_SYNC = "true" 10 CONTENT_REPO_URL = "https://github.com/your-username/Mizuki-Content.git" 11 USE_SUBMODULE = "true"

    Source: DEPLOYMENT.md

    私有仓库场景下,在 Site settings → Build & deploy → Deploy key 中添加有权限访问私有仓库的 SSH 密钥。

  • Cloudflare Pages:同样选 Astro 预设、pnpm build、dist。注意:Cloudflare Pages 默认不支持 Git Submodule,因此推荐 USE_SUBMODULE=false(独立仓库模式),或在构建命令中手动初始化:git submodule update --init && pnpm build。

    Source: DEPLOYMENT.md

内容仓库更新触发构建

内容代码分离架构下有一个固有的部署盲区:代码仓库(Mizuki)更新会触发自动构建,但内容仓库(Mizuki-Content)更新默认不会触发构建 —— 也就是说,发布新文章后需要手动重新部署代码仓库才能看到更新。文档给出三种解决方案:

方案难度推荐度适用平台
Repository Dispatch⭐ 简单⭐⭐⭐⭐⭐GitHub Pages, Vercel, Netlify, CF Pages
Webhook + Deploy Hook⭐⭐ 中等⭐⭐⭐⭐Vercel, Netlify, CF Pages
定时构建⭐ 简单⭐⭐⭐所有平台

Source: DEPLOYMENT.md

方案 1:Repository Dispatch(推荐)

原理是在内容仓库推送时,通过 GitHub Actions 触发代码仓库的构建工作流 —— 实时、无延迟、无需云平台特定配置、适用于所有部署平台且完全免费。配置分四步:

Step 1:创建 GitHub Personal Access Token(classic),Scopes 勾选 repo(完整仓库访问权限)。

Step 2:在内容仓库 Settings → Secrets and variables → Actions 中添加 Secret:DISPATCH_TOKEN = 刚创建的 PAT。

Step 3:在内容仓库创建 .github/workflows/trigger-build.yml:

yaml
1name: Trigger Main Repo Build 2 3on: 4 push: 5 branches: 6 - main # 或你使用的主分支名称 7 paths: 8 - 'posts/**' 9 - 'spec/**' 10 - 'data/**' 11 - 'images/**' 12 13jobs: 14 trigger: 15 runs-on: ubuntu-latest 16 steps: 17 - name: Trigger repository dispatch 18 uses: peter-evans/repository-dispatch@v2 19 with: 20 token: ${{ secrets.DISPATCH_TOKEN }} 21 repository: your-username/Mizuki # 改为你的代码仓库 22 event-type: content-updated 23 client-payload: | 24 { 25 "ref": "${{ github.ref }}", 26 "sha": "${{ github.sha }}", 27 "message": "${{ github.event.head_commit.message }}" 28 }

Source: DEPLOYMENT.md

paths 过滤器让只有实际内容目录(posts/、spec/、data/、images/)变化时才触发,避免无关提交导致空跑部署。

Step 4:在代码仓库的 .github/workflows/deploy.yml 中追加 repository_dispatch 触发器:

yaml
1name: Deploy to GitHub Pages 2 3on: 4 push: 5 branches: 6 - main 7 repository_dispatch: # 添加这个触发器 8 types: 9 - content-updated 10 11# ...其余配置保持不变

Source: DEPLOYMENT.md

方案 2:Webhook + Deploy Hook(Vercel / Netlify / Cloudflare Pages)

利用各平台提供的 Deploy Hook URL,在内容仓库更新时通过 GitHub Actions 发一个 curl 请求触发构建。优点是实时且与平台深度集成;缺点是需要为每个部署平台单独配置,且不适用于 GitHub Pages。

以 Vercel 为例(Netlify / Cloudflare Pages 同理,只是 Hook 的获取位置不同):

Step 1:Vercel 项目 Settings → Git → Deploy Hooks,创建 Hook(如 Name: Content Update,Git Branch: main),复制生成的 URL。

Step 2:在内容仓库创建 .github/workflows/trigger-vercel.yml:

yaml
1name: Trigger Vercel Deployment 2 3on: 4 push: 5 branches: 6 - main 7 paths: 8 - 'posts/**' 9 - 'spec/**' 10 - 'data/**' 11 - 'images/**' 12 13jobs: 14 trigger: 15 runs-on: ubuntu-latest 16 steps: 17 - name: Trigger Vercel Deploy Hook 18 run: | 19 curl -X POST "${{ secrets.VERCEL_DEPLOY_HOOK }}"

Source: DEPLOYMENT.md

Step 3:在内容仓库添加 Secret VERCEL_DEPLOY_HOOK = Hook URL。

Netlify 的对应写法只在 curl 上有细微差别(需要带请求体):

yaml
- name: Trigger Netlify Build Hook run: | curl -X POST -d '{}' "${{ secrets.NETLIFY_BUILD_HOOK }}"

Source: DEPLOYMENT.md

Cloudflare Pages 的 Deploy Hook 在 Settings → Builds & deployments → Deploy hooks 中创建,配置方式与上述相同,只需修改 Secret 名称和 workflow 文件名。

私有内容仓库的认证方式

内容分离模式下若内容仓库是私有的,各平台需要额外的认证配置。

GitHub Actions

场景配置方式
同账号私有仓库无需额外配置,自动使用 GITHUB_TOKEN 访问
跨账号私有仓库(SSH)用 webfactory/ssh-agent@v0.8.0 注入私钥 + submodules: true 检出
跨账号私有仓库(Token)checkout 时传 token: ${{ secrets.PAT_TOKEN }},或在 URL 内嵌 Token

跨账号 SSH 方式的工作流片段:

yaml
1# 添加 SSH 配置步骤 2- name: Setup SSH Key 3 uses: webfactory/ssh-agent@v0.8.0 4 with: 5 ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 6 7- name: Checkout 8 uses: actions/checkout@v4 9 with: 10 submodules: true

Source: DEPLOYMENT.md

Token 方式则在 Secrets 中添加 PAT_TOKEN(需要 repo 权限),并把内容仓库地址内嵌 Token:

yaml
1- name: Build site 2 run: pnpm run build 3 env: 4 ENABLE_CONTENT_SYNC: true 5 CONTENT_REPO_URL: https://${{ secrets.PAT_TOKEN }}@github.com/other-user/repo.git 6 USE_SUBMODULE: true

Source: DEPLOYMENT.md

Vercel / Netlify

  • 方式 A(授权):连接 GitHub 仓库时确保授权范围包括内容仓库的访问权限;
  • 方式 B(Token):添加环境变量,把 Token 内嵌进 CONTENT_REPO_URL:
1ENABLE_CONTENT_SYNC=true 2GITHUB_TOKEN=ghp_your_personal_access_token 3CONTENT_REPO_URL=https://${GITHUB_TOKEN}@github.com/your-username/Mizuki-Content-Private.git 4USE_SUBMODULE=true

Source: DEPLOYMENT.md

GitHub Pages 内容分离模式的环境变量注入

启用内容分离时,需要在 .github/workflows/deploy.yml 的构建步骤中取消注释环境变量部分:

yaml
1- name: Build site 2 run: pnpm run build 3 env: 4 ENABLE_CONTENT_SYNC: true 5 CONTENT_REPO_URL: ${{ secrets.CONTENT_REPO_URL }} 6 USE_SUBMODULE: true

Source: DEPLOYMENT.md

对应地,在仓库 Settings → Secrets and variables → Actions 中添加 Secret CONTENT_REPO_URL(如 https://github.com/your-username/Mizuki-Content.git)。

Configuration Options

环境变量参考

变量名必需默认值说明
ENABLE_CONTENT_SYNC❌false是否启用内容分离功能
CONTENT_REPO_URL⚠️-内容仓库地址(启用内容分离时必需)
USE_SUBMODULE❌false是否使用 Git Submodule 模式
CONTENT_DIR❌./content内容目录路径
INDEXNOW_KEY❌-IndexNow API 密钥,用于向搜索引擎提交 URL 更新
INDEXNOW_HOST❌-网站主机地址
BILI_SESSDATA❌-Bilibili SESSDATA,用于获取观看进度

⚠️ = 在特定模式下必需。

Source: DEPLOYMENT.md

关键文件

文件作用
astro.config.mjsAstro 站点配置,部署前需更新 site 为实际域名
vercel.jsonVercel 默认配置,适用于本地模式
vercel-with-content.json.exampleVercel 内容分离示例(可选)
.github/workflows/deploy.yml构建并部署到 pages 分支;内容分离时需取消注释 env 段并可选追加 repository_dispatch
.github/workflows/build.ymlCI 测试,检查构建
.github/workflows/format.yml代码格式和质量检查
netlify.toml(可选)固化 Netlify 构建命令、发布目录、Node/pnpm 版本与环境变量
package.json 的 prebuild 脚本触发 scripts/sync-content.js 内容同步

平台选择建议

场景推荐平台推荐模式配置
个人博客Vercel 或 GitHub Pages本地模式(最简单)无需环境变量
团队协作任意内容分离 - 私有仓库启用内容分离 + SSH 认证
多站点部署多个平台同时部署内容分离 - 公开仓库统一的环境变量配置

Source: DEPLOYMENT.md

Failure Modes, Edge Cases & Concurrency

文档以故障排查清单的形式覆盖了部署环节的主要失败模式,共 7 类:

问题 1:部署失败 —— "未设置 CONTENT_REPO_URL"

原因:启用了内容分离但未配置仓库地址。 解决:确认 ENABLE_CONTENT_SYNC=true 时同时设置了 CONTENT_REPO_URL;或将 ENABLE_CONTENT_SYNC 置为 false 回退到本地内容。

问题 2:私有仓库认证失败

  • GitHub Actions 同账号:确保使用 ${{ secrets.GITHUB_TOKEN }};跨账号:配置 SSH 密钥或 PAT Token;
  • Vercel/Netlify:确保授权了私有仓库访问,或使用 Token 内嵌 URL:https://TOKEN@github.com/user/repo.git。

问题 3:Submodule 与 .gitignore 冲突

错误信息:

The following paths are ignored by one of your .gitignore files: content fatal: Failed to add submodule 'content'

原因:.gitignore 中的 content/ 规则阻止了 Git 添加 submodule。三种解决方案:

方案 A —— 修改 .gitignore(推荐):

diff
1# content repository (if using independent mode) 2- content/ 3+ # content/ # 使用 submodule 时需要注释掉 4*.backup

Source: DEPLOYMENT.md

方案 B —— 切换到独立仓库模式:USE_SUBMODULE=false。

方案 C —— 自动降级(v1.1+):sync-content.js 会自动检测此冲突并降级到独立仓库模式,无需手动干预。

问题 4:Submodule 克隆失败

确认部署平台支持 Git Submodule(Cloudflare Pages 默认不支持);检查 SSH 密钥或 Token 配置;或改用 USE_SUBMODULE=false。

问题 5:构建成功但内容未更新

查看构建日志确认同步步骤是否执行;检查 ENABLE_CONTENT_SYNC 是否为 true;验证 CONTENT_REPO_URL 是否正确;清除部署平台缓存后重新部署。

问题 6:部署时间过长

优先使用 Git Submodule 模式(更快);启用部署平台缓存机制;优化图片大小和数量。

问题 7:Vercel 部署时 submodule 权限问题

错误信息 fatal: could not read Username for 'https://github.com',原因是私有仓库需要认证。解决:在 Vercel 项目设置中添加 GitHub 集成权限,或使用 Token URL,或切换到独立仓库模式。

并发与一致性注意点

  • 构建原子性:prebuild 的 || true 意味着内容同步失败不会阻断构建,代价是可能发布旧内容 —— 这是"可用性优先于新鲜度"的显式取舍;
  • Vercel 上的 submodule 陷阱:文档特别提示,若在 Vercel 上使用 USE_SUBMODULE=true,必须确保 .gitignore 中的 content/ 行已被注释掉,否则会导致部署失败;推荐在 Vercel 上使用 USE_SUBMODULE=false(独立仓库模式);
  • 触发风暴控制:Repository Dispatch 与 Deploy Hook 工作流都通过 paths 过滤(posts/**、spec/**、data/**、images/**)限制触发范围,避免无关提交反复触发重建。

Professional Notes

  • 设计哲学:Mizuki 的部署体系把"平台差异"压缩到两个维度 —— 触发器(Actions vs 平台 Git 集成 vs Deploy Hook)和认证(Token vs SSH vs 平台授权),而把"构建逻辑"完全统一在 pnpm build + 环境变量之后。新增一个托管平台时,只需要提供 Build Command / Output Directory 两个参数即可接入。
  • 运行时版本固化:netlify.toml 的 [build.environment] 中固化了 NODE_VERSION = "20" 与 PNPM_VERSION = "9",这是保证平台构建环境与本地一致、避免 Node 版本漂移导致构建失败的常规手段;其他平台建议在各自设置中固化相同版本。
  • 操作建议:首次部署推荐先跑本地模式走通全流程,再按需启用内容分离;私有内容仓库优先选择 Repository Dispatch + PAT 的组合(对平台无侵入、全部免费)。
  • 测试与验证:Repository Dispatch 方案的验证路径是 —— 在内容仓库编辑一篇文章并推送到 main,先确认内容仓库 Actions 页面的 "Trigger Main Repo Build" 工作流运行,再确认代码仓库 Actions 页面的部署工作流被触发。

Sources

(1 files)