Hugoでの記事ファイルの配置と画像管理

Hugoでの記事ファイルの配置と記事に貼り付ける画像の管理方法について書きます。 この記事を含めて、やってみて分かった的な Hugo 関連の内容をしばらく続けます。

公式サイトではおそらく推奨されないようなやり方も含んでいますので、あくまで参考程度で。

Hugo での content 配下のファイル配置

Hugo の記事ファイルは(設定で変更できますが)デフォルトでは/content以下に置きます。ちなみにこうしたファイルパスの説明で/はプロジェクト(サイト)のルートを表します。

content 配下には普通、記事全体をあらわす一つの_index.mdを置き、これがレイアウトファイル側の/layouts/index.htmlと結びつき、URL的にはサイトルートを表すトップページです。

content 配下に何らかのフォルダを作ると自動的にセクションとして扱われ、そこにまた_index.mdを置けば、レイアウト側の/layouts/_default/list.htmlなどと結びついて、それがそのセクションのリストページになります。例えば/content/post/_index.mdを置けば、postセクションの最上位ページになります。セクションの名前は自動的にフォルダ名、つまりこの場合だと「post」になりますが、/content/post/_index.mdのフロントマターで

title: "記事"

などとすれば、セクションの表示名を「記事」にできます。つまり_index.mdtitleプロパティを設定することでトップページやセクションリストページのタイトルを好きなように変更できるわけです。もちろんレイアウトファイル側で.Page.Titleといったページ変数を上手く使う必要はありますが、テンプレートの話は他サイトを見て下さい。

このcontent 配下の_index.mdや記事ファイルと layouts 配下のレイアウトファイルを、Hugoがどういう順序で探してどう結びつけるかは公式サイトに優先順位の説明があります。

gohugo.io

このレイアウトファイル検索順序をふまえた上で、最低限こんなレイアウトファイルを用意すればサイトが表示されるという話は、ここで以前に書きました。

marbles.hatenablog.com

詳しい話は省略しますが、公式サイトや日本語の解説サイトを見て下さい。ちなみに私が Hugo でサイト構築する上で、公式サイト以外で一番参考にさせていただいているのはこのサイトです。たぶん一番わかりやすい。

maku77.github.io


Page Bundle

Hugoには、content DIR配下に記事ファイルを作っていく上での一定のルールというか指針のようなものがあって、これをページバンドル (Page Bundle) と呼んでいます。

gohugo.io

Page Bundle には ブランチ(Branch Bundle) と リーフ(Leaf Bundle) の 2種類あって、Branch は「枝」、Leaf は「葉」と言う意味ですよね。アンダーバーがファイル名の先頭に付く_index.md を置いたDIRは Branch Bundle 、アンダーバーが付かないindex.md を置いたDIRは Leaf Bundle となります。

セクションページの配置方法はすでに説明したとおりですが、個別の記事ファイルはこのセクションフォルダ配下に何らかの記事専用フォルダを作って、そこにindex.mdを置いて記事とします。いきなり適当な名前のマークダウン、例えば/content/post/post01.mdとかを置いても、Page Bundleルール上では、あくまで所属セクションの_index.mdの付加ファイル(リソース)であって、厳密な意味では個別記事ファイルでは無いことになります。 この場合post01をフォルダ名とし、/content/post/post01/index.mdを作る必要があります。また記事の作成日時などを正しく記して Hugo が認識できるファイルにするためには、手動で作らず、コマンドラインからhugo new ファイルパスとするほうがベストなようです。

hugo new post/post01/index.md

これ馴れてこないと面倒ですが、このやり方が確実です。で結局 Page Bundle のルールについてあまり詳しく知らなくても、

  1. ブランチは、content 配下の_index.mdが置かれたセクションを表すフォルダ(とその同階層ファイル、配下の個別記事フォルダやサブセクションを含む)
  2. リーフは、個別記事を表すセクションフォルダ配下のindex.mdが置かれた記事フォルダ(とその同階層ファイル)

という理解でとりあえず使えます。逆に言えば content 配下にフォルダを作っても、その直下に_index.mdを置くかindex.mdを置くかで役割りが違ってくるということです。

ちなみに今使ってるHugoのバージョン(hugo v0.81.0-59D15C97+extended linux/amd64)では、content 配下にフォルダを作っても、そこに_index.mdindex.mdも置かなければ、セクションとしても記事としても認識されずただのフォルダです(内部的にはセクションなのかもしれませんが)。 例えばサイドバーでセクション階層のリストアップをしていても出てこない。

だから私の場合/content/documentsとか/content/documents/videosとか作って、他の記事から参照する資料フォルダにするような使い方をしています。これは/documents/videos/xxxx.mp4といったURLで個別記事やリストからアクセスできます。

セクションや個別記事のそばに置いたファイル

では_index.mdindex.md以外の、それらのファイルと同階層に置いたファイルはどう扱われるのでしょうか?

  1. まずブランチから見ると、その内部のフォルダは個別記事やサブセクションのために作るものなので、リソースファイル用のサブディレクトリを持つことができません。だからセクションの_index.mdから参照するリソースファイルは同じ階層に置く必要がある。
  2. またリーフはシングルページ用のindex.mdを置く場所なので、「葉=末端」という解釈であり、その中にサブセクションやサブ記事フォルダを置くとかはできません。ただ逆に、ブランチと違ってリソースファイル用のサブディレクトリを持つことができます。リーフのリソースはその配下の全てのファイルです。

例えば/content/post/post01/index.mdと同じ階層にimgというフォルダを作り画像を置けば、そのindex.mdからimg/xxxx.jpgなどといった相対パスでアクセスできます。あまり深い階層はおすすめできませんが、ルール上どこまで深くてもindex.mdと同階層直下なら一応リソースファイルとして扱われるそうなので、やったことは無いですが、/content/post/post01/img/001/001-01.pngとかでもいけるんだと思います。

このリソースファイルというのはブランチでもリーフでも同様に、画像以外 Hugo が読み込み可能な全てのファイルで、PDFや動画などもその階層のindex.mdからなら参照できます。 ただ、いろんな解説記事には別のマークダウンファイルですらリソースになると書いてあったりしますが、実際やってみるとあまり上手くいきません(HTMLとして書き出されない、何かやり方あるのかな〜? テストはしていませんが、たぶん対応するレイアウトファイルが必要なのでしょう)。

記事ファイル配置のサンプル(やや変則技)

以下は実際使っている Hugo で作ったサイトの/content配下のDIR構成の一部です。 ターミナルのtreeコマンドで 7階層下まで表示させた例です。

$ tree -L 7 content
content
├── _index.md (1)
├── post-01.md (2)
├── trv
│   ├── _index.md (3)
│   └── ovs
│       ├── _index.md (4)
│       ├── fr
│       │   ├── 01 (5)
│       │   │   ├── img (6)
│       │   │   │   └── duck.jpg
│       │   │   └── ref-01 (7)
│       │   │       ├── img
│       │   │       │   ├── ref-01-IMG.png
│       │   │       ├── index.md
│       │   │   ├── ref-02.md (8)
│       │   └── _index.md (9)
コンテンツルート

(1)の_index.mdがトップページにアクセスする場合のindex.htmlになります。ローカルサーバだとhttp://localhost:1313/で表示されるページ。

コンテンツルート直下のマークダウンファイル

(2)はコンテンツDIR直下のマークダウンファイルですが、これはトップページのリソースになります。実際はレイアウトファイルを適切に処理すれば、/post-01/index.htmlが書き出されhttp://localhost:1313/post-01/でアクセスできます。フロントマターのurl属性で例えばurl: /post-01.htmlなどとすれば、hugoコマンドでビルドした場合でも、フォルダを作らずに(/post-01/index.htmlを書き出さずに)ルート直下に/post-01.htmlで書き出され、http://localhost:1313/post-01.htmlでアクセスできます。

実はこのフロントマターのurl属性を使って個別のファイルの書き出しをコントロールする知識が役立ちます。またこのようにhugoコマンドでビルドしてみるのが、Hugo のサイト構築の仕方を理解する早道で、いろんな事が分かりました。このことは後でも述べます。

セクションDIR

この例では、コンテンツDIR直下にはtrv(「旅行」のつもり)というセクションがあり、その中にovs(「overseas」=「海外旅行」のつもり)というサブセクション、さらにその下にfr(「フランス」のつもり)というサブセクションがあり、それぞれに_index.mdを持っています。(3)(4)(9)

個別ファイル

(7)のref-01は個別記事のフォルダです。だからindex.mdを持っています。


やや変則技な配置

ここまでは一見 Page Bundles のルールどおりなのですが、あれ?っと思った方は鋭い。上記のDIR構成を見て変だと思ったでしょう。 (7)のref-01フォルダは、ルールどおりだと本来/content/trv/ovs/fr/の直下にあるべきですが、ここではさらにfrの下に01フォルダを作ってその中にあります。

  1. 01フォルダには_index.mdは存在せず、従ってこれはセクションフォルダでは無いのでこの配置は Page Bundles のルールからは違反です。
  2. またこの01フォルダのなかに直にref-02.mdという記事ファイルがあります。これはfrの直下に置くかref-02/index.mdとして置くべきなので、これも変則です。

実際はhugoコマンドでビルドしてみると分かるのですが、これでもやり方しだいで成り立ちます。(英文を全部読んだわけでは無いので自身はありませんが)公式サイトには書いていないと思われるので、変則技と思っています。良い子はマネしないで下さいw


フロントマターのurl属性を活用する

この変則技を成り立たせるコツが、フロントマターのurl属性を使いこなすことです。とにかくhugoコマンドでビルドしてみてわかってきます。これは個別ファイルとその個別ファイルに貼る画像ファイルの管理にも関わることです。

画像ファイルの管理

最初 Hugo では画像ファイルはプロジェクトルートの/static以下に置くルールが推奨だったそうです。/static/img/などといったDIRを作り、そこに画像ファイルを置くと、個別記事などから/img/xxxx.pngなどとサイトルートから始まる短いURLでアクセスできる。これはこれで楽ですし、サイト全体で使う画像などはこのやり方が理にかなっています。

しかし個々の記事にしか使わない画像までこの配置にしていると、場合によっては膨大な画像ファイルの数を/static/img/などにぶっこんでしまうので管理が大変です。そういう問題もあって Page Bundles のルールができて、セクション階層/個別記事フォルダ/index.mdの直下にリソースを置くことが推奨され管理しやすくなったわけです。

ただそれ以外の方法だとなぜダメなのでしょうか?


リソースの書き出され方を見てみる

そもそも Page Bundles のルールではどうして個別記事名.mdというファイルを作って、同階層にimg/xxxx.pngといった配置で画像を置くのではなく、個別記事フォルダ/index.mdとして、その配下にリソースを置くのでしょうか?

個別記事フォルダ/index.mdの形にこだわって開発されているのはURLを綺麗にしたい(/aaa/bbb/xxxx.htmlというHTMLファイル名むき出しのパスがイヤ)というのは当然あるでしょう。実際 Hugo じゃなくても JavaScript系のフレームワークでも、今は何らかのフォルダを作って中にindex.htmlを置くやり方が主流ですね。

ただ、ユーザから見ると、個人が作るブログのようなサイトで、何がなんでも拡張子.htmlを見せたくないってほどのこだわりは無いとおもいます。このへんは個人の好みの問題にもなってきますが。

そんな REST命的な理想は置いておいてテクニカル的な面だけみると、hugoコマンドでビルドしてみることで実際の理由が分かります。例えば/post/ref-01.mdというファイルを作って、同階層にimg/xxxx.pngといった配置で画像を置くと、書き出されるのは(意地でも!)/post/ref-01/index.htmlになり、同階層に置いたimg/xxxx.png/post/img/xxxx.pngに書き出される。

つまり/post/ref-01/index.htmlから見ると、一つ上のDIRに画像フォルダが動いてしまう。

これだと個別記事内で相対リンクでimg/xxxx.pngとしてもリンクが外れてしまいます。だからPage Bundles のルールを守ってねということになります。

uglyurls

config.tomluglyurls=trueにすることで、強制的に/post/ref-01.md/post/ref-01/index.htmlにしないという方法もあり、そちらがいいという方もいるようですが、記事の書き出し方法全てに影響するので、私は使っていません。これも若干好みの問題です。

ただuglyurls=trueは非推奨な扱いなので、なにか理由があるのかもしれません。知らんけどw

フロントマターのurl属性でビルドをコントロール

実は上記(7)ref-01/配下のindex.mdには、フロントマターのurl属性でパーマリンクが指定してあります。

---
url: trv/ovs/fr/01/ref-01/
---

これはあってもなくても良いような気がするんですが、パスに01という、セクションでも無いただの分類用フォルダがあっても問題なく出力できています(この点はたぶんurl属性が無くてもそうなる)。セクションではない01の配下ではありますが、形はリーフのルールなので記事内でimg/ref-01-IMG.pngに普通にアクセスできています。

また(8)のref-02.mdurl属性はこうなっています。

---
url: trv/ovs/fr/01/ref-02.html
---

こうしておくことで/trv/ovs/fr/01/ref-02/index.htmlとして書き出されず指定したURLどおりになるので、/trv/ovs/fr/01/img/duck.jpgといった同階層の画像フォルダにもアクセスできています。


ここで表面上 Page Bundles のルールに従ったref-01/index.mdと、ルールからはみ出したref-02.htmlを、セクションでもない共通フォルダ01配下に置いたことが特徴です。

本来/content/trv/ovs/fr/の直下に置くべきですし、直下に置いた別のファイルも使えるのですが、あえて分類用の01フォルダにそれらをまとめても、個別記事のフロントマターでurl属性の値(パス)さえ適切に設定しておけば問題ないことがわかりました。 例えばあるセクションの配下の記事数が50とか100とかに増えても管理しやすく、また作りたくない深い階層のセクションフォルダを作って管理する必要もない。

これはどういう目的のサイトにするかにもよるので、ある種のムダ知識ですが、例えば大量のブックマークを記事化したサイトなどだと使える発見と思います。