希曼日记

用 gh 命令行配 GitHub Pages

本页目录 8
  1. 为什么不点网页
  2. 启用
  3. 部署工作流
  4. 然后图就全裂了
  5. 验证要逐个资源查
  6. 一个差点误判的插曲
  7. 速查
  8. 顺带一提

用 gh 命令行配 GitHub Pages:页面能开,图全裂

给一个静态站配 GitHub Pages,我想全程不碰网页设置。gh 能做到,三条命令 的事。但第一次部署完打开一看——页面正常,四张截图全裂。

问题不在 Pages,在我没意识到站点跑在子路径下。这篇把过程和那个坑记下来。

为什么不点网页

不是为了炫技。网页点出来的配置没法进版本库,换个仓库要重新点一遍,也说不清 当初为什么那么选。命令行写下来的东西可以复制、可以 diff、可以给别人。

启用

gh 没有 gh pages 子命令,得用 gh api 打 REST 接口:

Bash
gh api -X POST repos/OWNER/REPO/pages -f build_type=workflow

build_type=workflow 是关键:告诉 GitHub 由 Actions 部署,而不是传统那种 "从 gh-pages 分支或 docs/ 目录发布"。后者还得单独维护一个分支,很别扭。

返回里确认两个字段:

JSON
{
"build_type": "workflow",
"html_url": "https://OWNER.github.io/REPO/"
}

注意 html_url 末尾那个 /REPO/。这就是后面翻车的原因,当时我扫了一眼 就过去了。

部署工作流

YAML
name: pages
on:
push:
branches: [main]
paths: ["site/**", ".github/workflows/pages.yml"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with: { runtime: node@24, install: false }
- working-directory: site
run: pnpm install --frozen-lockfile
- uses: actions/configure-pages@v6
id: pages
- working-directory: site
env:
SITE_BASE: ${{ steps.pages.outputs.base_path }}
SITE_URL: ${{ steps.pages.outputs.origin }}
run: pnpm build
- uses: actions/upload-pages-artifact@v5
with: { path: site/dist }
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- id: deploy
uses: actions/deploy-pages@v5

三个我踩过或差点踩的点:

id-token: write 不能省。 deploy-pages 用 OIDC 换部署令牌,缺了会因权限 失败,而报错不会告诉你少的是哪个权限。

cancel-in-progress 必须是 false。 Pages 同一时刻只接受一个部署,设成 true 的话连续推两次,前一次会被拦腰砍断。

别照着记忆写版本号。 我第一版写的是 v5/v4/v4,全错。action 的主版本号和它 的 Node 运行时是两回事——GitHub 在弃用 Node 20,有的 action 主版本号很新但运行 时还是旧的。逐个查:

Bash
gh api repos/OWNER/REPO/contents/action.yml?ref=TAG -q '.content' \
| base64 -d | grep -E "^\s*using:"

实测下来是 configure-pages@v6、upload-pages-artifact@v5、deploy-pages@v5, 三个主版本号各不相同。之前我在别的项目上就吃过亏:upload-artifact@v5 看着新, 运行时仍是 node20,升上去等于没升。

然后图就全裂了

部署成功,打开站点,页面结构完好,四张产品截图全是碎图标。

原因很直白:Pages 把项目站点放在 https://OWNER.github.io/REPO/,站点跑在 子路径下。而我模板里写的是绝对路径:

HTML
<img src="/shots/wbs.png">

浏览器于是去请求 https://OWNER.github.io/shots/wbs.png——少了 /REPO,404。

我原以为构建工具会处理。不会。 Astro 的 base 只影响它自己生成的引用 (打包后的 CSS/JS 路径),你手写在模板里的绝对路径它不碰。Vite、Next 也一样。

改法

同一份代码常常要部署到多处——我这个站主站在自己服务器的根路径,Pages 是镜像 在子路径。所以别写死,从环境变量取:

JavaScript · astro.config.mjs
const site = process.env.SITE_URL ?? "https://example.com";
const base = process.env.SITE_BASE ?? "/";
export default defineConfig({ site, base, output: "static" });

模板里加个助手:

Astro
---
const asset = (p) =>
`${import.meta.env.BASE_URL.replace(/\/$/, "")}/${p.replace(/^\//, "")}`;
---
<img src={asset("shots/wbs.png")} />
<link rel="icon" href={asset("favicon.svg")} />

工作流里 configure-pages 会直接给出 base_path,注入进去就行——上面 YAML 里 那两个 env 就是干这个的。

本地先验,别推上去才发现

两条命令的事:

Bash
pnpm build && grep -o 'src="[^"]*shots[^"]*"' dist/index.html | head -1
# src="/shots/wbs.png" 根路径,对
SITE_BASE=/REPO pnpm build && grep -o 'src="[^"]*shots[^"]*"' dist/index.html | head -1
# src="/REPO/shots/wbs.png" 子路径,对

两种都对才推。

验证要逐个资源查

这是我从这次学到的最实用的一条:首页 200 不代表站点没问题。

子路径配错的典型症状恰恰是首页完全正常、资源全 404。所以别只 curl 一下首页:

Bash
for u in / /shots/wbs.png /favicon.svg; do
curl -s -o /dev/null -w "$u -> %{http_code}\n" -L "https://OWNER.github.io/REPO$u"
done

再确认页面里的路径真的带了前缀:

Bash
curl -sL https://OWNER.github.io/REPO/ | grep -oE 'src="[^"]*"' | head -3

一个差点误判的插曲

验证时我用浏览器自动化去访问,连接直接被断(ERR_CONNECTION_CLOSED),第一反应 是站点挂了。但同一台机器上 curl 全部 200。

是本地网络到 GitHub CDN 的问题,跟站点无关。别因为一个工具失败就下结论, 换个方式交叉验证一下,能省很多冤枉功夫。

速查

Bash
# 看配置
gh api repos/OWNER/REPO/pages -q '.build_type, .html_url, .status'
# 手动触发
gh workflow run pages.yml
# 跟踪最近一次
gh run watch "$(gh run list --workflow=pages.yml --limit 1 \
--json databaseId --jq '.[0].databaseId')" --exit-status
# 看失败日志
gh run view --log-failed
# 关掉
gh api -X DELETE repos/OWNER/REPO/pages

自定义域名的话:

Bash
gh api -X PUT repos/OWNER/REPO/pages -f cname=www.example.com

DNS 加 CNAME 指到 OWNER.github.io。注意用了自定义域名后站点回到根路径, base 得跟着变——configure-pages 会自动处理,但如果你在别处写死了,记得改。

顺带一提

私有仓库要开 Pages 得 Pro 及以上,Free 账户开不了。另外公开仓库用 GitHub 托管 的标准 runner 不消耗 Actions 额度——如果你正被额度困扰,这也算个理由。