HugoとGitHub PagesだけでWordPressライクな予約投稿を実現する

このブログはHugo + GitHub Pagesという、いわゆる「静的サイト」構成で動いている。ホスティングは無料でCDNも効くし、サーバーの面倒を見る必要もない。ただしこの構成には、WordPressのような動的CMSなら当たり前にある機能がひとつ欠けている。予約投稿だ。 Hugoはビルド時点の現在時刻より未来のdate:を持つ記事をデフォルトでビルド対象から除外する(buildFuture: false相当の挙動)。裏を返せば、「未来日付の記事をmasterにマージしておいて、その時刻が来たら勝手にサイトに出てくる」ということは、誰かが正しいタイミングでもう一度ビルドを起動しない限り起きない。動的CMSなら公開時刻をDBに書いておいてリクエストのたびに判定すればいいが、静的サイトは「ビルドした瞬間の世界」を切り出して配信しているだけなので、時間の経過そのものをトリガーにできない。 この記事では、GitHub Actionsだけを使ってこの制約を回避し、実際に予約投稿を動かしている仕組みを、詰まった点も含めて書いておく。 全体構成 flowchart TD Author[記事を書く] -->|"date: に未来日時を指定してPR作成"| PR[Pull Request] PR -->|opened / synchronize| DateCheck[correct-manual-post-dates.yaml] DateCheck -->|"date <= 現在時刻なら現在時刻に補正<br/>未来ならそのまま"| PR PR -->|レビュー後マージ| Master[masterにpush] Master -->|pushイベント| HugoBuild[hugo.yaml: ビルド&デプロイ] HugoBuild -->|"未来日付記事は除外"| Pages[GitHub Pages] Cron["Cron: 15分ごと"] -->|起動| Checker[publish-checker.yaml] Checker -->|"content/posts/**/*.md をスキャン"| Due{公開時刻到来?} Due -->|Yes| Dispatch["gh workflow run hugo.yaml"] Dispatch --> HugoBuild DailyCron["Cron: 毎日0時UTC"] -->|安全網| HugoBuild 登場するワークフローは3つ。 correct-manual-post-dates.yaml: PRを開いた/更新した時点で、新規追加記事の日時を補正する hugo.yaml: Hugoでビルドし、GitHub Pagesにデプロイする(未来日付の記事は自動的に除外される) publish-checker.yaml: 15分ごとに全記事をスキャンし、公開時刻が到来した未来日付記事があればhugo.yamlを起動する 1. 記事のdate:と「実際に公開された時刻」を一致させる 普段の運用(予約投稿ではなく、書いたらすぐ公開したい記事)では、date:フィールドに正確な時刻を毎回手で入れるのは面倒だし、書き始めた時刻とマージされた時刻がずれることも多い。そこで、PRのopened/synchronizeイベントをトリガーに、新規追加されたMarkdownファイルのdate:を機械的に補正するワークフローを用意した。 - name: Correct publish dates of added posts run: | files_to_check=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/files" \ --paginate --jq '.[] | select(.status=="added") | .filename' \ | grep -E '^content/posts/.*\.md$' | grep -v '_index.md' || true) if [ -n "$files_to_check" ]; then git fetch origin "$BRANCH" git checkout -B "$BRANCH" origin/"$BRANCH" python3 scripts/correct_publish_dates.py $files_to_check # ... 差分があれば github-actions[bot] としてPRブランチにコミット&push fi ポイントは3つ。 ...