静态托管部署(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/,任何静态托管平台只要拿到这个目录就能上线。部署差异只体现在两处:
- 谁来跑构建 —— GitHub Pages 用仓库自带的 GitHub Actions 工作流跑;Vercel / Netlify / Cloudflare Pages 用平台自己的 Git 集成跑(连仓库、平台拉代码、平台构建)。
- 内容从哪来 —— 本地模式下内容就放在代码仓库里,开箱即用;内容分离模式下,构建前的
prebuild钩子(scripts/sync-content.js)根据环境变量决定是否从远程内容仓库(如Mizuki-Content)同步posts/、spec/、data/、images/等内容到src/content/与public/images/。
这个设计的关键取舍是:构建命令对所有平台、所有模式保持统一(都是 pnpm build),模式切换完全由环境变量驱动,而不是为每个平台维护不同的构建脚本。同步失败时 prebuild 以 || true 兜底,回退到本地内容,保证构建不被内容仓库故障打断。
部署前的唯一必做步骤是更新站点 URL(用于生成 canonical / sitemap 等绝对链接):
1export default defineConfig({
2 site: 'https://your-domain.com', // 更新为你的域名
3 // ...
4});Source: DEPLOYMENT.md
该配置位于仓库根目录的 astro.config.mjs。
Architecture
整体部署架构
要点解读:
- 统一的构建入口: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/。
- GitHub Pages 走 CI 通道:
- 内容仓库是旁路输入:只有
ENABLE_CONTENT_SYNC=true时,prebuild钩子才会去内容仓库取内容;内容仓库更新本身不会触发构建,需要额外的触发机制(见后文"内容仓库更新触发构建")。
平台部署参数对照
| 平台 | 构建命令 | 输出目录 | 配置来源 | 触发方式 |
|---|---|---|---|---|
| GitHub Pages | pnpm run build | dist | .github/workflows/deploy.yml | push 到 main(+ 可选 repository_dispatch) |
| Vercel | pnpm build | dist | vercel.json(默认)+ 平台环境变量 | Git 集成自动部署 / Deploy Hook |
| Netlify | pnpm build | dist | 可选 netlify.toml + 平台环境变量 | Git 集成自动部署 / Build Hook |
| Cloudflare Pages | pnpm build | dist | 平台环境变量 | Git 集成自动部署 / Deploy Hook |
Source: DEPLOYMENT.md
Core Flow
统一构建链路:prebuild 钩子
所有部署平台共享同一个自动同步机制,它来自 package.json 的脚本定义:
1// package.json
2{
3 "scripts": {
4 "prebuild": "node scripts/sync-content.js || true"
5 }
6}Source: DEPLOYMENT.md
工作原理(按执行顺序):
pnpm build执行前自动运行prebuild钩子;scripts/sync-content.js检查ENABLE_CONTENT_SYNC环境变量;- 如果为
true,从CONTENT_REPO_URL指定的远程仓库同步内容到src/content/和public/images/; - 如果为
false或未设置,跳过同步,直接使用本地内容; || true确保同步失败不会中断构建。
这个钩子的三个设计意图值得注意:
- 统一构建命令:无论本地模式还是内容分离模式,无论哪个平台,命令都是
pnpm build,不需要为每种组合维护不同脚本; - 自动兼容所有部署模式:模式切换是运行时行为(环境变量),不是构建时行为(改代码);
- 失败降级:内容仓库不可达时回退到本地内容,宁可发布旧内容也不让整站挂掉。
GitHub Pages:CI 通道完整时序
本地模式(默认)下无需任何配置即可使用,只需三步:推送代码到 GitHub → 仓库设置中启用 Pages(Source 选择 "Deploy from a branch",Branch 选择 pages / root)→ 等待 Actions 完成。
Source: DEPLOYMENT.md
项目包含三个工作流:
| 工作流 | 触发条件 | 功能 |
|---|---|---|
build.yml | Push/PR 到 main | CI 测试,检查构建 |
deploy.yml | Push 到 main | 构建并部署到 pages 分支 |
format.yml | Push/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把构建参数与环境变量固化进仓库:toml1[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:
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 触发器:
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:
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 上有细微差别(需要带请求体):
- 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 方式的工作流片段:
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: trueSource: DEPLOYMENT.md
Token 方式则在 Secrets 中添加 PAT_TOKEN(需要 repo 权限),并把内容仓库地址内嵌 Token:
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: trueSource: 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=trueSource: DEPLOYMENT.md
GitHub Pages 内容分离模式的环境变量注入
启用内容分离时,需要在 .github/workflows/deploy.yml 的构建步骤中取消注释环境变量部分:
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: trueSource: 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.mjs | Astro 站点配置,部署前需更新 site 为实际域名 |
vercel.json | Vercel 默认配置,适用于本地模式 |
vercel-with-content.json.example | Vercel 内容分离示例(可选) |
.github/workflows/deploy.yml | 构建并部署到 pages 分支;内容分离时需取消注释 env 段并可选追加 repository_dispatch |
.github/workflows/build.yml | CI 测试,检查构建 |
.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(推荐):
1# content repository (if using independent mode)
2- content/
3+ # content/ # 使用 submodule 时需要注释掉
4*.backupSource: 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 页面的部署工作流被触发。
Related Links
- DEPLOYMENT.md —— 本页所有部署配置的工作原文
- astro.config.mjs —— 站点 URL 等构建配置
- AUTO_BUILD_TRIGGER.md —— 内容仓库自动触发构建的专题文档
- CONTENT_SEPARATION.md —— 内容分离完整指南(兄弟页面:架构与目录结构)
- CONTENT_REPOSITORY.md —— 内容仓库的组织方式(兄弟页面)
- CLAUDE.md —— 项目整体说明