Hugo のテンプレートを理解するためのポイント
はじめに
Hugoは、Goで書かれた高速なビルドが可能な静的サイトジェネレータ(SSG)です。
自分も趣味で運営してるサイトはHugoで開発してて、とても便利です。
Hugoの機能の一つで重要な 「テンプレート機能」 があります。
テンプレート機能は便利なのですが、似た概念が多く混乱してしまったり、結局特定のページにどのレイアウトが適用されるのかが直観的にわかりづらいなど、ハマりどころがあります。
テンプレート機能に自分も何回もハマってしまい、覚えられないので、備忘録も兼ねてHugoのテンプレートを理解するためのポイントをまとめてみました。
対象読者
- Hugoでサイトを作成したことがあるが「テンプレート機能」について腹落ちできてない人
Hugo のテンプレートを理解するためのポイント
1. Hugo テンプレートの「呼び出し方・差し替え方」は 3 種類
Hugo には、「テンプレートを部品化したり差し替えたりする」ための仕組みが大きく分けて 3 つ あります。
-
partial- 「小さな断片を挿入する」 イメージ
- レイアウトの自動解決 (コンテンツタイプごとに探す) はしない
- 例:
{{ partial "header.html" . }}
-
.Render- 「ページを特定のレイアウトでレンダリングする」 イメージ
- Hugo のレイアウト検索 (Lookup) が走る → コンテンツタイプ別のテンプレートが自動で選ばれる
- 例:
{{ .Render "summary" }}
-
block/define- 「親子レイアウト (継承)」を実現する イメージ
- 例:
baseof.htmlとsingle.htmlのように、親で{{ block "main" . }}{{ end }}、子で{{ define "main" }}
Hugo でいう「コンテンツタイプ (Content Type)」は、主に ページ (Page) が属するセクションや Front Matter の設定 から決まるものです。.Render は、そのページが持つコンテンツタイプを基にレイアウトを探しにいきます。
たとえば次のようなイメージです。
- ページが何らかのコンテンツタイプ (例:
post,eventなど) を持っている- 通常は
content/<タイプ名>/...のようなディレクトリ構造から自動的に決まります。 - もしくは Front Matter で
type: "post"のように明示指定している場合もあります。
- 通常は
- ページ上で
{{ .Render "summary" }}と書くと、以下の順序でレイアウトを探します。layouts/<タイプ名>/summary.htmllayouts/_default/summary.html- それでも見つからなければ最終的にフォールバック (その他候補) へ…
したがって、.Render "summary" という呼び出しにおいて「どのテンプレートが使われるか」は、ページがどのコンテンツタイプに属しているか (= .Type プロパティの値) で変わるという仕組みです。
1.1 partial のポイント
-
「一部分を抜き出したテンプレート」を呼び出す手段
ちょっとした HTML スニペットや共通パーツ (ヘッダー、フッターなど) を、partials/フォルダに置いて使うことが多いです。 -
直接ファイル名を指定する
{{ partial "navbar.html" . }}のように呼び出すので、自動検索 (Lookup) の仕組みには乗りません。コンテンツタイプをまたいでも同じ partial を使い回せます。
1.2 .Render のポイント
-
「Hugo のレイアウト検索ロジック」を発動させる
例:{{ .Render "summary" }}と書くと、layouts/<コンテンツタイプ>/summary.html→layouts/_default/summary.html… といった優先順位で検索が行われます。 -
一覧 (list) ページなどから、子ページごとに異なるデザインを呼び出したいとき
.Pagesをrangeして、各ページに「特定のレイアウト」を当てるイメージ。例えば「ブログ記事なら blog/summary.html」を使い、「イベントなら events/summary.html」を使う…といった振り分けが自動で行われます。
1.3 block / define のポイント
-
ベースレイアウト (親) と、それを継承する子レイアウトを定義する
baseof.htmlで大枠 (header, footer, body) を書いておき、子 (single.html/list.htmlなど) で中身のブロックをオーバーライドする仕組みです。 -
HTML の構造や共通部分を “まとめる” のに向いている
繰り返しレンダリング (range .Pages) などには向きません。単純に「HTML タグの大枠を共有し、中身だけ差し替える」というケースで重宝します。
2. layouts/ フォルダの構成とフォールバックの流れ
Hugo のテンプレートファイルは、通常 layouts/ フォルダ以下に配置します。
よく見るディレクトリ構成例はこんな感じです:
layouts/
├── _default/
│ ├── baseof.html
│ ├── single.html
│ └── list.html
├── posts/
│ ├── single.html
│ └── list.html
└── partials/
└── header.html
-
_default/は「どこにも該当しないときのフォールバック」
たとえば「ブログ記事用レイアウト (posts/single.html) がなかったら_default/single.htmlを使う」イメージ。 -
<セクション名>/ディレクトリが優先される
コンテンツがcontent/posts/にある場合は、まずlayouts/posts/を探し、それが無ければ_default/を見にいきます。 -
baseof.htmlはあくまで「ベースレイアウト用」の特別ファイル名
_default/配下に置くのが一般的ですが、実は各セクションディレクトリにもbaseof.htmlを置けます。
もし同名ファイルがあれば、そちらが優先して使われます。
2.1 いつ _default/ が使われるのか?
- セクションに該当するテンプレートが「存在しない」場合
- あっても命名が違う (たとえば
list.htmlが無くoverview.htmlしかない) 場合 - その際、最終的に
_default/の中からファイルを探しにいき、見つかればそれが使われる
3. コンテンツ側 (content/) からのレイアウト指定方法
3.1 Front Matter で layout を指定
Markdown ファイルの先頭にある Front Matter (YAML / TOML など) で、以下のように書くと、Hugo はそのレイアウトを探して使おうとします:
layout: "my-custom-layout"
-
layouts/this-section/my-custom-layout.html→layouts/_default/my-custom-layout.html→ … のように探しにいきます。
3.2 _index.md の挙動に注意
セクションのルート (content/セクション名/_index.md) は “リストページ” として処理されるため、他のコンテンツ (記事) とは挙動が若干異なります。
さらに Front Matter で layout: "bar" のように指定しても、「Hugo の独自のフォールバックロジック」により期待通りのファイルを見にいかないケースがあります。
例
content/foo/_index.mdでlayout: "bar"と指定
layouts/foo/bar.htmlはなく、layouts/foo/list.htmlはある
layouts/_default/bar.htmlはある結果:
layouts/foo/list.htmlが優先され、layouts/_default/bar.htmlは使われない
一見、「bar が無いなら _default/bar.html を使うだろう」と思いきや、Hugo は「同じセクション (foo) の list.html があるから、そっちを採用しよう」と判断してしまうわけです。
これはバグか仕様か微妙なラインですが、Hugo に独特のフォールバックルールがあるという点を覚えておくと良いでしょう。
ただ、すでに修正されているなどの可能性があります。使う時に確かめておくと良いです。
(詳しい方いればコメントで教えてもらえるとありがたいです。)
4. 原理原則のまとめ
4.1 テンプレート呼び出しの三本柱
-
partial- 「直接呼び出す小さな部品」
- Layout Lookup (コンテンツタイプごとに自動検索) は使わない
-
.Render- 「ページオブジェクトをレイアウトに当てはめる」
- Layout Lookup が動き、コンテンツタイプ別に自動で読み分けしてくれる
-
block/define- 「ベース (親) と子レイアウトを上書きする」
- 「HTML の大枠を共通化・継承する」用途向け
4.2 レイアウト検索(フォールバック)の基本フロー
layouts/<セクション名>を探す- なければ →
layouts/_defaultを探す - それでもなければ → さらにデフォルトテンプレート (あるいは最終的に 404 的な結果) に落ちる
4.3 覚えておきたい特有の挙動
-
Front Matter の
layout:で指定しても “思い通りにならない” 場合がある
特に_index.mdのようなリストページは、list.htmlが優先されるなどの挙動があるので注意。 -
baseof.htmlはあくまで「テンプレート継承のための親ファイル」
各セクションごとに置けるが、存在すればそっちが優先 → なければ_default/を参照、という流れになる。
おわりに
Hugo は非常に柔軟なテンプレートシステムを備えている反面、「部分テンプレート (partial), 自動検索 (.Render), 親子継承 (block/define)」がごちゃ混ぜになりがちです。
しかし、それぞれの概念を
- 部品を直接呼び出す →
partial - コンテンツタイプごとに自動で切り替えたい →
.Render - HTML 構造を継承・上書きしたい →
block/define
と割り切って使い分ければ、頭の中で整理しやすくなります。
また、テンプレートファイルが格納される layouts/ フォルダの階層と _default/ の役割、そして Front Matter の layout: 指定が「どのようにフォールバックしていくのか」 を一度把握しておくと、Hugo のテンプレートカスタマイズがぐっと楽になるでしょう。
最終的には「どういう目的で呼び出したいのか」を常に意識するのがポイントです。「共通パーツを挿入したいだけ」なら partial、「ページごとに違うレイアウトを選びたい」なら .Render、「ベースレイアウトを使い回したい」なら block/define。
ここさえブレなければ、複雑に思える Hugo テンプレートも整理しやすくなるはずです。
Discussion