Hugoで1からテーマを作ってGitHub Pagesにデプロイする

Hugoで1からテーマを作ってGitHub Pagesにデプロイする

はじめに

自分は数ある静的サイトジェネレータの中で、Hugoが一番好きです。1000件以上の記事があっても数秒でビルドできるスピードが魅力的です。

そのHugoは日本でもよく使われていますが、日本語でまとまった情報はまだまだ少なく、公式サイトの英語のドキュメントを読み取る必要があります。

そのため、1からサイト、テーマを作成して(既存のテーマを使用する方法も記載)、 GitHub Pages にデプロイするまでを詳しく説明したものを1本の記事にまとめてみました。

前提条件

本記事ではHugoのインストール方法については説明しません。また、 Gitの知識および、GitHubのアカウントを持っていることを前提とします。

コマンドおよびテンプレートは以下の環境で確認しています。

  • macOS Catalina(10.15.4)
  • Hugo 0.70.0
  • シェル: bash

1. サイトの作成

まずは Quick Start の手順通り、新しいサイトを作ります。

# 新しいサイトの作成
hugo new site quickstart
cd quickstart

# Gitリポジトリの作成
git init
git commit --allow-empty -m "initial commit"

# Hugoが出力するHTMLディレクトリを除外
echo "public" >> .gitignore

なお、 git commit --allow-empty は自分の好みで、最初のコミットを空コミットにするために付けています。

2. 設定変更

生成された config.toml を以下のように変更してください。

baseURL = "http://example.org/"
languageCode = "ja"
title = "Yボタンがネギに見える"
contentDir = "post"
disableKinds = ["RSS", "sitemap"]

[taxonomies]
tag = "tags"

設定について説明します。詳細は Configure Hugo を参照してください。

baseURL = "http://example.org/"

サイトのベースとなるURLを指定します。後でもう一度説明しますが、 GitHub Pages では以下の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ボタンがネギに見える"

サイトのタイトルです。適宜変更してください。

contentDir = "post"

Markdownファイルなどのコンテンツを含むディレクトリを指定します。デフォルトでは content ですが、自分は post にしています。

なぜなら、デフォルトの content ディレクトリを使うと、 config.toml と先頭3文字が一致するため、タブ補完するときに cont<tab> と打たないと補完できません。

一方で post にすると、 p で始まるものは public のみのため、 po<tab> と打つと補完できます。地味に便利です。

disableKinds = ["RSS", "sitemap"]

index.xml(RSS)、 sitemap.xml を作成しなくなります。作成されるファイルを最小限にするだけなので、必要に応じて設定してください。

[taxonomies]
tag = "tags"

Hugo では taxonomy(タクソノミー) という概念があります。 taxonomy は「分類法」という意味です。 単数形 = "複数形" という形で定義します。デフォルトではカテゴリとタグの2つがありますが、タグのみ使うように設定しています。

詳細は Configure Taxonomies を確認してください。

3. 記事を作成

以下のコマンドで記事を作成します。

# contentDirを設定したため、ディレクトリをリネーム
mv content post

# 記事を3つ作成
hugo new article/hello-world.md
hugo new article/hello-hugo.md
hugo new _index.md

記事の内容は以下の通りです。2つの --- で囲まれたものは YAML Front Matter と呼ばれ、YAMLで記事のメタデータを設定します(TOMLも使用可能)。

post/article/hello-world.md という名前で以下の内容のファイルを作成してください。

---
title: "Hello World"
date: 2020-05-07T11:39:39+09:00
tags: ["hello", "world"]

---

Hello World!

post/article/hello-hugo.md という名前で以下の内容のファイルを作成してください。

---
title: "Hello Hugo"
date: 2020-05-07T11:39:39+09:00
tags: ["hello", "hugo"]

---

Hello Hugo!

post/article/_index.md という名前で以下の内容のファイルを作成してください。

---
title: ""
date: 2020-05-07T11:39:39+09:00

---

私のサイトにようこそ!

ここではテーマでタグを使用するため、 tags: の設定を行っています。また、デフォルトで付けられる draft: true を取り除いています。

hugo new で作成する記事のテンプレートはアーキタイプと呼ばれ、デフォルトでは archetypes/default.md が使われます。詳細は Archetypes を参照してください。

なお、 date: は時刻やタイムゾーンを省略して date: 2020-05-07 のように書けます。ただしこの場合はタイムゾーンがUTCとなり(Issue)、日本時間とは9時間ずれるため、書いた記事が表示できない可能性があります(後で書くように、 -F オプションをつけることで未来の記事もビルドできますが)。

4. 動作確認

以下のコマンドを打つと、ローカルでサーバが起動します。

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: LAN内の他のマシンからもアクセス可能にする
  • --disableFastRender: 変更時に完全な再レンダリングを行う(テーマの作成時に有用)

全部付けると次のようになります。

hugo serve -D -F --bind=0.0.0.0 --disableFastRender

最終的にHTMLを作成するためには、以下のように serve を除いてください。
-D, -F オプションはサーバ起動時と同様に使用可能です。

# オプションなしの場合
hugo

# オプションありの場合
hugo -D -F

ここまで完了したら、一度コミットしておきます。

git add -A
git commit -m "new site"

5. テーマの導入

ここからテーマの作成を行っていきますが、その前にテーマを使わない手順を記載します。

新規にテーマを作るときは、次の「5.2. テーマを作成」までスキップしてください。

5.1. 既存のテーマを使う場合

有志が作ったテーマが以下のサイトに公開されています。

例えば Ananke Gohugo Theme を使う場合は以下のコマンドを打ちます(見た目は2つのコマンドに見えますが、1つのコマンドです)。

git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

Hugoのテーマはデフォルトでは themes/<テーマ名> ディレクトリを使用します。このディレクトリに、Gitのサブモジュールという機能を使って、外部リポジトリを取り込んでいます。

そしてテーマ名を設定ファイルに追加します。

baseURL = "http://example.org/"
languageCode = "ja"
title = "Yボタンがネギに見える"
contentDir = "post"
disableKinds = ["RSS", "sitemap"]
theme = "ananke"  # この行を追加

[taxonomies]
tag = "tags"

hugo serve でサーバを立ち上げると、以下のような画面が表示されます。

ここまで完了したら、一度コミットしておきます。

git add -A
git commit -m "add theme"

「6. GitHub Pagesへのデプロイ」に進んでください。

5.2. テーマを作成

ここでは、新規にテーマを作成する方法を説明します。

5.2.1. テーマの仕組み

Hugoのテーマはテンプレートファイルと、その他のリソース(画像など)から成ります。Hugoのテンプレートはいくつかの特徴を持ちます。

  1. ページの種類によってどのテンプレートの参照順序が決まり、最初にマッチしたものが使われる
  2. ベーステンプレートを書き、それを拡張していく
  3. テーマと同じパスのファイルを 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 ディレクトリ内にテンプレートを直接置くことも可能です。テーマを再配布する予定がなければ、この方法で十分でしょう。今回はこの機能を使用します。

5.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コメントを追加しています。

5.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にアクセスしてください。

http://localhost:1313

次のような画面が表示されます。簡素なものですが、テンプレートが完成しました。

ここまで完了したら、一度コミットしておきます。

git add -A
git commit -m "add theme"

6. GitHub Pagesへのデプロイ

以下の記事を参考に、GitHub Pagesへのデプロイを行います。

GitHub Pagesには、2つのタイプ、3つのデプロイ方法があります。

1つ目のタイプは、ユーザまたは組織のページです。 https://<ユーザ名 or 組織名>.github.io でアクセスできます。
デプロイ元は master ブランチ固定で、ルートディレクトリ以下のファイルがデプロイされます。

もう1つのタイプは、プロジェクトのページです。 https://<ユーザ名 or 組織名>.github.io/<リポジトリ名> でアクセスできます。
デプロイ元は以下の3つがあります。

  • gh-pages ブランチ
  • master ブランチ
  • master ブランチの docs ディレクトリ

注意が必要なのは、 master ブランチからデプロイする場合です。ルートディレクトリ以下のファイルがデプロイされるため、そのままだと設定ファイルが公開されてしまいます(master ブランチの docs ディレクトリを使う場合は問題ありません)。

対策としては2種類あります。1つは設定ファイルやMarkdownファイルを置くブランチと、デプロイするHTMLファイルを置くブランチを分ける方法です。これは後で説明します。もう1つは設定ファイルやMarkdownファイルをを置くリポジトリと、デプロイするHTMLを置くリポジトリを分ける方法です。公式ページではこの方法が先に説明されているため、まずこちらを説明します。

プロジェクトのページについては、一番最後の「master ブランチの docs ディレクトリ」が簡単です。「6.3. docs ディレクトリを使用する方法」を参照してください。

なお、リモートにプッシュしても GitHub Pages 側でサイトが表示できないことが時々あります。その際は、新しいコミットを追加して(空コミットでもOK)からプッシュすると、サイトが表示されるはずです。

6.1. リポジトリを分けて管理する方法

まずは以下の2つのリポジトリを作ってください。 USERNAME はGitHubのアカウント名、 PROJECT は任意のプロジェクト名です。無料アカウントではパブリックリポジトリにする必要があります。注意してください。

  • USERNANE.github.io
  • PROJECT

USERNANE.github.io リポジトリ作成時は、次の画像のようにREADMEファイルのみ作成してください。
(空のリポジトリでもOKですが、空のリポジトリだと git submodule add でエラーが出る)

PROJECT リポジトリ作成時は、次の画像のように何もファイルを作らないようにしてください(ローカルでコミット済みのため面倒になる)。

以下のコマンドを打ち、作成したサイトをリモートにプッシュします。

# Hugoのサイトディレクトリに移動
cd ~/quickstart

# ユーザ名を入れる
USERNAME=ユーザ名

# プロジェクト名を入れる
PROJECT=プロジェクト名

# リモートリポジトリを追加
git remote add origin git@github.com:${USERNAME}/${PROJECT}

# リモートにプッシュ
git push -u origin master

それから、 USERNAME.github.io をサブモジュールとして追加します。

# Hugoが作成したファイルを一旦削除
rm -rf public

# この方式では .gitignore は不要なため削除
rm .gitignore
git add .gitignore
git commit -m "remove .gitignore"

# サブモジュールを登録する。このときに `USERNANE.github.io` リポジトリが空だとエラーになる
git submodule add -b master git@github.com:${USERNAME}/${USERNAME}.github.io public
git commit -m "add ${USERNAME}.github.io"

それから、Hugoを起動してできたファイルをサブモジュールにコミットします。

# ビルド
hugo

# サブモジュールに移動
cd public

# README.mdを削除
git rm README.md

# 作成されたHTMLファイルをコミットしてプッシュ
# ログには `rebuild site 2020年 5月7日 木曜日 21時39分39秒 JST` のような時刻が入る
git add -A
git commit -m "rebuild site $(date)"
git push

しばらく待ってから、 https://USERNAME.github.io にアクセスしてください。Hugoでビルドされたサイトが表示されるはずです。

公式サイトのPut it Into a Scriptに書かれている deploy.sh は、先ほどの手順をスクリプト化したものです。

6.2. ブランチを分けて管理する方法

1つのリポジトリに2つのブランチを使用する方法です。プロジェクトのページで、 gh-pages を使う場合の説明をします。

まず同様に任意のプロジェクト名でリポジトリを作成してください。以下プロジェクト名を PROJECT とします。
PROJECT リポジトリ作成時は、次の画像のように何もファイルを作らないようにしてください(ローカルでコミット済みのため面倒になる)。

まず作成したサイトをリモートにプッシュします。

# Hugoのサイトディレクトリに移動
cd ~/quickstart

# ユーザ名を入れる
USERNAME=ユーザ名

# プロジェクト名を入れる
PROJECT=プロジェクト名

# リモートリポジトリを追加
git remote add origin git@github.com:${USERNAME}/${PROJECT}

# リモートにプッシュ
git push -u origin master

それから、 master ブランチとは独立したブランチ gh-pages を作成し、空のコミットを追加した上でプッシュします。
(Preparations for gh-pages Branchで書かれているコマンドとほぼ同じです)

# orphan(孤立)ブランチの作成
git checkout --orphan gh-pages

# 全てのファイルを削除して空にする
git reset --hard

# 空コミットを追加
git commit --allow-empty -m "Initializing gh-pages branch"

# リモートにプッシュ(originで登録されているため公式のコマンドと異なる)
git push origin gh-pages

# masterブランチに戻る
git checkout master

次に、 gh-pagespublic ディレクトリにチェックアウトします。

# Hugoが作成したファイルを一旦削除
rm -rf public

# publicディレクトリにgh-pagesブランチをチェックアウト(公式のコマンドと異なるが内容は同じ)
git worktree add public gh-pages

それから、Hugoを起動してできたファイルを gh-pages ブランチにコミットし、リモートにプッシュします。

# ビルド
hugo

# gh-pagesのワークツリーに移動
cd public

# 作成されたHTMLファイルをコミット
git add -A
git commit -m "Publishing to gh-pages"
cd ..

# リモートにプッシュ
git push origin gh-pages

しばらく待ってから、 https://USERNAME.github.io/PROJECT/ にアクセスしてください。Hugoでビルドされたサイトが表示されるはずです。

公式サイトのPut it Into a Scriptに書かれている publish_to_ghpages.sh は、先ほどの手順をスクリプト化したものです。

6.3. docs ディレクトリを使用する方法

最後になりますが、これが一番簡単です。

プロジェクトのページで、1つのリポジトリ、1つのブランチでデプロイ元のディレクトリを docs にする方法です。

まず、Hugoの設定を変更し、 public ディレクトリでなく docs ディレクトリに出力先を変更します。

baseURL = "http://example.org/"
languageCode = "ja"
title = "Yボタンがネギに見える"
contentDir = "post"
disableKinds = ["RSS", "sitemap"]
publishDir = "docs"  # この行を追加

[taxonomies]
tag = "tags"

変更した設定ファイルをコミットしておきます。また、 .gitignore は不要なため削除しておきます。

# config.toml の変更をコミット
git add -A
git commit -m "change publishDir"

# .gitignoreは不要なので削除
git rm .gitignore
git commit -m "remove .gitignore"

それから、Hugoを起動してできたファイルを master ブランチにコミットし、リモートにプッシュします。

# ビルド
hugo

# 作成されたHTMLファイルをコミット
git add -A
git commit -m "rebuilding site $(date)"

# リモートにプッシュ
git push

それから、GitHub Pagesのソース設定を以下のように master branch /docs folder に変更します。

しばらく待ってから、 https://USERNAME.github.io/PROJECT/ にアクセスしてください。Hugoでビルドされたサイトが表示されるはずです。

おわりに

Hugoには他にもいろいろ機能があります。以下で挙げるものはほんの一部です。

  • partialテンプレート(テンプレートで使われる共通のコード)
  • ショートコード(コンテンツに埋め込める共通のコード)
  • 多言語対応
  • データテンプレート(YAMLやJSONなどから出力)
  • 画像処理(リサイズ、回転など)
  • Hugo Pipes(Sassやminifyなど)

また、CSSフレームワークを導入して、見栄えのいいサイトを作るのもいいです。いろいろ工夫してみてください。

採用情報

メンバーズエッジで最高のチームで最高のプロダクトを作りませんか?

最高のプロダクトをつくる 最高のチームで働く

在宅でも、地方でも、首都圏でも。多様な働き方で最高のチームをつくり、お客様のプロダクトパートナーを目指します。アジャイル開発を通じ、開発現場の第一線で活躍し続けたいエンジニアを募集しています。