年末年始にこのブログの SSG を Zola に移行してみた。
Zola は Rust 製の SSG で、シングルバイナリなので依存関係の管理が不要だったり、ビルドが高速であったりということを売りにしているよう。
このブログは今まで Julia 製の SSG である Franklin.jl で作っていた。Franklin の一番のメリットは数式の記述が楽というところにある。他のツールを使っていると、HTML と LaTeX の文法が競合して思った通りに表示されず、原因を探して適宜エスケープを追加するというような泥臭いプロセスが付き物だった。しかし、Franklin のパーサーはよくできていて、.tex
ファイルに書いたような数式をそのまま移行してくることができる。数式中心のシンプルな文章を書く SSG としては優れていると思う。
一方で色々触っていると Franklin ではブログらしい機能を実装するのがなかなか難しいということもわかってきた。例えば pagination や “read more” リンク・多言語対応などを追加してみたかったが、どうすれば良いかわからなかった。きっとできるんじゃないかとは思うが、ドキュメントや例が見つからず、かと言って自分で実装を読むにはちょっと重い。
そう言う事情もあって別の SSG を使ってみようと思って 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/
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 の細かい説明はしないが、公式のドキュメント以外にも、以下のブログ記事が参考になったので貼っておく
ここでは自分が必要になった個別の機能の実装方法について、備忘録も兼ねて紹介する。
ドキュメント に 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_url
に lang
パラメータを与える。
<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 -->
と書くと、その部分に目次を挿入してくれる。
README にこっそり書いてあるように、/static/CNAME
というテキストファイルを作ってドメイン名を書いておくと、自動で処理してくれる。
Zola で一通りブログを書き換えてみたが、求めている機能が十分あり、ドキュメントや example を漁ればそんなに詰まることなく実装できて今のところ満足している。Franklin を使っているときは、毎回 Julia のランタイムを起動する必要があったりするのも地味に面倒だったが、バイナリを一つインストールすれば使えるというのもシンプルで良い。
一方数式の記述については Mathjax を普通に使うだけになったので、Franklin 時代に比べ多少面倒になった。以前書いた記事の数式も結構書き換える必要があった。
今後しばらくは使い倒していきたい。