Docusaurus 跨 Repo 部署到 GitHub Pages:從零到自動化的五個坑
用 Docusaurus 建官方網站很快,但把它接上 GitHub Pages 的自動部署,尤其是「source repo 與 Pages repo 分開」的架構,踩了不少坑。這篇把整個過程與解法記下來,下次不用再查。
架構目標
ignikah-dev/official-website (private, source)
↓ GitHub Actions build
ignikah-dev/ignikah-dev.github.io (public, GitHub Pages)
source repo 保持 private,只有靜態輸出推到 Pages repo。這樣可以把草稿、設計稿、內部文件留在 source,不對外曝光。
坑一:peaceiris/actions-gh-pages 需要 Personal Token,不能用 GITHUB_TOKEN
GITHUB_TOKEN 的 scope 只限於 當前 repo,無法推送到外部的 ignikah-dev.github.io。
必須用 Fine-grained Personal Access Token,在 workflow 裡用 personal_token 參數:
- uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.GH_PAGES_DEPLOY_TOKEN }}
external_repository: ignikah-dev/ignikah-dev.github.io
publish_branch: main
publish_dir: ./build
force_orphan: true
坑二:Fine-grained PAT 的 Resource Owner 必須是 Org,不是個人帳號
第一次建 token 時,Resource Owner 選了個人帳號(balnibarbian),結果:
remote: Permission to ignikah-dev/ignikah-dev.github.io.git denied to balnibarbian.
fatal: unable to access '...': The requested URL returned error: 403
即使把 balnibarbian 加為 collaborator 也沒用。根本原因是 fine-grained PAT 的權限是「token 持有者在指定 owner 下的資源」——選個人帳號只能操作個人 repo,組織 repo 需要選 org 作為 Resource Owner。
正確設定:
- Resource owner →
ignikah-dev(org) - Repository access → Only
ignikah-dev/ignikah-dev.github.io - Permissions → Contents: Read and write
坑三:只開 pages 權限不夠,必須開 contents
第一版 token 設了 pages: read,結果還是 403。要能推 commit 到目標 repo,必須開:
Repository permissions
└─ Contents: Read and write ← 這個才是關鍵
pages 權限是控制 GitHub Pages 設定頁,跟推 code 無關。
坑四:Secret 名稱要跟 workflow 對得上
workflow 寫 secrets.PAGES_DEPLOY_TOKEN,但 repo 裡存的是 GH_PAGES_DEPLOY_TOKEN——結果一直出現:
Error: Action failed with "not found deploy key or tokens"
兩邊要一致,選一個名字就好:
personal_token: ${{ secrets.GH_PAGES_DEPLOY_TOKEN }}
坑五:navbar backdrop-filter 讓手機選單消失
這個跟部署無關,但是 CSS 坑,值得記一下。
Docusaurus 的手機側邊選單(.navbar-sidebar)使用 position: fixed。CSS 規範裡,backdrop-filter、filter、transform 加在祖先元素上,會讓該元素成為 fixed 子元素的新 containing block——導致側邊選單的定位從 viewport 變成 navbar 的邊界,整個跑掉。
錯誤做法:
.navbar {
backdrop-filter: saturate(180%) blur(12px); /* 破壞 fixed 子元素定位 */
}
正確做法——把 blur 移到 ::before 偽元素,不影響後代的定位:
.navbar {
position: sticky;
top: 0;
z-index: 300;
background: transparent !important;
}
.navbar::before {
content: '';
position: absolute;
inset: 0;
background: rgba(248, 250, 252, 0.88);
backdrop-filter: saturate(180%) blur(12px);
-webkit-backdrop-filter: saturate(180%) blur(12px);
z-index: -1;
pointer-events: none;
}
最終設定清單
| 項目 | 設定位置 | 值 |
|---|---|---|
| Deploy token | official-website → Secrets | GH_PAGES_DEPLOY_TOKEN |
| Token scope | Fine-grained PAT | Resource owner: org;Contents: R/W |
| Chatwoot token | official-website → Secrets | CHATWOOT_WEBSITE_TOKEN |
| Workflow | .github/workflows/deploy.yml | personal_token: ${{ secrets.GH_PAGES_DEPLOY_TOKEN }} |
設定完成後每次 push 到 main,CI 自動 build + 推到 Pages,約 90 秒後生效。