English

Rust 製の静的サイトジェネレータ Zola を使ってみる

年末年始にこのブログの SSG を Zola に移行してみた。

Zola
Zola

Zola は Rust 製の SSG で、シングルバイナリなので依存関係の管理が不要だったり、ビルドが高速であったりということを売りにしているよう。

このブログは今まで Julia 製の SSG である Franklin.jl で作っていた。Franklin の一番のメリットは数式の記述が楽というところにある。他のツールを使っていると、HTML と LaTeX の文法が競合して思った通りに表示されず、原因を探して適宜エスケープを追加するというような泥臭いプロセスが付き物だった。しかし、Franklin のパーサーはよくできていて、.tex ファイルに書いたような数式をそのまま移行してくることができる。数式中心のシンプルな文章を書く SSG としては優れていると思う。

一方で色々触っていると Franklin ではブログらしい機能を実装するのがなかなか難しいということもわかってきた。例えば pagination や “read more” リンク・多言語対応などを追加してみたかったが、どうすれば良いかわからなかった。きっとできるんじゃないかとは思うが、ドキュメントや例が見つからず、かと言って自分で実装を読むにはちょっと重い。

そう言う事情もあって別の SSG を使ってみようと思って Zola を試してみたらなかなか使い勝手が良かったので、メモを残しておこうと思う。

Zola のインストール

ドキュメント にあるように、バイナリが配布されているので適当に落としてくる。Mac であれば brew install zola で入る。

基本的な使い方

基本的な使い方はドキュメントの overview を見てほしい。Zola の標準的なディレクトリ構成を引用しておく。

├── config.toml
├── content/
│   └── blog/
│       ├── _index.md
│       ├── first.md
│       └── second.md
├── sass/
├── static/
├── templates/
│   ├── base.html
│   ├── blog-page.html
│   ├── blog.html
│   └── index.html
└── themes/

Section と page

Zola の基本的な構成要素として、section と page というものがある。おおむね、

というような理解で良いと思う。

コンテンツは /content/ 以下に配置する。上に書いた例では blog という section があり、section ページの設定が /content/blog/_index.md、個別の page 内容が /content/blog/first.md などに入っている。

テンプレート

ブログのデザインは /templates/ 以下の html ファイルと sass で指定する。これらの html では、config.toml などで定義されたパラメータが使用でき、Rust 製のテンプレートエンジンである Tera で処理される。この辺はドキュメントがあまりまとまっていないため、何か実装したい機能があれば公開されているテーマから似たようなものを探して実装を見てみるのが手っ取り早いと思う。

参考記事

ここではこれ以上 Zola の細かい説明はしないが、公式のドキュメント以外にも、以下のブログ記事が参考になったので貼っておく

Rust
Rust 製スタティックサイトジェネレーター Zola をつかう

Zola入門

こんな時どうする?

ここでは自分が必要になった個別の機能の実装方法について、備忘録も兼ねて紹介する。

Pagination を入れたい

ドキュメント に pagination のやり方が書いてあるが、少し不親切だと思う。

Paginate したいセクションがある場合は、まず _index.md ファイルの paginate_by 変数を正の値にする。例えばブログ記事を日付順に、ページあたり10記事表示したいなら、/content/blog/_index.md の front matter を以下のようにする。

+++
sort_by = "date"
paginate_by = 10
+++
...

これでテンプレート内で paginator が使えるようになる。例えばトップページに記事へのリンクを貼るには /templates/index.html に以下を追加すれば良い。

...
{% for page in paginator.pages %}
<ul>
    <li>
        <a href="{{ page.permalink | safe }}">{{ page.title }}</a>
    </li>
</ul>
{% endfor %}

言語ごとにリンク先を切り替えたい

日本語ページを見ているときに、“About” をクリックすると、日本語の About ページへ、英語で見ているときは英語の About へ移動するようにしたい。そのような場合は、get_urllang パラメータを与える。

<div class="site_title">
    <div class="menu">
      <a href="{{ get_url(path="@/about/_index.md", lang=lang) }}">{{ trans(key="about", lang=lang) }}</a>
    </div>
</div>

セクションのディレクトリ構造に階層を設けたい

/content/ に以下のような階層を設けたい場合、

├── content/
│   └── blog/
│       ├── 2022/
│       │    ├── _index.md
│       │    ├── first.md
│       │    └── second.md
│       ├── 2023/
│       │    ├── _index.md
│       │    ├── first.md
│       │    └── second.md
│       ├── index.md

子階層の /content/2022/_index.md などに transparent=true を設定する。

+++
transparent = true
+++

リダイレクトを設定したい

他のブログサービスや SSG から乗り換える場合、パスの構造が変わってしまうのでリダイレクトを設定する必要がある。これはページの aliases で設定可能

+++
title = "タイトル"
aliases = ["/old/path/to/this/article", "/old/path/to/this/article/2"]
+++
...

脚注表示のバグを直したい

この issue に上がっている通り、現在 footnote 機能にいくつかバグがある。まだ完全には解決されていないようで、issue 内では javascript を使った一時的な workaround が紹介されている。

Javascript ファイルを static/footnote.js などとして保存し、テンプレート html に <script type="text/javascript" src="/footnote.js"></script> を追加すると良い。

目次を好きな位置に表示したい

Zola 標準の機能で生成できる目次は、ページ内の好きな位置に挿入することができない。ちょっと面倒だが、以下で紹介されているマクロを用いる

Choose table of content position - Zola

このブログでは少し修正して

{%- macro toc(toc, level, depth) %}
{%- if level == 1 %}
<div class="toc">
<h3>Table of contents</h3>
{%- endif %}
<ul class="h{{ level }}">
{%- for h in toc %}
    <li>
        <a href="{{ h.permalink | safe }}">{{ h.title }}</a>
        {% if h.children and level < depth -%}
            {{ self::toc(toc=h.children, level=level+1, depth=depth, heading=false) }}
        {%- endif %}
    </li>
    {%- endfor %}
{%- if level != 1 %}
</ul>
{%- endif %}
{%- if level == 1 %}
</div>
{%- endif %}
{%- endmacro %}


{# =================== #}
{# === replace toc === #}
{# =================== #}
{%- macro format_content(resource) %}
	{%- set content = resource.content %}
	{%- if content is containing("<!-- toc -->") %}
		{%- set content = content | replace(from="<!-- toc -->", to=self::toc(toc=resource.toc, level=1, depth=resource.extra.toc_depth | default(value=1))) %}
	{%- endif -%}
	{{ content | safe }}
{%- endmacro %}

という形で使っている。これでマークダウン中に <!-- toc --> と書くと、その部分に目次を挿入してくれる。

GHA でカスタムドメインを使ってデプロイしたい

README にこっそり書いてあるように、/static/CNAME というテキストファイルを作ってドメイン名を書いておくと、自動で処理してくれる。

まとめ

Zola で一通りブログを書き換えてみたが、求めている機能が十分あり、ドキュメントや example を漁ればそんなに詰まることなく実装できて今のところ満足している。Franklin を使っているときは、毎回 Julia のランタイムを起動する必要があったりするのも地味に面倒だったが、バイナリを一つインストールすれば使えるというのもシンプルで良い。

一方数式の記述については Mathjax を普通に使うだけになったので、Franklin 時代に比べ多少面倒になった。以前書いた記事の数式も結構書き換える必要があった。

今後しばらくは使い倒していきたい。