GitHub PagesでPRごとにサイトのプレビューをできるようにActionsを設定する
やりたいこと
GitHub Pagesの公開元リポジトリで、PRを作成したら自動でプレビューサイトをdeployしてほしい!
GitHub PagesでのWebサイト構築でもステージング環境が欲しくなったので、自分なりに調べたり試してみた結果をまとめてみたいと思います。なお、参考サイト様に大いに助けられております。
無理やり感は否めませんが、結果的に目的は達成できたので参考になれば幸いです。ツッコミもお待ちしています!
前提
- 筆者はGitHub Pages/Actions初心者です
- GitHub Pagesを使用してWebサイトを構築しています
参考)GitHub Pages について - GitHub Docs - WebサイトはNext.JSで作成し、パッケージマネージャーはbunを使用していますが、他の構成でも同じようなフローで作成できると思われます(未検証)
概要
今回作成したフローとブランチ構成は図のようになります:
ブランチ構成
このワークフローを適用するためには、特定の役割をもつブランチを2つ用意する必要があります。その他ブランチと合わせて3種類のブランチに分けられます。
-
main
: 仮想的な本番環境ブランチ
ふつうにexample.github.io
にアクセスしたときに見えるページのソース。
デフォルトのブランチにすると見通しが良いと思います。今回はデフォルトブランチとして話を進めます。 -
gh-pages
: デプロイのためだけのブランチ
.github/workflows/以外はビルド後のソースが入る。main
ではなくこちらをデプロイする。 -
feature1
,feature2
,...: その他の普通のブランチ
↑これらのブランチからPRを作成したとき、ステージング環境としてデプロイしたい
main
,gh-pages
は、基本的に名前を固定する前提です(この2つでなくてもOK)。具体的には、この名前をワークフローファイルに直接書きます。
ワークフロー
ワークフローも3種類作成しました。やっていることは以下のとおりです。
-
preview.yml
: PRが作成されたら、自動でプレビューをデプロイ -
deploy.yml
: PRがマージされたら、更新されたソースをデプロイしてWebサイトを更新 -
gh-pages.yml
:gh-pages
をデプロイするためだけのワークフロー(練習のため書きましたが、Settingsを変更すればいらなくなるはずです。詳しくは補足を参照)
Next.JS側の設定
まず、next.config.mjs
に以下のような設定を追加します。
const isPreview = process.env.IS_PREVIEW === "true";
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
// Configure basePath and assetPrefix to support preview deployments
basePath: isPreview ? `/pr-preview-${process.env.PR_NUMBER}` : "",
assetPrefix: isPreview ? `/pr-preview-${process.env.PR_NUMBER}/` : "",
};
// Export the configuration
export default nextConfig;
basePath, assetPathについての解説は公式ドキュメントに譲ります。
補足
お忙ぎの方は読み飛ばしてOKです!
今回はActionsのワークフローを書く練習として、単なるブランチgh-pages
からデプロイするワークフローgh-pages.yml
を書きました。ただ、普通はそんなことしなくとも以下のようにすればgh-pages.yml
は不要になります(検証済)
リポジトリのSettings>Pages 今回のはSourceを"Actions"にしていますが……
本来はSourceを"gh-pages"(デプロイ元ブランチ)にすれば事足りるはず
ワークフローの解説
1. preview.yml
ソース全体はこちら:
要所を解説します。
on:
pull_request:
types: [opened, synchronize, reopened, closed]
ここでは、ワークフローの対象となるトリガーイベントを設定します。
env:
PAGE_DIR: pr-preview-${{ github.event.pull_request.number }}
gh-pages
ブランチ内で、プレビューサイトを格納するディレクトリ名を環境変数として設定します。
jobs:
build-and-commit:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
......
- name: Build with Next.js
run: |
export IS_PREVIEW=true
export PR_NUMBER=${{ github.event.pull_request.number }}
bunx next build
ここまではNext.JSのビルド+αだけです。
+αは環境変数の追加です。IS_PREVIEW
はプレビューサイトか否か、PR_NUMBER
はそのままPRのIDです。どちらもnext.config.mjs
で参照する必要があるので、ビルド前にexportしておきます。
- name: Move out directory
run: mv out /tmp/out
- name: Checkout gh-pages
uses: actions/checkout@v4
with:
ref: gh-pages
- name: Commit changes to gh-pages branch
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "action@github.com"
cp -r /tmp/out/* ${{ env.PAGE_DIR }}
git add .
git commit -m "Deploy PR preview for #${{ github.event.pull_request.number }}"
git push origin gh-pages
まず、ビルドしたソースが格納されているout
を、ホームディレクトリ以外の場所tmp/out
などに移動しておきます。
そうしたら、gh-pages
ブランチにチェックアウトして、tmp/out
の中身をPAGE_DIR
で設定したディレクトリに入るようにします。ここはcpでもmvでも問題ないと思います。
変更をコミットし、プッシュします。
このpushも、gh-pages.yml
のトリガーにもなっているので、自動でデプロイされます。
また、git config --global ...
の2行は単にコミットするユーザーをbotに設定しているだけです。Actionsで自動化した作業は、actions-userくんがやったことにして分かりやすくしています。
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview deployed to: https://${context.repo.owner}.github.io/${process.env.PAGE_DIR}`
})
PRに、プレビューサイトのURLをコメントとして残しておきます。
PRのページを開くと一番下にこのコメントが来て、ワンクリックで飛べるようになっています。
cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages
uses: actions/checkout@v4
with:
ref: gh-pages
- name: Remove preview directory
run: |
rm -rf ${{ env.PAGE_DIR }} || exit 0
git config user.name github-actions
git config user.email github-actions@github.com
git add .
git commit -m "Remove PR preview for #${{ github.event.pull_request.number }}"
git push origin gh-pages
PRがクローズされたら、プレビューを消したコミットをgh-pages
にプッシュします。
2. deploy.yml
ソース全体はこちら:
こちらも要所を解説します。
on:
push:
branches: ["main"]
workflow_dispatch:
こちらのトリガーは、mainブランチへのpush(commit)です。
jobs:
build-and-commit:
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v4
......
- name: Build with Next.js
run: bunx next build
preview.yml
同様、ビルドします。プレビューではないので、環境変数は設定しません。
- name: Move out directory
run: mv out /tmp/out
- name: Checkout gh-pages
uses: actions/checkout@v4
with:
ref: gh-pages
fetch-depth: 0
ここもpreview.yml
同様、out
ディレクトリを退避させ、gh-pages
ブランチをチェックアウトします。
- name: Preserve specific directories and clear other files
run: |
mkdir -p /tmp/preserve
find . -maxdepth 1 -type d -name 'pr-preview-*' -exec cp -r {} /tmp/preserve/ \;
if [ -d .github/workflows ]; then
mkdir -p /tmp/preserve/.github
cp -r .github/workflows /tmp/preserve/.github/
fi
続いて、pr-preview-*
ディレクトリ、.github
ディレクトリを退避しています。ここでは/tmp/preserve/
に入れています。
git rm -rf .
git clean -fxd
shopt -s dotglob nullglob
mv /tmp/preserve/* . || true
shopt -u dotglob nullglob
一旦現在のブランチにあるファイルを削除します。これらは前のmain
ブランチのビルド結果だからですね。その後、/tmp/preserve/
以下を戻します。
- name: Copy new build
run: |
cp -r /tmp/out/* .
touch .nojekyll
その後、新しくビルドされたファイル群をブランチに持ってきます。
- name: Commit changes to gh-pages branch
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "action@github.com"
git add -N .
set +e
git diff --exit-code --quiet
if [[ $? -eq 1 ]]; then
git add .
git commit -m "Deploy to gh-pages: ${{ github.sha }}"
git push origin gh-pages
fi
3. gh-pages.yml
ほぼほぼサンプル(Next.JS)のdeploy部分のコピーなので解説はほとんど省略しますが、1点だけ。
concurrency:
group: "pages"
cancel-in-progress: true
cancel-in-progress
をtrue
にすることで、前のデプロイが終わらないまま次のデプロイジョブが渡された場合に前のデプロイをキャンセルできます。
私の運用の場合、main
にPRをマージした際に、deploy.yml
によるプッシュと、PRのクローズをトリガーとしたpreview.yml
によるph-pages
へのプッシュがバッティングすることがほとんどです。
バッティング自体をなくすのは一旦後回しにして、バッティングした場合に古いデプロイを待つ時間を短縮しました。
ソース全体はこちら:
使ってみる
既存のリポジトリに適用してみます。
まずはじめに、gh-pages
ブランチは手動でセットアップしておく必要があります。
-
gh-pages
ブランチを作成し、リモート上に発行 -
gh-pages.yml
を使う場合、.github/workflows/gh-pages.yml
として作成しておく
また、設定の変更もしておきます。
リポジトリのSettingsタブで、Environment>Github pagesを以下のように設定しておきます。
gh-pagesブランチ(デプロイ元)を許可しておく
それでは、main
から適当なブランチを切り分けて、コミットを作成したらPRを作ってみます。
今回はZennのリンクを追加するためのブランチ・コミットを作成しました。
Actionsが動いて、成功すれば以下のようにプレビュー用のURLがコメントされます。
プレビューのURLへ飛んでみると…
変更が反映されていることがわかります。
マージすると…(Actionsのタブから確認できます)
こちらでも変更が確認できますね〜
今後の展望
- Rulesetまでは作れていないので、デプロイが成功したらマージ可能にするなどの制約をつけたい
- もう少しスマートな方法が思いついたらやってみたい
参考サイト
大いに参考にさせていただきました。この場を借りて感謝申し上げます。
- GitHub Pagesでフロントエンド開発のステージング環境を手軽に構築する(翻訳)|TechRacho by BPS株式会社
- Next.jsで静的ビルドしたソースコードをサブディレクトリパスにデプロイする方法
- Next.jsで作ったサイトをGitHub Pagesで公開する際の設定4点(.nojekyllの追加、ルートの変更、exportのディレクトリ変更、trailingSlashの設定) 👀 - みかづきブログ・カスタム
- Git での新規ファイル作成を含んだファイル変更有無の判定方法 - reboooot․net
- GitHub Actions上でgit commitするときにgit userをどうするか #GitHubActions - Qiita
Discussion