何をするか?
こことは違う Web サイト (GitHub Pages) の話ですが、いい加減 Jekyll での Web サイト管理は限界 になってきたので、重い腰を上げて全面的に Hugo に乗り換えようと思います。 待っていれば Jekyll も高速化されるかなという淡い期待を抱いて 3 年が経ちましたが、一向にその気配はないので、残念ですがここで Jekyll は見限ります(決して Ruby と Golang の速度の差だけだとは思いませんが、やはり Golang 製のツールの方が高速な傾向はあるのかも)。
とはいえ、現状 Jekyll で管理している Web ページは何千ページにも膨れ上がっており、一気に乗り換えるのは困難です。
そこで、Jekyll 運用されている Web サイトに、少しずつ Hugo 管理のページを加えていく、という作戦を取りたいと思います。
ドメイン名はそのまま xxx.github.io
を使いたいので、ホスティングはこれまで通り GitHub Pages で行い、ビルドやデプロイには GitHub Actions を使うことにします。
基本方針
GitHub Pages のデフォルトの仕組みとして、Markdown ファイルをソース用リポジトリに置いておくだけで、Jekyll で HTML に変換して公開してくれるというのがあるのですが、今回はこの仕組みは使えません。 なぜなら、Jekyll によるビルド結果と、Hugo によるビルド結果をマージしたものをデプロイしなければいけないからです。 イメージとしては、GitHub リポジトリに push したときに、GitHub Actions で次のような処理を実行できればうまくいくはずです。
- Jekyll プロジェクトの Markdown ファイルをビルドする
- Jekyll のビルド対象から Hugo のディレクトリを除外しておきます
- Hugo プロジェクトの Markdown ファイルをビルドする
- Hugo プロジェクトのビルド結果を Jekyll プロジェクトのビルド結果にマージする
- 同名のファイルは上書きしないことにします(先輩である Jekyll の出力を優先)
- GitHub Pages のホスティング先にデプロイ
- ここでは
gh-pages
ブランチを公開ブランチとして使います
- ここでは
Hugo のプロジェクトをフラットに配置してしまうと、content
ディレクトリや layouts
ディレクトリが混在してわかりにくくなるので、Hugo 側のファイル群は、hugo-files
ディレクトリを作ってそこに配置することにします。
最終的に、Jekyll から Hugo への以降がすべて完了したら、この中のファイル群をルートに移動させる想定です。
設定作業
GitHub Actions を設定し、Jekyll と Hugo の共存プロジェクトをビルドできるようにしていきます。
gh-pages をデプロイ先に設定
GitHub Pages のソースディレクトリが gh-pages
ブランチになっていない場合は、対象の GitHub リポジトリのページから Settings
→ Pages
と辿り、次のように変更しておきます。
- Source:
Deploy from a branch
- Branch:
gh-pages
ブランチの選択肢に gh-pages
が出てこない場合は、次のように作成しておきます。
# 親なしのブランチとして gh-pages を作成する
$ git switch --orphan gh-pages
# 適当な index.html をコミット
$ echo Hello > index.html
$ git add index.html
$ git commit -m "First commit"
# GitHub へ gh-pages ブランチをプッシュ
$ git push --set-upstream origin gh-pages
GitHub Actions で Jekyll ビルド
GitHub Actions で Jekyll サイトをビルドする設定の例は、Jekyll の GitHub Actions 説明ページ に一応書かれていますが、今回のように Jekyll と Hugo を共存させる場合には同じ設定は使えないので参考程度にとどめます。
GitHub Pages + Jekyll の環境で Web サイトを構築している場合は、RubyGems の Bundler 設定 (Gemfile
) は次のような感じになっていると思います。
ここはおそらくそのままで大丈夫です。
GitHub Actions 用の Workflow ファイルを作成し、main ブランチへのプッシュをトリガーとして Jekyll ビルドが自動実行されるようにします。 この段階では Hugo のビルドは行わず、まずは Jekyll のビルドとデプロイまでを設定します。 一歩ずつ一歩ずつ…。
GitHub Pages のデフォルトの振る舞いでは、ソースブランチ(今回は gh-pages
)内のファイルを自動的に Jekyll で処理しようとします。
今回は、GitHub Actions で明示的にビルドするので、これを抑制するために gh-pages
ブランチのルートに .nojekyll
ファイルをおく必要がある のですが、上記の例のように peaceiris/actions-gh-pages
を使っている場合は、自動的にこの処理を行ってくれます。
ここまで準備できたら、GitHub にプッシュして GitHub Actions が起動するかを確認します。 Jekyll ビルドとデプロイが完了したら、Web サイトにアクセスできるようになっているはずです。
GitHub Actions で Hugo ビルド
いよいよ、Jekyll のプロジェクトの中に、Hugo のプロジェクトを共存させます。
リポジトリのルートに、hugo-files
というディレクトリを作って、Hugo プロジェクトはそこで管理していきます。
共存させたい Hugo プロジェクトがすでに存在する場合は、そのディレクトリごとリポジトリルートにコピーして、hugo-files
という名前にリネームしておきます。
Hugo プロジェクトがまだ存在しない場合は、次のような感じで作成します。
$ hugo new site hugo-files
Hugo コンテンツの具体的な作り方はここでは割愛しますが、シンプルな構成例を下記 GitHub リポジトリにコミットしてあるので参考にしてください。
hugo-files
という Hugo 管轄のディレクトリを追加したので、Jekyll ビルド時には、このディレクトリを無視するように設定しておきます。
GitHub Actions の Workflow ファイルを修正して、Hugo のインストールとビルドを行うようにします。
ここでは、いったん Hugo 用のビルド結果はデフォルトのまま hugo-files/public
ディレクトリに出力しておき、ビルド後にリポジトリルートの _site
ディレクトリに中身をマージすることにします。
このように段階的に出力結果をマージすることで、同名ファイルの扱いを制御しやすくなります。
Linux の cp
コマンドで -RT
オプションを指定することで、hugo-files/public
ディレクトリの中身だけをまるごと _site
へコピーしています(ちなみに、macOS の cp
コマンドの場合は BSD 版なので -T
オプションが存在せず、代わりに cp -Rn hugo-files/public/ _site
のように、src ディレクトリ名に /
サフィックスを付けます)。
さらに、cp
コマンドの -n
オプションを指定することで、コピー先に同名のファイルがある場合に上書きしないようにしています。
おそらく、トップページ用の index.html
は両方のビルド結果として出力されていますが、今回は Jekyll の方のファイルを採用することになります(Hugo の方のトップページを使わないのであれば、最初から hugo
コマンドのオプション で --disableKinds home
を指定して出力しないのがよいかもしれません)。
サイトマップファイル (sitemap.xml
) を両方のプロジェクトで生成している場合は、どちらのファイルもデプロイしたいので、次のような感じで Hugo 側のファイルをリネームしてコピーしておきます。
サイトマップファイルが 2 つに分かれるので、Google Search Console などでサイトマップを送信する場合は、2 つの XML ファイル(sitemap.xml
と sitemap-hugo.xml
)を指定することになります。
- name: Rename and copy Hugo's sitemap.xml
run: cp hugo-files/public/sitemap.xml _site/sitemap-hugo.xml
これで、Hugo と Jekyll の共存環境は完成です。 GitHub リポジトリに Markdown コンテンツをプッシュするたびに、両方のビルド結果がマージされてホスティングされるようになります。 最終的な Workflow ファイルはこちら を参照してください。
(おまけ)普段の記事の執筆中はどうするか?
GitHub Actions で Jekyll と Hugo を同時にビルドするときはよいのですが、ローカルサーバーを起動して記事を執筆しているときはどうやって共存させればよいでしょうか?
デフォルトでは、Jekyll のローカルサーバー (bundle exec jekyll serve
) は 4000 番ポートで動作し、Hugo のローカルサーバー (hugo serve
) は 1313 番ポートで動作します。
-p
オプションでポート番号を変更することはできますが、TCP/IP の仕様上、同じポート番号でサーバーを立ち上げることはできません。
よって、どちらかのサーバーに相乗りする形で動作させる必要があります。
ここでは、Jekyll のローカルサーバーが _site
ディレクトリを使う性質を利用して、Hugo のコンテンツをそこに随時放り込むという方法をとってみます。
まず、Jekyll のローカルサーバーを起動します。
ここでは、--baseurl ""
オプションを指定して、http://localhost:4000
というシンプルな URL でトップページにアクセスできるようにしています。
これは、GitHub のリポジトリ名に合わせて、Jekyll の設定ファイル (_config.yml
) で baseurl: "/jekyll-with-hugo"
のような設定をしているからです。
baseurl をクリアしておかないと、トップページにアクセスするときに、http://localhost:4000/jekyll-with-hugo/
という URL を使わないといけません。
次に、Hugo のビルドコマンドを、Watch モードで起動します。 Hugo はサーバーとしては起動しないところがポイントです。
-w
(--watch
) オプションを付けて Hugo ビルドを実行すると、記事ディレクトリ (content
) を監視して随時ビルドをかけてくれるようになります。
そのビルド結果は --destination ../_site
という指定により、Jekyll がホスティングしているディレクトリ (../_site
) にマージされていきます。
さらに、--disableKinds home
というオプションで、Hugo 側のトップページ (index.html
) の出力を抑制し、Jekyll 側のトップページが上書きされてしまうのを防いでいます。
これで、ブラウザで http://localhost:4000
を開いたまま、記事の執筆を進めることができます。
ただ、ちょっと強引な組み合わせ方をしているので、いくつか問題もあります。
- Jekyll 側の記事を更新すると
_site
内の Hugo 記事が消されてしまう- これは Jekyll が出力をきれいにしてくれるための機能なので、この振る舞いを変えることはできないみたいです。もし、Hugo 側の記事が必ず
p
ディレクトリ以下に出力されるなど決まっているのであれば、_config.yml
にkeep_files: ["p"]
と記述しておけば、それらの記事だけは消去されずに済みます。
- これは Jekyll が出力をきれいにしてくれるための機能なので、この振る舞いを変えることはできないみたいです。もし、Hugo 側の記事が必ず
- Live Reload などの機能が使えない
- Jekyll の出力先ディレクトリに直接ファイルを突っ込んでいるので、記事の修正を検出してくれないのは仕方ないですね…。
まぁこのあたりは、Hugo への完全移行が終わるまでの辛抱です。
関連記事
- GitHub Actions で Hugo サイトをビルドして VPS サーバーに rsync デプロイする
- GitHub Pages で Jekyll による自動変換を無効化する (.nojekyll)
- GitHub Pages は早く Jekyll から Hugo に乗り換えるべき
- Next.js アプリを GitHub Actions でビルドして GitHub Pages で公開する
- GitHub Pages で React Router を使った SPA サイトを動かす方法
- GitHub Actions で Web サイトをビルドして GitHub Pages へ公開する
- Parcel でビルドした Web サイトが GitHub Pages で動作しないとき