Hugo のレイアウトの仕組み

テーマの無いテスト用サイトを作る

普通はthemesDIRに任意のテーマをインストールすることで、サイトの基礎になる最低限の見た目が表示されるのだが、Hugo のレイアウトの仕組みを理解するためにわざと既存のテーマをインストールせずに進めてみる。

新しい Hugo のテスト用サイトを作りサーバーを起ち上げる.

$ hugo new site hugo-test-site
$ cd hugo-test-site/
$ hugo server

Hugo は最低限のテンプレートが無いとHTMLを生成しないので、このままでブラウザからhttp://localhost:1313/にアクセスしても真っ白で何も表示しない.

最低限必要となるテンプレート

Hugoは次の4つの基本テンプレートが存在する事でページを生成することになている。

  • layouts/index.html(トップページ)
  • layouts/_default/baseof.html(全ページで使われるBase Template)
  • layouts/_default/list.html(セクションやTaxonomyごとの記事一覧ページ
  • layouts/_default/single.html(記事個別ページ)

順に手書きで作ってみる.

トップページ

layouts/index.htmlを作る.

[layouts/index.html]

<!DOCTYPE html>
<html lang="ja">
  <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, user-scalable=no">
      <link rel="stylesheet" href="{{ .Site.BaseURL }}/style.css">
      <title>Hugo テスト</title>
  </head>
  <body>
    <header>
      <div class="header_title">Hugo テスト</div>
    </header>

    <footer>
      ここがフッター
    </footer>
    
  </body>
</html>

一応{{ .Site.BaseURL }}/style.cssでサイト全体のスタイルを定義したCSSを読み込む.これはstatic/style.cssとして置かれたファイルで、CSSファイル以外に画像などもstatic/img/001.jpgなどとして配置すると、テンプレート中でimg/001.jpgでアクセスできる.style.cssには最低限のスタイルのみが定義されている.これからパーツを追加していく過程で適宜スタイルも追加していくが cssの詳細は割愛する.

これだけでヘッダーとフッターに文字が表示された極めて最低限の表示がされるはずだ.これが URLlocalhost:1313/で表示されるトップページになる.

個別の記事ファイル

しかしまだ本体メインの中身が無い.そこでcontent以下に記事ファイルを用意する.1つめにcontent/sample1.mdを作る.

[content/sample1.md]

---
title: "Sample1"
date: 2020-08-25T18:03:25+09:00
# draft: true
---

最初の記事です.

ここに内容を書きます.

Front matter のdraft項目はコメントアウト又は削除する. ただ記事ファイルを作っただけではトップページにコンテンツを表示する仕組みが無いので何も変わらない.そこでトップページにはたいてい記事のリストを表示しておくので、layouts/index.htmlに記事へのリンクリストを並べたメインコンテンツを表示するブロックを追加する.

[layouts/index.html]

<!DOCTYPE html>
<html lang="ja">
  <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, user-scalable=no">
      <link rel="stylesheet" href="{{ .Site.BaseURL }}/style.css">
      <title>Hugo テスト</title>
  </head>
  <body>
    <header>
      <div class="header_title">Hugo テスト</div>
    </header>

    <div class="contents">
      <ul>
        {{ range .Data.Pages }}
          <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})
        {{ end }}
      </ul>
    </div>

    <footer>
      ここがフッター
    </footer>
    
  </body>
</html>

.Data.Pages: すべてのコンテンツの情報が格納された変数。 range から end がループで、その間はループの現在値(ページの値)に . で直接アクセスできる. .Date.Format "日付表示の形式サンプル"の形で、各記事の Front matter に書かれたdate項目の日付をフォーマットして表示する.

記事が一つではリストの実感が無いので、sample2.mdsample4.mdといった形でもう2〜3記事追加して試してみたい.ただこのまま記事を追加しても、トップページのコンテンツには記事リストは表示されるが、個別記事のリンクをクリックした際の元になるレイアウトファイルが無いので404エラーになる.

個別記事用のレイアウト

そこで個別記事用のレイアウトlayouts/_default/single.htmlを用意する.

[layouts/_default/single.html]

<div class="site-content">  
  <h1>{{ .Title }}</h1>   
  <time>{{ .Date.Format "2006-01-02" }}</time>    
  <article>
    {{ .Content }} <!-- ここに本文すべてがはいる -->
  </article>
</div>

これでトップページの記事リンクをクリックすると、個別の記事内容が表示される.

リスト用のレイアウト

トップページのコンテンツ表示部分の.Data.Pages.Site.Pagesに変えてみる..Site.Pagesはデフォルトで日時 (.Date) の一番新しいものから昇順に並べられたPage配列を返す.

するとcontentフォルダ内の記事ファイルしか表示されていなかった記事一覧に、作った憶えのないCategoriesとかTagsとかconfig.tomlで指定したサイトタイトルのリンクなどが表示される.これらのリンク先もやはり専用レイアウトがまだ用意されていないのでクリックしても何も表示しない.ただし404エラーではなく、コンソールには「"taxonomy"表示のためのHTMLが無い」といった警告が出ているはずだ.

WARN 2020/08/25 21:08:37 found no layout file for "HTML" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
Total in 3 ms

そこで/layouts/_default/list.htmlというレイアウトファイルを用意する.これはコンテンツリストを表示することを目的とした基本的な共通テンプレートファイルだ.

[/layouts/_default/list.html]

<div>
  <ul>
    {{ range .Pages }}
      <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})</li>
    {{ end }}
  </ul>
</div>

ここで記事のカテゴリーやタグの一覧を表示するために、個別の記事の Front matter にそれらを適当に追加しておく.

---
title: "Sample1"
date: 2020-08-25T19:02:56+09:00
# draft: true
tags: [ example, test ]
categories: [ Hugo ]
---

これでトップページのCategoriesとかTagsなどのリンクをクリックするとカテゴリーやタグで分類された記事のリストが表示される.このリスト表示にさきほど作った/layouts/_default/list.htmlが使われている.

Section の仕組みで記事を分類する

contentフォルダ内にサブフォルダを作ると Section になる.例えばcontent/posts/という形にサブフォルダを作って、その下に先程の記事ファイルを置くと、"baseURL"/posts/sample1といったURLでアクセスできて、それらの記事はpostsセクションに分類される.

実際にやってみると、トップページのコンテンツリストには、個別記事やカテゴリーなどのリンクとは別にPostsというリンクが貼られており、クリックするとcontent/posts/配下に移動した個別記事へのリンクリストのページが表示される.このリストの表示に使われているレイアウトも、さきほどの/layouts/_default/list.htmlだ.

partial でサイトの部品をつくる

サイトを構築する際に、共通部分を部品化する仕組みがたいていCMSにはあるが、Hugo も partial という仕組みを備えている.とりあえずlayouts/index.htmlに直書きしているヘッダーやフッターを部品化してみる.

layouts/partials/というフォルダを作って、配下にheader.htmlfooter.htmlを置く.

[layouts/partials/header.html]

<!DOCTYPE html>
<html lang="ja">
  <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, user-scalable=no">
      <link rel="stylesheet" href="{{ .Site.BaseURL }}/style.css">
      <title>Hugo テスト</title>
  </head>
  <body>

[layouts/partials/footer.html]

    <footer>
      ここがフッター
    </footer>

  </body>
</html>

layouts/index.htmlからこれらを読み込む.ヘッダーとフッターを展開したい位置に partialコマンドでこれらの部品を読み込む.コマンドの最後のピリオドは partial で読み込んだデータをこの位置に展開するためのもので必要.

[layouts/index.html]

{{ partial "header" . }}

<header>
  <div class="header_title">Hugo テスト</div>
</header>

<div class="contents">
  <ul>
    {{ range .Site.Pages }}
      <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})</li>
    {{ end }}
  </ul>
</div>

{{ partial "footer" . }}

list.htmlsingle.htmlでも同様の partialコマンドのコードを書けば共通の部品にはなるが、ここではもっと共通化を進めるためにBase Template の仕組みを使う.

Base Template でサイトの共通部分をまとめる

ヘッダーやフッターの partial をこのままlayouts/index.htmllayouts/_default/single.html/layouts/_default/list.htmlといった各レイアウトファイルに埋め込んでいってもそれなりの共通化はできるが、Base Template の仕組みを使えばもっとスマートになる.

各テンプレートの中身を{{ define "main" }}{{ end }}で囲み、layouts/_default/baseof.html内で{{ block "main" . }}{{ end }}で囲んだ中にそれぞれのページの内容が埋め込まれる.ヘッダーやフッターもこのbaseof.htmlの中で読み込めばindex.htmlsingle.htmllist.htmlなどに partial で書く必要は無い.

ここではより共通化を進めるためにindex.htmlにあったサイトヘッダーもlayouts/partials/site-header.htmlとして部品化し、baseof.htmlの中で読み込んだ.これで全ページで共通のヘッダー、サイトヘッダー、フッターが表示される.

ちなみに DOCTYPE宣言とか html や body タグといった全体の構造枠(外枠)はヘッダーやフッターに欠片があるのは気持ちわるいので、baseof.htmlにまとめた。この方が全体の関連が把握しやすいと思う。

[layouts/partials/site-header.html]

<header>
  <div class="header_title">Hugo テスト</div>
</header>

[layouts/_default/baseof.html]

<!DOCTYPE html>
<html lang="ja">
  {{ partial "header" . }}
  <body>
    <div id="main">
      {{ partial "site-header" . }}
      <div class="contents">
      {{ block "main" . }}
        <!-- ここに各ページの内容がはいる -->
      {{ end }}
      </div>
    </div>
  {{ partial "footer" . }}
  </body>
</html>

[layouts/_default/index.html]

{{ define "main" }}
<ul>
  {{ range .Site.Pages }}
    <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})</li>
  {{ end }}
</ul>
{{ end }}

[layouts/_default/list.html]

{{ define "main" }}
<div>
  <ul>
    {{ range .Pages }}
      <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})</li>
    {{ end }}
  </ul>
</div>
{{ end }}

[layouts/_default/single.html]

{{ define "main" }}
<div class="site-content">  
  <h1>{{ .Title }}</h1>   
  <time>{{ .Date.Format "2006-01-02" }}</time>    
  <article>
    {{ .Content }} <!-- ここに本文すべてがはいる -->
  </article>
</div>
{{ end }}