Hugoで1からテーマを作ってGitHub Pagesにデプロイする
この記事は「株式会社メンバーズ Jamstack研究会主催 Advent Calendar 2023」の3日目の記事です。
はじめに
世の中にはさまざまな静的サイトジェネレータがありますが、その中で異彩を放っているのがHugoです。HugoはGoで書かれており、1000件以上の記事があっても数秒でビルドできるスピードと、テンプレートベースでありながら、柔軟性の高さが魅力です。
そのHugoは日本でもよく使われていますが、日本語でまとまった情報はまだまだ少なく、公式サイトの英語のドキュメントを読み取る必要があります。
そのため、1からサイト・テーマを作成して(既存のテーマを使用する方法も記載)、 GitHub Pages にデプロイするまでを詳しく説明したものを1本の記事にまとめてみました。
前提条件
本記事ではHugoのインストール方法については説明しません。また、 Gitの知識および、GitHubのアカウントを持っていることを前提とします。必須ではありませんが、.gitignoreの作成にgiboを利用しています。
コマンドおよびテンプレートはHugo 0.120.4で確認しています。
1. サイトの作成
まずは Quick Start の手順通り、新しいサイトを作ります。
# 新しいサイトの作成
hugo new site quickstart
cd quickstart
# Gitリポジトリの作成
git init
git commit --allow-empty -m "initial commit"
# Hugo向けの.gitignoreを作成
gibo dump Hugo > .gitignore
なお、 git commit --allow-empty は自分の好みで、最初のコミットを空コミットにするために付けています。
hugo new site コマンドを実行すると、2つのファイルが作成されます。
archetypes/default.md
hugo.toml
2. デフォルトテンプレートの変更
まず前者の archetypes/default.md について解説します。これは新規コンテンツのテンプレートファイルです。詳細は Archetypes を参照してください。
このファイルの中身は次のようになります。この "+++" で囲まれたものを TOML Front Matter と言い、コンテンツのメタデータがはいります。
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++
しかし通常のMarkdownファイルではYAML Front Matterが使われます。相互互換性のため、次のように変更することをお勧めします。
---
title: '{{ replace .File.ContentBaseName "-" " " | title }}'
date: {{ .Date }}
draft: true
---
このファイルの中身について解説します。
titleは記事のタイトルに使われます。"{{" と "}}" で囲まれたものがHugoのテンプレートの書式で、.File.ContentBaseName関数を使ってコンテンツファイルのベース名を取得し、replace関数を使ってハイフンをスペースに置き換え、title関数を使って各単語の最初の文字を大文字にしています。
dateはページの日付が入りますが、新規コンテンツ作成時には現在の日時が入ります。
draftはドラフト記事かどうかです。この値がtrueだとビルド時にファイルが生成されません(-Dオプションを使うとドラフト記事でもビルド対象になります)。
3. Hugoの設定変更
次に後者の hugo.toml について解説します。
hugo.tomlはTOMLと呼ばれる設定ファイル向けのフォーマットです。Windowsでよく使われるINIファイルと似た形式です。
この hugo.toml を次のように変更してください。
baseURL = "https://example.org/"
languageCode = "ja"
title = "Yボタンがネギに見える"
disableKinds = ["RSS", "sitemap"]
[taxonomies]
tag = "tags"
内容について説明します。詳細は Configure Hugo を参照してください。
baseURL = "http://example.org/"
サイトのベースとなるURLを指定します。後でもう一度説明しますが、 GitHub Pages では次の2種類のURLが使われます。
ユーザ or 組織のページ: https://USERNAME.github.io
プロジェクトのページ: https://USERNAME.github.io/PROJECT/
http://example.org/blog/ のようにパス指定するとテーマによっては対応していない可能性があるため、できれば http://blog.example.org/ のような独自ドメインの使用をオススメします。
languageCode = "ja"
言語を指定します。デフォルトは “en-us” ですが、日本語にしています。この languageCode はRSSに埋め込まれます。また、多言語対応で使われます。
title = "Yボタンがネギに見える"
サイトのタイトルです。適宜変更してください。
disableKinds = ["RSS", "sitemap"]
index.xml(RSS)、sitemap.xml を作成しなくなります。作成されるファイルを最小限にするだけなので、必要に応じて設定してください。
[taxonomies]
tag = "tags"
Hugo では taxonomy(タクソノミー、分類法) という概念があります。 このtaxonomyは「単数形 = "複数形"」という形で定義します。デフォルトではカテゴリとタグの2つがありますが、タグのみ使うように設定しています。
詳細は Configure Taxonomies を確認してください。
4. 記事を作成
次のコマンドで記事を作成します。
# 記事を3つ作成
hugo new article/hello-world.md
hugo new article/hello-hugo.md
hugo new _index.md
記事の内容は次の通りです。2つの --- で囲まれたものは YAML Front Matter と呼ばれ、YAMLで記事のメタデータを設定します(TOMLも使用可能)。
content/article/hello-hugo.md を開き、次のように変更してください(dateは公開日として使用されるため、適宜変更してください)。
---
title: "Hello Hugo"
date: 2023-11-10T17:39:39+09:00
tags: ["hello", "hugo"]
---
Hello Hugo!
content/article/hello-world.md を開き、次のように変更してください(dateは公開日として使用されるため、適宜変更してください)。
---
title: "Hello World"
date: 2023-11-10T17:39:39+09:00
tags: ["hello", "world"]
---
Hello World!
content/article/_index.md という名前で次の内容のファイルを作成してください。ここではテーマでタグを使用するため、 tags: の設定を行っています。また、デフォルトで付けられる draft: true を取り除いています。
---
title: ""
date: 2023-11-10T17:39:39+09:00
---
私のサイトにようこそ!
なお、 date: は時刻やタイムゾーンを省略して date: 2020-05-07 のように書けます。ただしこの場合はタイムゾーンがUTCとなり(Issue)、日本時間とは9時間ずれるため、書いた記事が表示できない可能性があります(後で書くように、 -F オプションをつけることで未来の記事もビルドできます)。
5. ローカルでの動作確認
次のコマンドを打つと、ローカルでサーバが起動します。
hugo serve
このサーバには、次のURLでアクセス可能です。ただしこの時点ではテーマがないため何も表示されません。
http://localhost:1313
なお、設定で baseURL でパス指定している場合は、ローカルで表示する場合にもパス指定が必要です。例えば baseURL = http://example.com/blog/ の場合、 http://localhost:1313/blog/ でアクセスする必要があります。
このローカルサーバ起動時にはオプションが付けられます。自分は次のオプションをよく使います。
-D: ドラフト記事(draft: true)も処理
-F: 未来の記事も処理
--bind=0.0.0.0: 同じネットワーク内の他のマシンからもアクセス可能にする
--disableFastRender: 変更時に完全な再レンダリングを行う(テーマの作成時に有用)
全部付けると次のようになります。
hugo serve -D -F --bind=0.0.0.0 --disableFastRender
最終的にHTMLを作成するためには、次のように serve を除いてください。-D, -F オプションはサーバ起動時と同様に使用可能です。
# オプションなしの場合
hugo
# オプションありの場合
hugo -D -F
なお、hugo serveでサーバを起動、hugoでビルドしたときに .hugo_build.lock というファイルが作成されます。このファイルはv0.89.0から導入されており、コマンド間で競合しないために使われているようです。
このファイルを .gitignore に入れても構いませんが、今回はコミットする方向で対応します。
ここまで完了したら、一度コミットしておきます。
git add -A
git commit -m "new site"
6. テーマの導入
ここからテーマを作っていきますが、その前に既存のテーマを使う場合の手順を記載します。
新規にテーマを作るときは、次の「6.2. テーマを作成」までスキップしてください。
6.1. 既存のテーマを使う場合
有志が作ったテーマが次のサイトに公開されています。
例えば Ananke Gohugo Theme を使う場合は次のコマンドを打ちます(見た目は2つのコマンドに見えますが、1つのコマンドです)。
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
Hugoのテーマはデフォルトでは themes/<テーマ名> ディレクトリを使用します。このディレクトリに、Gitのサブモジュールという機能を使って、外部リポジトリを取り込んでいます。
そしてテーマ名を設定ファイルに追加します。
baseURL = "http://example.org/"
languageCode = "ja"
title = "Yボタンがネギに見える"
disableKinds = ["RSS", "sitemap"]
theme = "ananke" # この行を追加
[taxonomies]
tag = "tags"
hugo serve でサーバを立ち上げてアクセスすると、次のような画面が表示されます。
ここまで完了したら、一度コミットしておきます。
git add -A
git commit -m "add theme"
「7. GitHub Pagesへのデプロイ」に進んでください。
6.2. テーマを作成
ここでは、新規にテーマを作成する方法を説明します。
6.2.1. テーマの仕組み
Hugoのテーマはテンプレートファイルと、その他のリソース(画像など)から成ります。Hugoのテンプレートはいくつかの特徴を持ちます。
ページの種類によってテンプレートの参照順序が決まり、最初にマッチしたものが使われる
ベーステンプレートを書き、それを拡張していく
テーマと同じパスのファイルを layouts 以下に置くことで、処理を上書きできる
まず、Hugoのテンプレートはページの種類によってテンプレートの参照順序が決まり、最初にマッチしたものが使われます。
例えばタグ一覧の場合、 terms.html を探し、見つからなければ list.html を見に行きます。また、タグに紐づくページ一覧の場合は taxonomy.html を探し、見つからなければ list.html を見に行きます。
トップページの場合、 index.html を探し、見つからなければ home.html を探し、さらに見つからなければ list.html を見に行きます。
実際はもっと柔軟(複雑)です。興味がある方は Hugo’s Lookup Order を参照してください。
次に、Hugoには「ベーステンプレート」というものがあります。これは共通となるHTMLを提供し、差分だけ埋めていくためのものです。ベーステンプレートを使わなくてもテンプレートは作れますが、開発効率が上がるため、使うことをおすすめします。
最後に、テーマと同じパスのファイルを layouts 以下に置くことで、処理を上書きできます。
例えば themes/<テーマ名>/layouts/_default/single.html の処理を一部変更したい場合、layouts/_default/single.html というファイル名でコピーし、書き換えればOKです。
逆に、テーマを使わずに layouts ディレクトリ内にテンプレートを直接置くことも可能です。テーマを再配布する予定がなければ、この方法で十分でしょう。今回はこの機能を使用します。
6.2.2. ベーステンプレート
まずベーステンプレートを作ります。
このベーステンプレートには、どのページでも使われる大まかな枠組みを定義します。
次の内容を layouts/_default/baseof.html というファイル名で作成してください。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ block "title" . }}{{ end }}</title>
</head>
<body>
{{ "<!-- _default/baseof.html start -->" | safeHTML }}
<ul>
<li><a href="{{ relURL "/" }}">トップ</a></li>
<li><a href="{{ relURL "/article/" }}">記事</a></li>
<li><a href="{{ relURL "/tags/" }}">タグ</a></li>
</ul>
<hr />
{{ block "main" . }}{{ end }}
{{ "<!-- _default/baseof.html end -->" | safeHTML }}
</body>
</html>
{{ block "title" . }}{{ end }}、 {{ block "main" . }}{{ end }} という風に、ページによって変わる箇所をブロックとして定義します。ドットを入れるのを忘れないでください。これは現在のコンテキストを渡すために使われています。
また、 {{ relURL "/" }} という記載がありますが、これは baseURL から見た相対パスを生成する方法です。引数で渡す他に、 {{ print "/" | relURL }} のようにフィルタのような使い方もできます。
なお、 {{ "<!-- _default/baseof.html start -->" | safeHTML }} という書き方がありますが、これはデバッグ用にHTMLコメントを追加しています。
6.2.3. その他のテンプレートファイル
Hugoのテンプレートは参照順序が決まっていますが、HTMLの場合必ず single.html または list.html が使われます。そのため、 baseof.html, list.html, single.html の3ファイルがあればテーマは作れます。
しかし自分の経験上、次のようにページの種類によって分けた方が開発しやすいです。
layouts/_default/baseof.html: ベーステンプレート
layouts/_default/list.html: セクションの記事一覧
layouts/_default/single.html: 個々の記事の内容
layouts/_default/taxonomy.html: 個々のタグ
layouts/_default/terms.html: タグ一覧
layouts/_default/index.html: トップページ
ベーステンプレートは説明したため、次にリストテンプレートを作ります。これは taxonomy.html, terms.html がないときに使われると同時に、同じディレクトリ(セクション(Section))にある記事のリストを表示するためにも使われます。
なお、 {{- .Title -}} の前後の - は、空白(改行含む)を制御して、ソースコードをキレイに見せるためにつけています。 {{ の直後に - をつけると、 {{- の前の文字までの空白が除去されます。同様に -}} とすると、その後の空白が除去されます。
layout/_default/list.html という名前で次の内容のファイルを作成してください。
{{ define "title" }}
{{- .Title -}}
{{ end }}
{{ define "main" }}
{{ "<!-- _default/list.html start -->" | safeHTML }}
<ul>
{{ range .Pages -}}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
{{ "<!-- _default/list.html end -->" | safeHTML }}
{{ end }}
次に、シングルテンプレートを作ります。これは個々の記事、個々のページに使われます。この例ではタグ一覧を表示し、リンクを付けています。
layout/_default/single.html という名前で次の内容のファイルを作成してください。
{{ define "title" }}
{{- .Title -}}
{{ end }}
{{ define "main" }}
{{ "<!-- _default/single.html start -->" | safeHTML }}
<ul>
{{ range .Params.tags -}}
<li><a href="{{ print "/tags/" . | relURL }}/">{{ . }}</a></li>
{{ end -}}
</ul>
{{ .Content }}
{{ "<!-- _default/single.html end -->" | safeHTML }}
{{ end }}
次に、タクソノミテンプレートを作ります。これは個々のタグに相当するページに使われます。タグを付けたページをリスト表示しています。
layout/_default/taxonomy.html という名前で次の内容のファイルを作成してください。
{{ define "title" }}
{{- .Title -}}
{{ end }}
{{ define "main" }}
{{ "<!-- _default/taxonomy.html start -->" | safeHTML }}
{{ .Content }}
<ul>
{{ range .Pages -}}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
{{ "<!-- _default/taxonomy.html end -->" | safeHTML }}
{{ end }}
次に、タームテンプレートを作ります。これはタグ一覧のページに使われます。タグをリスト表示しています。
layout/_default/terms.html という名前で次の内容のファイルを作成してください。
{{ define "title" }}
{{- .Title -}}
{{ end }}
{{ define "main" }}
{{ "<!-- _default/terms.html start -->" | safeHTML }}
{{ .Content }}
<ul>
{{ range .Pages -}}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
{{ "<!-- _default/terms.html end -->" | safeHTML }}
{{ end }}
最後に、ホームページテンプレートを作ります。これはサイトのトップページに使われます。ページ一覧を表示しています。
layout/_default/index.html という名前で次の内容のファイルを作成してください。
{{ define "title" }}
{{- .Site.Title -}}
{{ end }}
{{ define "main" }}
{{ "<!-- _default/index.html start -->" | safeHTML }}
<h1>{{ .Site.Title }}</h1>
{{ .Content }}
<ul>
{{ range .Site.RegularPages -}}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
{{ "<!-- _default/index.html end -->" | safeHTML }}
{{ end }}
テンプレートを作成したら、 hugo serve でサーバを起動し、次のURLにアクセスしてください。
次のような画面が表示されます。簡素なものですが、テンプレートが完成しました。もし "Recen Articles" が出る場合は、 hugo.toml から theme = "ananke" を消してください。
ここまで完了したら、一度コミットしておきます。
git add -A
git commit -m "add theme"
7. GitHub Pagesへのデプロイ
次の記事を参考に、GitHub Pagesへのデプロイを行います。
GitHub Pagesには、2つのタイプ、3つのデプロイ方法があります。
1つ目のタイプは、ユーザまたは組織のページです。 https://<ユーザ名 or 組織名>.github.io でアクセスできます。
もう1つのタイプは、プロジェクトのページです。 https://<ユーザ名 or 組織名>.github.io/<リポジトリ名> でアクセスできます。
デプロイ方法は次の3つがあります。
任意のブランチ
任意のブランチの docs ディレクトリ
GitHub Actionsによるデプロイ
しかしGitHub Pages サイトの公開元を設定するというページには次のように書かれています。
また、GitHub Pagesの説明画面でも、ブランチからのデプロイは "Classic" Pages experienceとなっています。よって今後はGitHub Actionsのみ使われると思った方がいいでしょう。この記事でもGitHub Actionsの設定のみ記載します。
GitHub Actionsからのデプロイを選択すると次のような画面になります。 今使っているのはHugoのため、"browse all workflows" のリンクをクリックします。
検索画面が出てくるので "hugo" で検索すると、1件出てきました。"By GitHub Actions" ということで、GitHub Actions公式のワークフローのようです。今回はこれを使います。
"Configure" ボタンを押すと、ワークフローの作成画面になります。特に変更せず、そのまま "main" ブランチにコミットします。
コミットすると自動的にGitHub Actionsが動きます。
ビルドが完了したら deploy にあるリンクをクリックしてください。これでサイトが表示されました。
なお、このGitHub Actionsのワークフローでは次のような処理が行われます。使用するHugoのバージョンは環境変数 HUGO_VERSION で指定されます。
hugoのextendedバージョン(Sassなどに対応)インストール
Dart Sassのインストール
リポジトリのチェックアウト
GitHub Pagesの初期化(actions/configure-pagesを使用)
(必要なら)Node.jsのパッケージインストール(npmを使用)
Hugoによるビルド
生成されたファイルのアップロード
GitHub Pagesにデプロイ(actions/deploy-pagesを使用)
おわりに
Hugoには他にもいろいろな機能があります。次に挙げるものはほんの一部です。
partialテンプレート(テンプレートで使われる共通のコード)
ショートコード(コンテンツに埋め込める共通のコード)
多言語対応
データテンプレート(YAMLやJSONなどから出力)
画像処理(リサイズ、回転など)
Hugo Pipes(Sassやminify、esbuildによるバンドルなど)
また、CSSフレームワークを導入して、見栄えのいいサイトを作るのもいいです。いろいろ工夫してみてください。