用 gh 命令行配 GitHub Pages
用 gh 命令行配 GitHub Pages:页面能开,图全裂
给一个静态站配 GitHub Pages,我想全程不碰网页设置。gh 能做到,三条命令
的事。但第一次部署完打开一看——页面正常,四张截图全裂。
问题不在 Pages,在我没意识到站点跑在子路径下。这篇把过程和那个坑记下来。
为什么不点网页
不是为了炫技。网页点出来的配置没法进版本库,换个仓库要重新点一遍,也说不清 当初为什么那么选。命令行写下来的东西可以复制、可以 diff、可以给别人。
启用
gh 没有 gh pages 子命令,得用 gh api 打 REST 接口:
gh api -X POST repos/OWNER/REPO/pages -f build_type=workflowbuild_type=workflow 是关键:告诉 GitHub 由 Actions 部署,而不是传统那种
"从 gh-pages 分支或 docs/ 目录发布"。后者还得单独维护一个分支,很别扭。
返回里确认两个字段:
{ "build_type": "workflow", "html_url": "https://OWNER.github.io/REPO/"}注意 html_url 末尾那个 /REPO/。这就是后面翻车的原因,当时我扫了一眼
就过去了。
部署工作流
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 主版本号很新但运行 时还是旧的。逐个查:
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/,站点跑在
子路径下。而我模板里写的是绝对路径:
<img src="/shots/wbs.png">浏览器于是去请求 https://OWNER.github.io/shots/wbs.png——少了 /REPO,404。
我原以为构建工具会处理。不会。 Astro 的 base 只影响它自己生成的引用
(打包后的 CSS/JS 路径),你手写在模板里的绝对路径它不碰。Vite、Next 也一样。
改法
同一份代码常常要部署到多处——我这个站主站在自己服务器的根路径,Pages 是镜像 在子路径。所以别写死,从环境变量取:
const site = process.env.SITE_URL ?? "https://example.com";const base = process.env.SITE_BASE ?? "/";export default defineConfig({ site, base, output: "static" });模板里加个助手:
---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 就是干这个的。
本地先验,别推上去才发现
两条命令的事:
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 一下首页:
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再确认页面里的路径真的带了前缀:
curl -sL https://OWNER.github.io/REPO/ | grep -oE 'src="[^"]*"' | head -3一个差点误判的插曲
验证时我用浏览器自动化去访问,连接直接被断(ERR_CONNECTION_CLOSED),第一反应
是站点挂了。但同一台机器上 curl 全部 200。
是本地网络到 GitHub CDN 的问题,跟站点无关。别因为一个工具失败就下结论, 换个方式交叉验证一下,能省很多冤枉功夫。
速查
# 看配置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自定义域名的话:
gh api -X PUT repos/OWNER/REPO/pages -f cname=www.example.comDNS 加 CNAME 指到 OWNER.github.io。注意用了自定义域名后站点回到根路径,
base 得跟着变——configure-pages 会自动处理,但如果你在别处写死了,记得改。
顺带一提
私有仓库要开 Pages 得 Pro 及以上,Free 账户开不了。另外公开仓库用 GitHub 托管 的标准 runner 不消耗 Actions 额度——如果你正被额度困扰,这也算个理由。