deploy blog to github cloudflare vercel

六月 13, 2026 更新: 八月 21, 2026 [网络与安全] #deployment #github #cloudflare

准备工作

先构建站点,确认 public/ 目录正常生成:

zola build
ls public/

GitHub Pages

创建仓库

创建 github.com/<你的用户名>/<你的用户名>.github.io 仓库,这是 GitHub 的 user site,域名固定为 https://<用户名>.github.io。

git init
git remote add origin git@github.com:<用户名>/<用户名>.github.io.git

GitHub Actions 自动部署

创建 .github/workflows/deploy.yml:

name: Deploy Zola Blog

on:
  push:
    branches: [master]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Install Zola
        run: |
          ZOLA_VERSION=$(curl -s https://api.github.com/repos/getzola/zola/releases/latest | grep tag_name | cut -d '"' -f4)
          curl -sL "https://github.com/getzola/zola/releases/download/${ZOLA_VERSION}/zola-${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz
          sudo mv zola /usr/local/bin/

      - name: Build
        run: zola build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

推送后 GitHub Actions 自动构建并部署到 gh-pages 分支。在仓库 Settings → Pages 中选择 gh-pages 分支作为部署源。

坑:shalzz action 必须 pin 版本 tag,不能跟 master

uses: shalzz/zola-deploy-action@master 的 Dockerfile 硬编码了 zola 版本(2026-08 时为 0.23.1),ZOLA_VERSION env 变量在新版完全被忽略(传了也不生效)。当 action 更新内置 zola 后,旧主题(abridge)模板语法不兼容新版直接构建失败:

ERROR error: Found `:` but expected }}.
  --> abridge/templates/categories/single.html:17:17

修复:pin 到与本地一致的版本 tag(action 的 tag 按 zola 版本命名):

uses: shalzz/zola-deploy-action@v0.19.2

排查技巧:gh run view <run-id> --log-failed 看日志里的 Using zola x.y.z 行,与本地 zola --version 对比即可发现版本漂移。

Cloudflare Pages

Cloudflare Pages 直接托管静态站点,支持自定义域名 juzhong.xyz 或子域名如 blog.juzhong.xyz。

连接 GitHub 仓库

在 Cloudflare Dashboard → Workers & Pages → Pages → 创建项目,连接 GitHub 仓库。

构建配置:

配置项值
构建命令zola build
输出目录public
环境变量ZOLA_VERSION=0.19.2

Cloudflare Pages 内置了 Zola 支持,会自动下载对应版本。

自定义域名

Pages 项目 → 自定义域 → 添加 blog.juzhong.xyz。

需要在 juzhong.xyz 的 DNS 中添加 CNAME 记录:

blog.juzhong.xyz  CNAME  <项目名>.pages.dev

Cloudflare 代理(橙色云朵)开启后自动提供 HTTPS。

阿里云 VPS + Cloudflare Tunnel

适用场景:域名未备案(国内服务器直连 80/443 会被要求备案)、想自己掌控服务器。Cloudflare Tunnel 让 VPS 主动连接 Cloudflare 边缘,用户访问走 Cloudflare 的 HTTPS,源站端口无需对外开放,未备案域名也能正常访问。

架构

用户 --HTTPS--> Cloudflare 边缘 --Tunnel--> VPS localhost:2083 --Caddy file_server--> 静态文件

1. 安装 cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
sudo install -m 755 cloudflared /usr/local/bin/cloudflared

2. 登录并创建 Tunnel

cloudflared tunnel login              # 浏览器授权
cloudflared tunnel create blog-tunnel

3. 配置 Tunnel

编辑 /etc/cloudflared/config.yml:

tunnel: <tunnel-id>
credentials-file: /home/<user>/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: blog.juzhong.xyz
    service: http://localhost:2083
  - service: http_status:404

4. 绑定 DNS

cloudflared tunnel route dns <tunnel-id> blog.juzhong.xyz

等价于手动在 Cloudflare DNS 加 CNAME:blog.juzhong.xyz → <tunnel-id>.cfargotunnel.com(保持橙色云朵代理)。

5. 构建站点(关键:-u 必须匹配实际域名)

zola build -u https://blog.juzhong.xyz

为什么 -u 这么重要? Zola 的搜索索引 search_index.*.js 里存的是绝对 URL,按 base_url(即 --base-url/-u 参数)生成。如果构建时的域名和用户实际访问的域名不一致(比如用 https://ali.juzhong.xyz 构建、用户却从 blog.juzhong.xyz 访问),搜索能搜出结果,但点击结果会跳到错误域名 → 404。每个部署目标都要用对应的域名构建:本站用 -u https://blog.juzhong.xyz,Vercel 用 --base-url https://vercel.juzhong.xyz(见下文)。换域名部署时必须同步重新构建。

6. 上传到 VPS

rsync -av --delete public/ alivps:/home/peter/project/caddy-dns/blog/

7. Caddy 静态部署

服务器上 docker-compose.yaml(caddy 镜像需含 Cloudflare DNS 插件,如 slothcroissant/caddy-cloudflaredns):

services:
  caddy:
    image: slothcroissant/caddy-cloudflaredns:2.11.4
    restart: unless-stopped
    ports:
      - "2083:2083"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./blog:/var/www/blog:ro

Caddyfile:

http://blog.juzhong.xyz:2083 {
    root * /var/www/blog
    file_server
    encode gzip
}
docker compose up -d

如果 VPS 上还要托管别的站点(如 wg-easy 管理界面),Caddy 里加一个 reverse_proxy 站点块共用 2083 入口即可。

Vercel

Vercel 也支持 Zola 静态站点,但默认构建环境没有 Zola 版本太低,需要自定义构建命令。

连接仓库与构建配置

访问 vercel.com/new 导入 GitHub 仓库。

Vercel 可能无法自动识别 Zola 项目,手动配置构建参数:

配置项值
FrameworkOther
Build Command见下方自定义命令
Output Directorypublic

自定义构建命令(下载指定版本 Zola 并构建):

curl -sL https://github.com/getzola/zola/releases/download/v0.19.2/zola-v0.19.2-x86_64-unknown-linux-gnu.tar.gz | tar -xzf - && ./zola build --base-url https://vercel.juzhong.xyz

拆解说明:

注意:将 vercel.juzhong.xyz 替换为你的实际域名;如果没有自定义域名,使用 Vercel 分配的 xxx.vercel.app 域名。

自定义域名配置(以 Cloudflare 为例)

下面以子域名 vercel.juzhong.xyz 为例,DNS 解析由 Cloudflare 管理。

第一阶段:Vercel 平台操作

  1. 进入项目仪表板,点击顶部 Settings → 左侧菜单 Domains
  2. 输入完整域名(例如 vercel.juzhong.xyz),点击 Add
  3. 页面会提示 Invalid Configuration(正常),点击域名旁边的 View 查看需要的 DNS 记录

Vercel 通常建议添加以下记录:

本项目使用子域名,只需 CNAME 记录即可。

第二阶段:Cloudflare DNS 操作

  1. 登录 Cloudflare 仪表板 → 选择域名 juzhong.xyz → DNS → 记录
  2. 点击 添加记录,配置如下:
字段值
类型CNAME
名称vercel
目标cname.vercel-dns.com
代理状态仅 DNS(灰色云朵 ☁️)

关键:代理状态务必选择灰色云朵(仅 DNS),不要开启橙色云朵(代理)。

为什么? Vercel 使用 Let's Encrypt 自动签发 SSL 证书,需要通过 ACME 协议验证域名所有权。Cloudflare 橙色代理会拦截 ACME 验证请求,导致证书签发失败、域名始终显示 Invalid Configuration。

2026-08 更正(针对自建 Caddy):上面的说法对 Vercel 平台成立,但不能泛化——"CF 橙云拦截 ACME 验证"只拦截 tls-alpn-01 挑战(CF 边缘终结 TLS,LE 协商不了 acme-tls/1),而 http-01 挑战走 80 端口普通转发,不受代理影响。自建 Caddy 在橙云下会 tls-alpn 失败后自动跨 order 回退 http-01 并签发成功(实测 1~2 分钟完成,全程自动)。唯一死锁场景:CF 开启 "Always Use HTTPS" + SSL 模式 Full (strict) 且源站还没有有效证书。详见 Caddy 在 CF 橙云下的 ACME 挑战实测。

特殊情况:根域名配置

DNS 标准不允许根域名(@)使用 CNAME 记录。Cloudflare 通过 CNAME Flattening 技术自动将根域名的 CNAME 转换为 A 记录,你只需正常添加 CNAME 记录,名称写 @ 即可。

如果 Vercel 提示根域名需要 A 记录,则改为:

字段值
类型A
名称@
IPv4 地址76.76.21.21

第三阶段:验证与生效

  1. DNS 记录全球生效通常需要几分钟到几十分钟
  2. 回到 Vercel 项目 Domains 页面,状态从红色 Invalid Configuration 变为绿色 Valid Configuration 即表示成功
  3. SSL 证书自动签发,通常 1-3 分钟内完成
  4. 访问 https://vercel.juzhong.xyz 确认站点正常

额外配置:URL 重定向(可选)

在 Vercel 的 Domains 页面,点击域名旁边的 Edit,可设置:

常见问题排查

问题原因解决
状态一直 InvalidCloudflare 代理未关闭(橙色云朵)将 DNS 记录的代理状态改为灰色(仅 DNS)
DNS 记录冲突Cloudflare 中存在旧 A/AAAA 记录与 CNAME 冲突删除无关的解析记录,只保留 Vercel 所需的
www 子域名无法访问Vercel 不会自动添加 www 子域名手动在 Vercel 中添加 www.vercel.juzhong.xyz,并添加对应的 Cloudflare DNS 记录
SSL 证书续期失败后续开启了 Cloudflare 代理(橙色云朵),拦截了 ACME 验证在 Cloudflare Zero Trust 中为路径 /.well-known/acme-challenge/* 设置 Bypass 策略;或在证书续期期间临时关闭代理(自建 Caddy 场景无需此操作,会自动回退 http-01,见上方更正说明)

四平台对比

GitHub PagesCloudflare PagesVercel阿里云 VPS + Tunnel
免费额度无限无限100GB/月带宽取决于 VPS
自定义域名支持支持支持支持
HTTPS自动自动自动自动(Cloudflare)
构建时间10min/次500次/月6000min/月无限制
域名xxx.github.ioxxx.pages.devxxx.vercel.app自备域名
适用场景简单托管自定义域名 + CDN开发预览未备案域名 / 自己掌控服务器

推荐方案:源码放 GitHub,用 Cloudflare Pages 部署到 blog.juzhong.xyz。GitHub 负责版本管理,Cloudflare 提供全球 CDN 和免费 HTTPS。域名未备案或想用国内 VPS 时,改用「阿里云 VPS + Cloudflare Tunnel」方案。

无论哪种方案,部署/构建时 --base-url(或 serve -u)必须填实际访问域名,否则站内搜索不可用。