この記事は何
先日、何年かぶりに静的サイトジェネレーターの一つ「hugo」でwebサイトを構築する機会がありました。
その際に知人たちに色々聞かれたり、自分でもhugoの覚え直しが必要になったので、2024/03現時点までの備忘録として紹介記事とテーマ作成までのチュートリアル記事を投稿させていただきます。誰かのお役に立てば幸い。
本稿ではhugoの紹介と使い方、自作テーマの入り口のあたりまでご紹介します。
個人の感想多めでお送りしておりますがご容赦ください。
使用した環境
- macOS Sonoma 14.4.1(23E224)
- Mac mini 2018モデル
- hugoバージョン
❯ hugo version
hugo v0.124.1
対象読者
- 静的サイト生成に興味がある人
- コマンドライン怖くない人
- gitコマンドが使えること
- いきなりgo言語のhtml/template構文が出てきても泣かない人
hugo is 何
人気の高いSSG = Static Site Generator、「静的サイトジェネレーター」の一つです。
SSGの歴史もそこそこ長いので、web界隈ではだいぶ浸透していると思いますが、wikipediaを引用しつつ軽くご紹介を。
SSGとは、その名前の通りサーバサイド処理が不要な「静的webサイト」を生成するツールの総称です。
hugoについても、コマンドライン実行によるローカルで生成するのが主流かと思います。やってることは往年のホームページビルダーなんかと同じです。
Github ActionやAWS Lambdaからデプロイする運用事例もあり、割と運用スタイルは豊富です。
SSGは安く済むと言われます。
これは静的サイトなのでGithub PagesやS3 Public Site Hostingに配置できるという意味でしかないです。手書きでhtml書いてアップロードするのと同じことです。
もちろんSSGを使えば、手書きするのに比べて圧倒的に楽に、高機能なサイトを構築できます。例えばタグクラウドを手書きで全ページに仕込めるかというと、まあ辛いですよね。
SSGではmarkdown形式の記事ファイルを用意して、後述するフロントマターという追加情報を冒頭に記述するものが多いようです。手軽です。hugoでも同様です。
このフロントマター部分にタグ情報やカテゴリ情報などを記述することで、各種リンクやタグクラウドが整備されたwebサイトが出来上がります。
構築中はローカルサーバが起動して、お好みのブラウザ上でライブリロードされます。
まあ一言でまとめると、結構便利っすよ。SSG。
興味が向いた方はぜひこの記事の続きをお読みいただければと。
hugo以外のSSGについて
余談気味ではあるのですが、hugo以外にもSSGツールはたくさんあるので、このセクションは先に記載させていただきます。
先に結論から。hugoじゃなくても良くて、好みのSSGツールが見つかればもちろんそれで良いと思います。
僕自身、今回hugoを復習する前に、最近名前を聞く機会の多かったAstroを試しました。いい物です。公式が英語情報しかないhugoに比べると、Astroは日本語ドキュメントがしっかり整っているのが良かったです。
ただ、機能が豊富すぎて把握が追いつかなかったこともあり、僕は過去に触ったことのあるhugoを復習することにしました。
SSGツールはhugo以外にもいろいろあります。有名なところとしてはNext.js・Nuxt.js、先に触れたAstroなどが有名どころでしょうか。一部のブログシステムも静的サイトの形で出力するものがありますね。
hugoは数あるSSGの中では比較的古参組に入る印象です。機能的にはSSG生成に特化しているのでSSRに利用できるものではないです。その分シンプルなところが人気の理由かもしれません。
2024/3現在、jamstack.orgのSSGランキングではNextに次ぐ2位に位置しています。
hugoはGo言語(以下golangと記載)で作成されたSSGです。
普段使いではgolangの知識は不要ですが、レイアウトテンプレートにはgolangのhtml/templateパッケージが利用されているので、テンプレートを扱う際には記述方法の把握が必要になります。
この点は初見の方には若干ストレスかもしれません。
AstroはJSX風の記述でテンプレートを書けるので、reactに慣れてる方にはAstroをお勧めします。
余談: 「ブログシステムやWixとかじゃダメなの?」
本稿の余談枠は読み飛ばして大丈夫です。大したことは書いてません。
先の言葉は今回webサイト作るときに聞かれた言葉でした。
まあ「用途や特徴が違う物です」という説明で良いかと思うのですが。
例えばサイトの運用前提として、「更新担当者が複数いて全員がWordpressに慣れている」という前提であれば、SSGを選択することはほぼないかと思います。Wixについても同様です。
SSGが向く場面の一例としては、webサイトを「安く」「ほぼ放置したい」という状況でしょう。ペライチのランディングページを立ち上げたいとか、たまに更新する程度の情報ページを作る際に良いでしょう。
もちろんブログ形式のサイトも運用できますが、コメント機能など動的な機能が必要な場合は代替手段が必要になるのが悩みどころです。hugo単体では管理画面に当たる仕組みもないので、スマホからの緊急的な更新・編集みたいなこともできません。用途次第ですね。
余談2: 「SSGって安いみたいね?」
この「安い」というのも状況次第ですよね。単に「静的サイトを安くデプロイできる方法がある」というだけの話です。
先入観などをお持ちでSSGを過大評価されてそうな方に説明する際には、ミスリードしないように結構言葉選びに神経を使います。
GitHub Pagesで運用できる内容でしたら無料で運用できますね。この使い方にSSGがうまく乗っかったことも、SSGが流行った一因かと思います。無料って言葉は強いよね。
収益化や商用目的などの理由でGitHub Pagesの規約から外れるwebサイトもあるでしょう。その際はAWS、GCP、Azureなど大手クラウドベンダーの静的webサイトホスティング機能を使うと安く上がります。
アクセスが少なければ月額10円かかりません。
もちろんアクセスなければサイトの存在意義自体が薄いということだけども。
一つ二つSSGツールに慣れておくと、こう言った用途向けのwebサイトの雛形作成までを一瞬で提案できるので、「こいつはちょっとできるやつ」という評価をいただける機会もあるかもしれませんね。
僕はデプロイ先にAWS S3とCloudFrontをよく使っています。本稿ではAWSに関する詳細は省きますが、静的サイトをhttps運用する手軽な方法の代表例の一つかと思います。こういったマネージドサービスは従量課金で勝手にスケールするので負荷面の心配もほぼありません。
動的サイトと比べるとセキュリティ面の不安も減らせます。もちろんAWS側に障害があったり、IAMロールの運用ミスなど人為的ミスが挟まるとなんでもありうるので「SSGなら障害ゼロ! セキュリティ完璧!」とは言えないのですが。
権限管理と請求アラート設定は徹底しておきましょう。
話がだいぶそれました。すみません。
インストールと起動
公式のQuick Startが優秀なので、補足することはあまりないですが。
公式の情報は全て英語です。不得手な方はChrome翻訳やDeepl翻訳でご対応ください。
インストールの説明はwin/mac/linux/BSDの項目があります。お手元の環境に合わせてご利用ください。
僕はmacOSでhomebrewを導入済みなので、「brew install hugo」で済ませました。homebrewの解説は別の記事にお譲りします。
ではお好みのターミナルでローカルマシンのお好きな場所に移動して、hugoサイトを生成してみましょう。
hugo new site quickstart
cd quickstart
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo "theme = 'ananke'" >> hugo.toml
hugo server
ここでのトピックは、anankeというテーマをサブモジュールとして導入している点ですね。いきなり見た目が整うので便利ですし、他のテーマを試したいという時にもサブモジュール運用は便利です。まずはこれを触っていましょう。
このQuick Startの例では、テーマの指定をechoコマンドで行っています。全体設定ファイル「hugo.toml」に書き込んでいるだけですね。
後からエディタで「theme = 'ananke'」の行を編集することで、後から任意に変更できます。
既存テーマの使い方に乗っかるだけなら、テンプレートのカスタマイズなしでそのままwebサイトが整います。anankeはカスタマイズの幅もなかなか広いので、どんなことができるのかreadmeを眺めてみるのも良いでしょう。本稿ではリンクだけ置いておきます。
ananke以外にも、hugo公式ではテーマがたくさん紹介されています。
お好みで色々試してみてください。
さて、hugo serverを起動したことで以下の応答が表示されました。
Built in 81 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
今回は「http://localhost:1313/
」にローカルサーバが立ち上がったようですね。ポート番号は環境によって変わることもありますのでご留意を。hugo serverは複数起動すると勝手に別のポート番号を立ち上げてくれます。(といっても複数サイトの同時編集なんてあんまりやらない気はする)
このURLをブラウザで開くと空っぽのwebサイトが表示されます。
記事を作成してみよう
Quick Startでは 「hugo new content posts/my-first-post.md
」 というコマンドで新しい記事を追加しています。これが何をしているかというと、プロジェクトルートの「archetypes/default.md」に定義された内容をもとに「content/posts/my-first-post.md」を作成してくれる機能です。
慣れたらいちいちコマンド叩かなくても、自分でmarkdownファイルを作成して大丈夫です。
まあ最初は試しにnew contentで作成してみましょうか。
フロントマターについて
「hugo new content posts/my-first-post.md
」で作成されたmdをお好みのエディタで開いてみましょう。
+++
title = 'My First Post'
date = 2024-03-30T13:39:18+09:00
draft = true
+++
「これのどこがmarkdownやねん」と思った方もいるかもしれません。これはmarkdownの冒頭につけるフロントマター(front matter)と呼ばれるメタデータです。
フロントマターについての公式のドキュメントは以下にあります。
ここで生成されたmdには、タイトルと投稿日付、記事がドラフトかどうかが記載されています。
日付書式が読みにくい方もいらっしゃるかもしれません。ISO 8601、俗にISO Dateと呼ばれる書式です。テーマによりますが、anankeはこの書式を要求します。
記事はこの日付順に並びますので、並べ替えたい場合はここを修正してください。途中の「T」を削るなどISO Dateとして不正な書式になるとエラーになるのでご注意を。最後の「+09:00」は日本時間を示す物なので触らずで。海外の方はお住まいのタイムゾーンに合わせてくださいね。
間違って未来の日付・時間を指定してしまうと、hugoはページを生成してくれないようです。タイムゾーンの記述ミスなどでもありがちなのでご注意を。
hugo new contentで作成した際は、コマンド実行日時をもとに、architypesの定義によって自動でdateを入力してくれます。もちろん手打ちで編集しても大丈夫です。
ではフロントマターの下に記事を書いてみましょう。
ドラフトがtrueだとhugo serverが読み込んでくれないのでfalseにしておきます。
+++
title = 'My First 日本語ページ'
date = 2024-03-30T13:39:18+09:00
draft = false
+++
## Introduction 日本語
This is **bold** text, and this is *emphasized* text.
Visit the [Hugo](https://gohugo.io) website!
<!--more-->
↑こういうブログでよくあるmoreコメントを打てば、
そこまでがサマリになり、そのあとは
「続きを読む」に当たる単体ページの内容になります
hugoのサマリーの落とし穴について
日本語でhugoに入門した際に、全員が踏むであろう落とし穴について解説しておきます。
hugoはデフォルトの挙動では、記事の一覧ページでは記事本文から「70単語までを自動的にサマリとして拾う仕様」です。
この70単語というのは半角スペースで判定されるため、分かち書きを行わない日本語の文章ではイメージ通りになりません……。僕も最初の頃、ほぼ全文がサマリに展開されてしまって頭を抱えました。
markdown中に<!--more-->
が存在する記事では、hugoはこの自動処理をしなくなります。
このコメントの上までがサマリ、それ以外が「続きを読む」の後のシングルページで表示される形になります。
確実な制御をお望みの場合はこれを記事中に配置しておくのが手っ取り早いでしょう。
全文をサマリに露出させたい場合は末尾に記載で。
全体設定「hugo.toml」
※昔のhugoをご存知の方へ: 前はこれ「config.toml|yaml」だったと思います。今でもconfigで動く様子ですが、今のデフォルトと推奨はhugo.xxxxのようですので本稿もこれで扱います。
プロジェクトルートにあるhugo.tomlを開いてみましょう。
これはサイト全体の設定を記述するファイルになります。
languageCodeがen-usになっているのでja-jpに書き換えておきましょう。この情報はhtmlタグのlangに埋め込まれます。en-usのままだと、chrome翻訳が英語と勘違いして翻訳勧めてきてうざいです。
baseURL = 'https://example.org/'
languageCode = 'ja-jp'
title = '書き換えたタイトルです'
theme = 'ananke'
ちょっと色変えてますがこんな感じのwebページがlive reloadされているかと思います。
ライブリロードがちょくちょく失敗する件のメモ
反映されない際は、hugo serverを停止して再起動してください。
また、「hugo server」コマンドはデフォルトではFast Renderという、全体構築を端折る挙動を示します。
サイト構築段階では色々手を加えることになるので、「hugo server --disableFastRender
」で起動しておくことをお勧めします。
ただ、v0.124.1現在のhugo serverでは、disableFastRenderしてもhugo.tomlの編集時の反映が怪しかったり、記事投稿後のタグクラウドの反映が出てこないなど、怪しい挙動がちょいちょいあるようです。
まあ「なんか変だな」と思ったらctrl+cしてコマンド履歴から再起動しちゃうのが一番です。
anankeテーマの機能をちょっと使ってみる
このtomlにanankeテーマが提供する機能の設定を記述することで、サイトをカスタマイズできます。
anankeのreadmeを参考に、hugo.tomlの設定値をいくつか適用してみます。
ここではsite_logoにplacehold.jp様の画像生成機能を利用させていただきました。
baseURL = 'https://example.org/'
languageCode = 'ja-jp'
title = 'My テスト'
theme = 'ananke'
[params]
site_logo = "https://placehold.jp/3d4070/ffffff/500x150.png"
text_color = "green"
date_format = "2006/01/02 03:04"
こうなりました。
いくらサンプルとはいえもう少しなんとかならんかったのか俺
date_formatについて
date_format = "2006/01/02 03:04"
この記述部分で、デフォルトの日付表示「March 30, 2024」という表示内容を「2024/03/30」というフォーマットに変更しています。
公式のFormatのドキュメントに説明があるのですが、「年」を入れたい場合のvalid componentは「"2006"」か、二桁の時は「"06"」と入れる仕様……と書かれています。
https://gohugo.io/methods/time/format/
これ分かりにくいんですよね……。「なんで2006やねん」、というのが少々初見殺しかと思います。
2006と書けば「四桁の年表示」になる、という点だけ暗記しておきましょう。あとは01、02、03の順に「月」「日」「時」「分」「秒」をフォーマットできます。
ここは試験に出ます 試験には出ませんが、毎回忘れて調べ直しがちな点でした(´・ω・`)
anankeテーマを活用できている状態とは言えませんが、anakneテーマの活用方法は本稿の趣旨ではないのでここまでとします。
静的サイトとしてpublishする際は、「hugo」を実行するだけです。
※minifyなどオプションは色々ありますが本稿では割愛します。
あとはpublicフォルダの内容を、お好きな環境にデプロイいただくだけでwebサイトの完成ですね。
new themeを触ってみよう
ここまでの内容でとりあえずは記事を作成してデプロイするまでの流れが整ったと思います。
もしあなたがすでに気に入ったテーマを手に入れており、望み通りのサイトが出来上がったのであれば、この記事のお役目はここまでと思います。良いhugo投稿ライフを!
……とはいえ、なかなかジャストなテーマは見つからない物です。
おそらくはみなさんも、サイトをもっとカスタマイズしていきたいという気持ちではないでしょうか。
ここまでで触ってきたanankeテーマに手を加えていくのは、入門段階ではお勧めしません。結構複雑なので右往左往することでしょう。
theme/ananke/layoutの中にanankeを構成するレイアウトテンプレートの本丸が含まれているので、ファイル構成を眺めてみてください。
20240330現在、54項目のファイル構成です。ファイル数もそこそこですが、それぞれの内容もガッツリ作り込まれており手を出しにくい雰囲気ですね。
よくできたテーマは多機能なものが多く、その分内容も複雑になっていくわけです。
最初はもっとシンプルなところから始めましょう。
テーマ作成
最終的にhugoのレイアウトテンプレートをフルカスタマイズしていく際には、hugoの公式サイトの以下のドキュメントを参照しながらページ構成の検討・改修に取り組むことになるでしょう。
ちょっとボリュームが多いです。入門用のもうちょっと端折った解説が欲しいところですね。
幸い、hugoにはテーマの雛形を作る機能があります。
本稿ではこの先、anankeテーマから離れて、雛形の内容を読み解きながら新しいテーマの作成に取り組んでいきます。
まずはプロジェクトルートで「hugo new theme my-theme
」を実行してください。
themeフォルダにmy-themeが作成されます。
my-themeの部分ははお好きな名前でどうぞ。ただのフォルダ名なので、後から変えても問題ないです。
hugo.tomlのtheme指定も、この作成時に指定したテーマ名(=作成されたフォルダ名)に修正してください。
theme = 'my-theme'
以上で雛形テーマによるwebサイトが生成されます。
hugo serverが生成した内容をブラウザで見てみましょう。
このスクショは色を変えてます。ダークモード派なもので。みなさんは白地で表示されてますかね。
そっけない雰囲気ですが、何やら作った覚えのないポストがいくつか表示されていますね。
また、この段階ですでにhome/posts/tagsなる分類ができてます。
ミニマムですが、この時点でそれなりにwebサイトとして機能していることがお分かりでしょう。
先にanankeテーマ上で作った「My First Post」もこの新しいテーマ上に引き継がれていますし、このまま追加でcontentフォルダ内にmarkdownファイルを作成すれば、記事一覧に追加されていきます。
この余計な記事はなんなのかというと、theme側のcontentフォルダにサンプルのポストがいくつか入っていて、それが反映されています。
テーマのフォルダ構成はプロジェクトルートのフォルダ構成と一緒です。
サイトの運用中にtheme側のcontentフォルダを触ることはあんまりないと思います。テーマ側のcontentの内容は一旦ルートのcontentに移動しておきましょう。
hugoはテーマ側のファイル構成とプロジェクト側のファイル構成を混ぜ込んで構築します。同名のファイルがあればルート側で上書きするルールがあり、既存テーマの一部を改変するときに便利です。
これらのサンプル記事は、フロントマターにタグが設定されていたり、フォルダ内で画像を配置するサンプル構成が含まれています。
内容を把握したあとは削除して大丈夫ですが、「_index.md」「post/_index.md」というファイルは、今のところ必要なので残しておいてください。もちろん内容は編集して構いません。
仕組みは後述しますが、今の段階で「_index.md」「post/_index.md」を削除すると、hugo serverがエラーメッセージを表示して更新停止してしまいます。
このレイアウトはどうやって組み立てられているのか
まずは「my-theme/layouts」の中身を見てみましょう。
二つのフォルダがあります。
- 「_defaults」
- 「partials」
まず「_defaults」フォルダから見ていきましょう。
ここにはhugoの規約上重要なレイアウトファイルが四つ格納されています。「baseof.html」が本丸です。他には「home.html」「list.html」「single.html」が生成されています。
どういうルールでこういうファイルがルックアップされているのかについては、公式の解説は以下で網羅されています。ちょっと量が多いので、今はチラ見するくらいで流しましょう。
まずは「baseof.html」の内容を見てみます。
<!DOCTYPE html>
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
{{ partial "head.html" . }}
</head>
<body>
<header>
{{ partial "header.html" . }}
</header>
<main>
{{ block "main" . }}{{ end }}
</main>
<footer>
{{ partial "footer.html" . }}
</footer>
</body>
</html>
DOCTYPE宣言がありますね。ご想像の通り、このファイルはこのテーマで生成されるすべてのページに適用されるテンプレートになります。
partial templatesについて
早速ですが、明らかにhtmlの要素ではない記載がありますね。
{{ partial "head.html" . }}
hugoのテンプレートは、golangの「html/template」パッケージを利用して実装されています。この一文もその文脈のものです。
html/templateパッケージは、テンプレートエンジンとしては割とシンプルな部類のものかと思いますが、webサイト構築に必要十分な表現力を持っています。
このpartial文は「他のテンプレートをここに埋め込む」という機能になります。部品化の要になりますのでご把握を。
partialについての公式の解説は以下にあります。
「partials/head.html」の内容を見てみましょう。
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
{{ partialCached "head/css.html" . }}
{{ partialCached "head/js.html" . }}
また新種のpartialが出てきましたね。本稿ではpartialCachedの説明は省きますので、興味があれば先の公式ページをご確認ください。
if文やelse文がありますね。このようにテンプレートでは条件分岐による柔軟な表現が可能です。
では、試しにhead.htmlの内容を先のbaseofに組み込んでみましょう。もちろん正常に動作します。
<!DOCTYPE html>
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
<!-- ⭐️⭐️⭐️⭐️ここにがっちゃんこしたよ⭐️⭐️⭐️⭐️ -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
{{ partialCached "head/css.html" . }}
{{ partialCached "head/js.html" . }}
</head>
<body>
<header>
{{ partial "header.html" . }}
</header>
<main>
{{ block "main" . }}{{ end }}
</main>
<footer>
{{ partial "footer.html" . }}
</footer>
</body>
</html>
この状態でhead.htmlを削除しても大丈夫です。もうhead.htmlは誰にも参照されていない状態です。
あなたのサイトでhead.htmlを使い回す必要がなければ、partialでの分割をやめることでレイアウトのファイル数を減らせます。header.html、footer.htmlについても同様です。
この辺はお好みでご自由にどうぞ。
ブロック「main」とContentについて
{{ block "main" . }}{{ end }}
ここに表示される内容は、表示中の文脈によって変わります。
- ルートURLなら「home.html」
- 「Posts」「Tags」などリスト系のトップを踏んだときは「list.html」
- 記事独立ページを踏んだときは「single.html」
こういったルーティングはhugoが規定しています。
先に見たように、new themeした段階で対応するテンプレートファイルも生成されています。
hugoではこのルール以外にも色々なページを作れますが、本稿では割愛します(こればっかだなこの記事)
ではまずhome.htmlの内容から見ていきましょう。
{{ define "main" }}
{{ .Content }}
{{ range site.RegularPages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ .Summary }}
{{ end }}
{{ end }}
htmlってファイル名なのが変に感じるレベルの内容ですね。かろうじてh2タグとaタグがhtmlの部品であることを主張しています。
「{{ .Content }}
」は、「home.htmlに対応するコンテンツ」をここに表示します。
home.htmlに相当するコンテンツは何かというと、「content/_index.md」になります。
先に「_index.mdを削除するとエラーになる」と書いた理由がこれです。.Contentにより参照されていたからビルドエラーになったわけですね。
逆にいえば、この参照がなくなれば_index.mdは不要になります。
運用中にカスタマイズすることがなければ、セットで削除してしまって構いません。
余談: お気づきかもですが、このレイアウトテンプレート中にそのままhtmlを書いちゃってももちろん問題はありません。home.htmlに書いた内容はそのままhomeに表示される内容になります。
個人的には、コンテンツをレイアウトテンプレートに直書きしていくと、SSGの運用としておかしなことになっていく気がしないでもないです。個人の感想ですので、そういった不安がなければあなたの自由です。
もちろんレイアウトテンプレートにコンテンツ内容を直書きすると、テーマを切り替えた時にそこに書いた情報は吹っ飛びます。
ただ、これも個人的にはあまり気にする必要はない気がします。運用中にテーマを切り替える際にはどのみち大工事になりがちですので、その時に悩めばいいかと。
この_index.mdの事例でもわかる通り、hugoはテーマを切り替えた時にコンテンツの配置が噛み合わなくなればビルドエラーにつながります。
良くいえば自由度が高い、悪くいえばテーマ間の共通化ルールみたいなことがあんまり考慮されていない、ということかもしれません。まあhtmlテンプレートエンジンって大体こういう物かと思いますが(´・ω・`)
さて、続きのコードを見てみましょう。
{{ range site.RegularPages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ .Summary }}
{{ end }}
この部分は、テンプレートのrange構文を利用して記事一覧に対する繰り返し処理をしています。
contentに含まれるmarkdownコンテンツ全てについてのページのサマリを順次表示し、aタグでリンクして表示しているわけですね。
この前後にお好みでhtmlタグやclass指定を追加してお好きなhtmlを配置することで、あなたのテーマを構築できるわけです。
CSSはテーマフォルダ側「assets/css/main.css」にあります。
ここまでで大体構成が見えてきたのではないでしょうか。
「list.html」を編集すればPostsやTag一覧のページの内容に反映されます。また、「single.html」を編集すれば記事単独ページのレイアウトを編集できます。
あとはお好きなように変更を加えてくださいね! ……といきたいところですが、その前にもう少しだけ初手で把握しておいた方が良い要素があります。今しばらくお付き合いください。
とりあえず、new theme直後のテンプレートには、ブログの形式で運用する場合に最低限必要と思われる機能が欠けています。
ページネーションです。
pagenationを実装する
ページネーションとは、投稿数が増えたときにn件で一度の表示数を分割する機能です。
「<< < 1 2 3 4 5 > >>
」みたいなやつです。これで伝わりますかね。
試しにcontentフォルダ内で、適当なmarkdownファイルを30個ほどコピペで増やしてみてください。
homeやpostsにすべての記事サマリが並んでしまいます。これはあまり良くないですね。
もちろん作るサイト次第です。ページ数を増やす必要のないwebサイトでしたらページネーションが不要な事例もあるでしょう。
本稿では、ページ数増加に備えてpagenationを組み込んでいきます。
……と言っても、hugoは組み込みのpagenation機能を持っているので、実装はとても簡単です。
公式の説明は以下にあります。
まず、hugo.tomlに以下の記載を追加してください。
サイト全体で、1ページあたり5件ごとにページネーションする設定です。
paginate = 5
続いてhome.htmlを以下のように修正してみましょう。
{{ define "main" }}
{{ .Content }}
{{ template "_internal/pagination.html" . }}
{{ range (.Paginator).Pages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ .Summary }}
{{ end }}
{{ template "_internal/pagination.html" . }}
{{ end }}
無事1ページあたり5件で区切られて、ページネーションのリンクも生成されました。
でもリンク部分が変な表示ですね。
ページリンクの表示を修正する
ここに何が生成されたのか、hugoが出力したhtmlソースを見てみましょう。
<ul class="pagination pagination-default">
<li class="page-item disabled">
<a aria-disabled="true" aria-label="First" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">««</span></a>
</li>
<li class="page-item disabled">
<a aria-disabled="true" aria-label="Previous" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">«</span></a>
</li>
<li class="page-item active">
<a aria-current="page" aria-label="Page 1" class="page-link" role="button">1</a>
</li>
<li class="page-item">
<a href="/page/2/" aria-label="Page 2" class="page-link" role="button">2</a>
</li>
<li class="page-item">
<a href="/page/2/" aria-label="Next" class="page-link" role="button"><span aria-hidden="true">»</span></a>
</li>
<li class="page-item">
<a href="/page/2/" aria-label="Last" class="page-link" role="button"><span aria-hidden="true">»»</span></a>
</li>
</ul>
はい、もうお分かりでしょう。ulとliだけで実装されています。ブロック要素ですから縦並びになっちゃいます。
hugoの組み込みのページネーション機能は、スタイルシートまでは生成してくれません。
とはいえhtmlとしては素直な文書構造ではないかと。分かりやすいクラス名も記述されています。これならあとはCSSを当てればなんとかなりそうですね。
今回は雑に以下のようなCSSを、テーマフォルダ側の「assets/css/main.css」に追記してみました。
/* internalページネーションの見た目を横一列にする */
ul.pagination {
text-align : center ;
}
ul.pagination li.page-item {
display : inline;
text-align : center;
}
ul.pagination li.page-item.active {
border-bottom : 2px solid #ccc;
padding : 2px;
}
ul.pagination li.page-item.disabled a {
color : #333;
}
hugoはscssなども使えますが本稿では以下略。
こうなりました。まあ、最低限ユーザに伝わるレイアウトではないかと……。
溢れ出るインターネット老人会のフレーバーをお楽しみください
partialを試してみよう
ここまでで、最低限ではありますがwebサイトに必要な要素が出揃いました。
本稿ではもういくつか、webサイト構築時に重要な機能をご紹介していきます。
まずはコンテンツの部品化に重要なpartialから。
partialについては、先にhead.htmlをbaseof埋め込みに変更するテストを通して挙動を確認しましたね。
head.htmlのように「意味のあるまとまり」を別ファイルに分けておきたい時にも便利ですが、partialが最も能力を発揮するのは、「各所で使い回す要素」が出てきた時です。
ちょうどここまでの作業で、home.htmlについてはページネーションに対応したものの、list.htmlはページネーションに対応していない状態です。
list.htmlテンプレートをを利用するPosts一覧に進むと、ページネーションされていないことがわかるでしょう。
list.htmlはPosts、Tagsの一覧表示に使われています。これらのページでもやりたいことはhomeとほぼ同じで、記述もほぼ同様です。
これらをpartialにしてまとめてページネーションを組み込んでみましょう。
今回は、layout/partialsフォルダに「summaries.html」を新規作成しました。
{{ template "_internal/pagination.html" . }}
{{ range (.Paginator).Pages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ .Summary }}
{{ end }}
{{ template "_internal/pagination.html" . }}
これを利用するhome.htmlは以下の通り。
{{ define "main" }}
{{ .Content }}
{{ partial "summaries.html" . }}
{{ end }}
list.htmlはこうなりました。
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ .Content }}
{{ partial "summaries.html" . }}
{{ end }}
だいぶスッキリしましたね。
部品化はwebサイト構築時の重要な機能です。共通の処理は極力partialで部品化することをお勧めします。
footerに表示する内容をhugo.tomlに定義して表示させてみよう
先のanankeテーマでは、hugo.tomlに記載することでサイト全体に反映されるオプションが多数用意されていました。
もちろん自作テーマでも、この全体設定を任意の場所で自在に活用できます。
後からまとめて変更できそうな要素があれば、片っ端設定ファイルへの定義に移動してしまいましょう。テーマ流用時の汎用性にも繋がります。
ここでは簡単なサンプルを一つだけ。
footer.htmlを以下のように書き換えます。
<p>© {{ now.Year }} {{ .Site.Params.author }}</p>
hugo.tomlには以下のように記載しましょう。
[params]
author = "testman"
これでテンプレート中「{{.Site.Params.author}}
」と記載のある場所全てに「testman」という文字列が埋め込まれます。簡単ですね。
本稿では割愛しますが、設定値により条件分岐で機能単位のオンオフができるような仕組みを作るとサイト構成を一発で変更できて便利です。既存テーマでも、不要な機能を任意でオンオフできる仕組みを提供しています。
何を設定値にすると便利かを考える際は、色々な既存テーマが提供している設定値を眺めてみるのが良いでしょう。
shortcodeを試してみよう
hugoのshortcodeという機能は、webサイト構築に必須というほどではないのですが、把握しておくとサイト構築の幅が大きく広がります。ぜひとも入門の初手で触ってみてください。
この機能はmarkdownコンテンツ側の任意の場所で「{{< shortCodeFileName >}}
」という記載を埋め込むことで、その場所で独自のhtml構築処理を呼び出せる、という機能です。
shortcodeは「layout/shortcodes」に配置することで呼び出せるようになります。
部品化という意味ではpartialと同様ですね。最大の違いは、markdownの中に記述できるという点です。レイアウトシステムの部品であるpartialでは、markdownコンテンツ内の任意の場所での任意の処理は記述できません。
公式の解説は以下にあります。
本稿ではshortcordの一例として、「フロントマターに配列で画像ファイル名を羅列して、そのファイル類を並べたhtmlを生成する」という処理を考えてみます。
以下のような記事データを考えてみましょう。
+++
title = 'ギャラリーテスト'
date = 2024-03-23T19:58:00+09:00
draft = false
tags = ['hugo']
[gallaryData]
base = 'http://example.com/'
images = [
"test1.png",
"test2.png",
"test3.png"
]
+++
## shortcodeのギャラリーテスト
テストです。
## 以下画像
{{< testImageList >}}
shortcodeの実装は以下のようになります。
layout/shortcodes/testImageList.htmlという名前で保存してください。
{{ $base := .Page.Params.gallaryData.base }}
{{ range $index, $element := .Page.Params.gallaryData.images }}
<p><img src="{{ $base }}{{ $element }}" /></p>
{{ end }}
pタグで囲ったimgタグをrange構文で並べてみました。
.Page.Params
を通してフロントマターの情報を利用できるのも便利です。
コンテンツとレイアウト組み立ての部品化、データの分離が実現できます。
フロントマターはyamlやjsonでも記述できるので、シェルスクリプトやpythonなど任意のツールで出力したjson内容で大規模データを渡してshortcodeでhtml化、といった処理も考えられるでしょう。
汎用性の高い機能ですので、是非先の公式の情報にも目を通してみてください。
終わりに
お疲れ様でした。いささか駆け足な内容だったかもしれません。
ぜひ皆様も、hugoで新規テーマを作成してサイト構築を行ってみてください。最低限、初手で必要になる情報はお伝えできたのではないかと思います。
既存のテーマの構成を読み解く際に必要な基本的なところは把握いただけたのではないでしょうか。
もちろんhugoの機能全体からすると本当に最低限です。
hugoで最初に把握しておいた方が良い要素、という観点では、本来であれば記事分類システムであるTaxonomyについての解説も入れたかったところです。本稿の文脈をテーマに絞りたかったこともあり割愛させていただきました。
最後になりますがTaxonomyについては公式の解説リンクだけ置いておきます。
hugoの便利な機能は他にもたくさんあります。是非皆様も公式サイトを調べてみてください。
皆様のhugoライフの一助となれれば幸いです。