はじめに

前回、前々回に続いて第三回目となった今シリーズですが、今回はZolaという静的サイトジェネレータ(Static Site Generators)について紹介します。

静的サイトジェネレータというと、Go言語で作られたHugoが有名ですが、ZolaはRustでコーディングされています。

また、Zolaは依存関係にある他の要素が無い、つまり単一のバイナリで作られており、Zolaに入っている要素はすべて一本のものとして組み込まれています。

言語がRustという点も気になるポイントであり、Rustは速度と堅牢性を備えた非常に頼もしい言語(しかし新しく学ぶには難しいと言われています)で、例えばブラウザではFirefoxがRustを採用しています。

他にも、ZolaではTeraというテンプレートエンジンを用いていますが、このTeraもZolaと同様にRustを使って作られています。

まだまだ知名度はそこまで高くないZolaですが、宣伝およびメモ用途を兼ねつつ、Zolaの導入から設定に至るまでを色々と説明していきます。

これまでに公開した当シリーズに関する記事は以下のとおりです。

なお、このシリーズではホスティングにVultrを使っています。

サーバのスペックおよび構成は以下の通りです。

  • $ cat /etc/lsb-release

    • DISTRIB_ID=Ubuntu
    • DISTRIB_RELEASE=20.04
    • DISTRIB_CODENAME=focal
    • DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
  • Vultr : High Frequency Server

    • 32GB MVMe
    • 1CPU
    • 1GB Memory
    • Additional Features
      • Virtual Private Clouds: ON

Zolaのインストール

Zolaの公式サイトは以下のリンクです。

  1. まず最初に$ sudo apt updateでアップデートチェックを行います。

  2. 次に$ sudo apt install snapdでSnappyと呼ばれるパッケージ管理システムをインストールします(最近のUbuntuであれば基本的に既に入っています)。

  3. snapdのインストールが完了すれば、$ sudo snap install coreおよび$ sudo snap refresh coreで最新バージョンのsnapを使用しているか確認します。

  4. 既に最新になっているあるいは更新が完了したあとは公式サイトの手順に従い、$ sudo snap install zola --edgeでZolaの最新版をインストールします。

    1. snapのinstallにはリスクレベル(Risk-levels)と呼ばれる、安定性と新機能のトレードオフで分類した項目があります。
    2. 安定している順でstable、candidate、beta、edgeの4つがあり、stableでは比較的安定した運用が可能ですが、edgeでは多くのアップデートを得ることができる代わりに何らかの不具合を有している可能性があります。
    3. $ zola --versionでZolaの現在のバージョンを確認できます。

プロジェクトを作成

  1. $ zola init プロジェクト名で自身の新プロジェクト(サイト)を作成することができます。

    1. この時作成されるルートは/home/ユーザ名/プロジェクト名となっています。
  2. 上記コマンドを入力時、複数の質問が表示されるため、それぞれ回答します。

    1. この項目はすべて/home/ユーザ名/プロジェクト名/config.tomlで変更することができるため、今は適当でも問題ありません。
1. 作成するサイトのURLを記入する。
Welcome to Zola!
Please answer a few questions to get started quickly.
Any choices made can be changed by modifying the `config.toml` file later.
> What is the URL of your site? (https://example.com):

2. Sass(Syntactically Awesome StyleSheet)を有効にするかどうか
> Do you want to enable Sass compilation? [Y/n]:

3. シンタックスハイライト(コードの色分け)を有効にするかどうか
> Do you want to enable syntax highlighting? [y/N]:

4. サイトの検索インデックスを有効にするかどうか
> Do you want to build a search index of the content? [y/N]:

5. 初期設定が完了し、/home/ユーザ名/プロジェクト名にプロジェクトが生成される
Done! Your site was created in /home/ユーザ名/プロジェクト名

Get started by moving into the directory and using the built-in server: `zola serve`
Visit https://www.getzola.org for the full documentation.

生成されたファイルを確認する

/home/ユーザ名/プロジェクト名内には複数の用途に分かれたファイルが存在します。

$ ls /home/ユーザ名/プロジェクト名でチェックすると以下のファイルが現れます。

  • Content

    • このディレクトリ内のコンテンツは、ビルド時に公開用ディレクトリであるPublicに生成されます。
  • Static

    • 名前の通り、静的コンテンツ(CSS、JS、favicon等の画像など)を保管するディレクトリ。
    • ビルド時にPublicディレクトリ直下にコピー(もしくは設定でハードリンク)されます。
  • Templates

    • サイトを構成するパーツ(index,page,taxonomies,shortcodeなど)を保管するディレクトリ。
    • ビルド時にこのディレクトリのパーツを参照しながらhtmlファイルが構築されます。
  • Themes

    • 公式サイトやGithubなどで公開されているテーマを組み込むためのディレクトリ。
    • 公式に紹介されているテーマ一覧は以下。
  • config.toml

    • サイトURLやMinify、RSSFeedにカテゴリ設定など、Zolaの様々な構成を設定しているファイル。
    • あれこれしたいという時に割と影響するため、このファイルの設定すべてを一度目を通しておくことをおすすめします。
    • Configuration
  • Public

    • ビルドを行った際に生成される公開用ディレクトリ)。
    • 設定でディレクトリ名やルートの変更が可能です。
    • html、CSS、JS、画像など、ウェブサイト閲覧に必要なすべてのファイルがこのディレクトリ内にコピーもしくは生成されます。

外部テーマを適用する

Zolaでは外部に公開されているテーマを、自身のサイトへ適用することが簡単にできます。

また、外部テーマは自作のテーマを作成したり、適用したテーマの改変を行う際にも大変参考になるため、それぞれのコードを見ておくことをおすすめします。

公式の方法に倣いつつ、今回はZulmaという外部テーマを例として構築します。

  1. まずは$ cd /home/ユーザ名/プロジェクト名/themesでテーマを保管するディレクトリに移動します。

  2. 次に/themes$ git clone https://github.com/Worble/Zulmaでgithubからthemesディレクトリにテーマをコピーします。

  3. /themes$ vim /home/ユーザ名/プロジェクト名/config.tomlで設定ファイルに追記を行います。

config.toml

# The site theme to use.
theme = "Zulma"
  1. あとは$ zola --root /home/ユーザ名/プロジェクト名/ buildでビルドを行うと、Zulmaテーマが反映された状態で静的ファイルが生成されますが、ウェブサーバ(シリーズではNginx)の公開ルートをPublicディレクトリにまだ指定していないため、$ sudo vim /etc/nginx/conf.d/ドメイン名.confで変更を行っておきます。

ドメイン名.conf

server {
## 省略 ##
    # アドレス指定後に参照されるディレクトリ
    root /home/ユーザ名/プロジェクト名/public;
## 省略 ##
}
  1. 設定を反映させるため、$ sudo service nginx restartを行います。

  2. これによりウェブサーバの公開ディレクトリがZolaのPublicディレクトリに指定されました。

テンプレートを作成する

次は実際にテンプレートを作成してみましょう。テンプレートファイルは/home/ユーザ名/プロジェクト名/templatesに格納されます。

今回の記事ではできるだけシンプルに、そして作成するファイルも少ない状態でサイトを稼働できるようにします。

Zolaのテンプレートに関するドキュメントは以下に記載されています。

作成するファイル一覧

  • index.html

    • 記事一覧ページ(index)で使われるパーツ。このファイルをメインとして扱い、一部を上書きすることで他ページに応用することが多いです。
  • page.html

    • 記事ページで使われるパーツ。後述する例ではindex.htmlを最初に読み込み({% extends "index.html" %})、その一部をpage.htmlの要素に置き換えます。
  • taxonomy_list.html

    • タクソノミー(Taxonomies)の一覧に使われるパーツ。例えばタグリストの表示やカテゴリ一覧など。
  • taxonomy_single.html

    • タクソノミーの個別ページに使われるパーツ。例えばウェブカテゴリに属する記事一覧など。
  • _index.md

    • Contentディレクトリ内に設置するファイル。このファイルが存在するディレクトリにはビルド時にセクションページ(記事一覧など)が作成されます。
    • 各タクソノミーディレクトリにこのファイルが無い場合、サイト内のメタデータに記事を登録しないため、記事一覧ページには表示されません。
    • _index.mdファイルにはconfig.tomlのように複数の設定項目が存在し、記事のソート順やページネーションなどが設定できます。
    • Sectionに関する詳細については以下を参照してください
  • config.toml

    • /home/ユーザ名/プロジェクト名直下にある、Zolaの各種設定ファイル。
    • MinifyやRSS、タクソノミーの設定など、様々な要素の可否に必要です。
  • imagesディレクトリ(必要な場合のみ)

    • Staticディレクトリ内に配置するディレクトリ。
    • Zolaでは、Contentディレクトリ内の各ディレクトリに画像などの静的コンテンツを配置することで、相対パスを使って画像を表示することが可能ですが、画像をまとめて管理したい場合、StaticディレクトリにImagesディレクトリを作成(名前は自由)し、config.tomlhard_link_static = trueにすることでStaticディレクトリ内のファイルをZolaの公開ディレクトリにハードリンクしつつ構築を行うことも可能です。ただしこの方法では絶対パスが必要です。
    • 各アセットの配置については以下を参照してください。
  • shortcodesディレクトリ(必要な場合のみ)

    • ZolaではWordpressのようにショートコードを使うことができます。
    • 例えばYoutubeのiframe埋め込みリンクを作成するようなものなど。
    • ショートコードの詳細はZolaのドキュメントを参照してください。

次にそれぞれのファイルについて、例となるコードを最低限に抑えた上で掲示します。

index.html

記事一覧で使われるパーツです。

例ではこのファイルをメインとして使用し、他パーツではこのファイルの一部を上書きします。

index.html

<!DOCTYPE html>
<html lang="ja">

  <head>

    <!-- 使用するcharsetをUTF-8に指定(文字化け防止) -->
    <meta charset="UTF-8">

    <!-- viewportをデバイスの幅に合わせる -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- URLの正規化処理 -->
    <link rel="canonical" href="{{ current_url }}">

    {% block css %}
    <!-- CSSの読み込み -->

    <!-- "get_url"は"public"ディレクトリからのルートを指定する -->
    <!-- "cachebust"を"true"にすることでURL末尾にファイル更新ごとに変化するクエリ(?=文字列)が付与される -->
    <link rel="stylesheet" href="{{ get_url(path='body.css', cachebust=true) }}"  type="text/css" media="all">

    <noscript>
    <!-- スクリプトが無効の場合に読み込む -->
    <link rel="stylesheet" href="{{ get_url(path='body.css', cachebust=true) }}" type="text/css" media="all">
    </noscript>

    <!-- /CSSの読み込み -->
    {% endblock css %}

    <!-- ウェブサイトのアイコンを指定 -->
    <link rel="icon" href="{{ get_url(path='favicon.ico', cachebust=true) }}">
    <!-- /ウェブサイトのアイコンを指定 -->

    <!-- ページタイトル -->
    <!-- "config.title"は"config.toml"から参照される -->
    <title>{% block title %}{{ config.title }}{% endblock title %}</title>
    <!-- /ページタイトル -->

  </head>
  <body>

    {% block logo %}
    <header>
      <h1 class="logo">
        <!-- "config.base_url"は"config.toml"から参照される -->
        <a href="{{ config.base_url }}">PR1SM</a>
      </h1>
    </header>
    {% endblock logo %}

    <main>

    {% block content %}
    <!-- block content -->

    <!-- for文で記事一覧をループさせて表示 -->
    <!-- "paginator.pages"を使うことでページネーション出力を行える。使わない場合はsection.pagesでも可 -->
    {% for page in paginator.pages %}
    <section>
      <div class="entry">
        <div class="posted">
          <time itemprop="datePublished" datetime="{{ page.date }}">{{ page.date }}</time>
        </div>
        <h2>
          <a href="{{ page.permalink | safe }}">{{ page.title }}</a>
        </h2>
      </div>
    </section>
    {% endfor %}

<!-- ページネーション -->
{% if paginator.number_pagers > 1 %}
<!-- ページが2以上存在するときに表示 -->
<nav class="pagination">
  <ul>
    <!-- Prevボタン -->
    {% if paginator.current_index != 1 %}
      <!-- 現在のページが1ではない時 -->
      <li><a class="previous" href="{{ paginator.previous }}">&larr;</a></li>
    {% else %}
      <!-- 現在のページが1の時 -->
      <li class="disabled">&larr;</li>
    {% endif %}

    <!-- 最初ページボタン -->
    {% if paginator.current_index != 1 %}
      <!-- 現在のページが最初ではない時 -->
      <li><a href="{{ paginator.first }}">1</a></li>
    {% endif %}

    <!-- 三点リーダ(前) -->
    {% if paginator.current_index > 3 %}
      <!-- 現在のページが3より大きい場合 -->
      <li class="disabled">...</li>
    {% endif %}

    <!-- 現在から一つ戻るボタン(数値) -->
    {% if paginator.previous and paginator.previous != paginator.first %}
      <!-- 前のページがあり、なおかつ前のページが最初でない場合 -->
      <li><a href="{{ paginator.previous }}">{{ paginator.current_index - 1 }}</a></li>
    {% endif %}

    <!-- 現在位置 -->
    <li class="current">{{ paginator.current_index }}</li>

    <!-- 現在から一つ進むボタン(数値) -->
    {% if paginator.next and paginator.next != paginator.last %}
      <!-- 次のページがあり、なおかつ次のページが最終でない場合-->
      <li><a href="{{ paginator.next }}">{{ paginator.current_index + 1 }}</a></li>
    {% endif %}

    <!-- 三点リーダ(後) -->
    {% if paginator.number_pagers - paginator.current_index > 2 %}
      <!-- ページ最大数-現在のページが2より大きい場合 -->
      <li class="disabled">...</li>
    {% endif %}

    <!-- 最終ページボタン -->
    {% if paginator.current_index != paginator.number_pagers %}
      <!-- 現在のページが最終ではない時 -->
      <li><a href="{{ paginator.last }}">{{ paginator.number_pagers }}</a></li>
    {% endif %}

    <!-- Nextボタン -->
    {% if paginator.current_index != paginator.number_pagers %}
      <!-- 現在のページが最終ではない時 -->
      <li><a href="{{ paginator.next }}">&rarr;</a></li>
    {% else %}
    <!-- 現在のページが最終の時 -->
      <li class="disabled">&rarr;</li>
    {% endif %}
  </ul>
</nav>
{% endif %}

    <!-- /block content -->
    {% endblock content %}

    </main>

    {% block js_foot %}
    <!-- bodyの最後に設置するjs -->

    <script src="{{ get_url(path='sw-loader.min.js', cachebust=true) }}"></script>

    <!-- /bodyの最後に設置するjs -->
    {% endblock js_foot %}

  </body>
</html>

{% block css %}各要素{% endblock css %}のように{% (end)block 自由な文字列 %}では各テンプレートで上書き可能なブロックを作成することが可能です。

例えば記事ページでのみ使いたいCSSがある場合は、page.html{% block css %}<link rel略>{% endblock css %}を追加することで、index.htmlで作成したブロックを上書きします。

page.html

記事ページに用いられるパーツです。

page.html

<!-- index.htmlを読み込む -->
{% extends "index.html" %}

<!-- ページタイトル -->
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}

{% block logo %}
    <header>
      <div class="logo">
        <a href="{{ config.base_url }}">PR1SM</a>
      </div>
    </header>
{% endblock logo %}

{% block content %}
      <!-- block content -->
      <article>
        <header>
          <div class="posted">
            <time itemprop="datePublished" datetime="{{ page.date }}">{{ page.date }}</time>
          </div>

          {% if page.updated %}
          <!-- 記事にupdatedが設定されている場合 -->
          <div class="updated">
            <time itemprop="dateModified" datetime="{{ page.updated }}">{{ page.updated }}</time>
          </div>
          {% endif %}

          <h1 itemprop="headline">{{ page.title }}</h1>

          <!-- 目次(Table of Content)の作成。hタグが本文にあれば作成可能 -->
          {% if page.toc %}
          <!-- ページにhタグが存在する場合 -->
            <details open>
              <summary>目次</summary>
              <nav>
                <ol>
                {% for h1 in page.toc %}
                  <li>
                    <a href="{{ h1.permalink }}">{{ h1.title }}</a>
                    {% if h1.children %}
                      <ol>
                      {% for h2 in h1.children %}
                         <li>
                           <a href="{{ h2.permalink }}">{{ h2.title }}</a>
                           {% if h2.children %}
                             <ol>
                             {% for h3 in h2.children %}
                               <li>
                                 <a href="{{ h3.permalink }}">{{ h3.title }}</a>
                               </li>
                             {% endfor %}
                             </ol>
                           {% endif %}
                         </li>
                      {% endfor %}
                      </ol>
                    {% endif %}
                  </li>
                {% endfor %}
                </ol>
              </nav>
            </details>
          <!-- /ページにhタグが存在する場合 -->
          {% endif %}

        </header>

        <div class="article-body">

<! -- 記事本文の表示 -->
<!-- "safe"を使うことでサニタイズから除外される。未使用の場合は各種タグもサニタイズされるので使用が必須 -->
{{ page.content | safe }}

        </div><!-- /article-body -->
      </article>

      <!-- /block content -->
{% endblock content %}

{% (end)block content %}を使うことでindex.htmlで使用されている記事一覧のブロックを丸ごと上書きしています。

taxonomy_list.html

タクソノミー(カテゴリ、タグ)一覧に使われるパーツです。

taxonomy_list.html

{% extends "index.html" %}

<!-- ページタイトル -->
{% block title %}{{ taxonomy.name | title }} | {{ config.title }}{% endblock title %}

{% block logo %}
    <header>
      <div class="logo">
        <a href="{{ config.base_url }}">PR1SM</a>
      </div>
    </header>
{% endblock logo %}

{% block content %}
<!-- 最上位タクソノミーの名前を表示させる -->
<!-- Categoriesという名前のタクソノミーを設定していればそれが表示 -->
    <h1>{{ taxonomy.name | title }}</h1>

    <!-- for文で記事一覧をループさせる -->
    {% for cat in terms %}
    <!-- タクソノミーの名前を個別で表示 -->
    <h2><a href="{{ cat.permalink }}">{{ cat.name | title }}</a></h2>
      <section>
        <ul class="entry">
        {% for post in cat.pages | slice(end=3) %}
          <li class="info">
            <div class="posted">
              <time itemprop="datePublished" datetime="{{ post.date }}">{{ post.date }}</time>
            </div>
            <a href="{{ post.permalink }}">{{ post.title }}</a>
          </li>
        {% endfor %}
        </ul>
      </section>
      {% endfor %}

{% endblock content %}

index.htmlと同じように記事一覧を表示しますが、なるべく重複した要素とならないように改変しています。

taxonomy_single.html

タクソノミーの個別ページに用いられるパーツ。

taxonomy_single.html

{% extends "index.html" %}

<!-- ページタイトル -->
<!-- 個別タクソノミー(最上位タクソノミー)という形にしている。例えばWeb(Categories)という表示 -->
{% block title %}{{ term.name | title }}({{ taxonomy.name | title }}) | {{ config.title }}{% endblock title %}

{% block logo %}
    <header>
      <div class="logo">
        <a href="{{ config.base_url }}">PR1SM</a>
      </div>
    </header>
{% endblock logo %}

{% block content %}
<!--  子タクソノミーの名前を表示させる -->
    <h1><a href="{{ term.permalink }}">{{ term.name | title }}</a></h1>

    <!-- for文で記事一覧をループさせる -->
    {% for post in paginator.pages %}
    <section>
      <div class="entry">
        <div class="posted">
          <time itemprop="datePublished" datetime="{{ post.date }}">{{ post.date }}</time>
        </div>
        <h2>
          <a href="{{ post.permalink }}">{{ post.title }}</a>
        </h2>
      </div>
    </section>
    {% endfor %}

<!-- ページネーション -->
{% if paginator.number_pagers > 1 %}
<!-- ページが2以上存在するときに表示 -->
<nav class="pagination">
  <ul>
    <!-- Prevボタン -->
    {% if paginator.current_index != 1 %}
      <!-- 現在のページが1ではない時 -->
      <li><a class="previous" href="{{ paginator.previous }}">&larr;</a></li>
    {% else %}
      <!-- 現在のページが1の時 -->
      <li class="disabled">&larr;</li>
    {% endif %}

    <!-- 最初ページボタン -->
    {% if paginator.current_index != 1 %}
      <!-- 現在のページが最初ではない時 -->
      <li><a href="{{ paginator.first }}">1</a></li>
    {% endif %}

    <!-- 三点リーダ(前) -->
    {% if paginator.current_index > 3 %}
      <!-- 現在のページが3より大きい場合 -->
      <li class="disabled">...</li>
    {% endif %}

    <!-- 現在から一つ戻るボタン(数値) -->
    {% if paginator.previous and paginator.previous != paginator.first %}
      <!-- 前のページがあり、なおかつ前のページが最初でない場合 -->
      <li><a href="{{ paginator.previous }}">{{ paginator.current_index - 1 }}</a></li>
    {% endif %}

    <!-- 現在位置 -->
    <li class="current">{{ paginator.current_index }}</li>

    <!-- 現在から一つ進むボタン(数値) -->
    {% if paginator.next and paginator.next != paginator.last %}
      <!-- 次のページがあり、なおかつ次のページが最終でない場合-->
      <li><a href="{{ paginator.next }}">{{ paginator.current_index + 1 }}</a></li>
    {% endif %}

    <!-- 三点リーダ(後) -->
    {% if paginator.number_pagers - paginator.current_index > 2 %}
      <!-- ページ最大数-現在のページが2より大きい場合 -->
      <li class="disabled">...</li>
    {% endif %}

    <!-- 最終ページボタン -->
    {% if paginator.current_index != paginator.number_pagers %}
      <!-- 現在のページが最終ではない時 -->
      <li><a href="{{ paginator.last }}">{{ paginator.number_pagers }}</a></li>
    {% endif %}

    <!-- Nextボタン -->
    {% if paginator.current_index != paginator.number_pagers %}
      <!-- 現在のページが最終ではない時 -->
      <li><a href="{{ paginator.next }}">&rarr;</a></li>
    {% else %}
    <!-- 現在のページが最終の時 -->
      <li class="disabled">&rarr;</li>
    {% endif %}
  </ul>
</nav>
{% endif %}

{% endblock content %}

_index.md

Contentディレクトリ内に設置するファイル。_index.mdというように_が含まれていなければなりません。

このファイルがあるディレクトリには記事一覧ページが作成され、他にもそのディレクトリにある記事はメタデータに反映されます。

また、これには複数の設定項目があります。

/content/_index.md

+++
title = "ここはContentディレクトリ直下の_index.mdです"
sort_by = "date"
paginate_by = 10
+++

上記ファイルではContentディレクトリ直下に設置しています。

記事のソートを日付順(sort_by = "date")にし、ページネーションを表示する記事数を10に設定(paginate_by = 10)しています。

titleに関してはわかりやすくするためにこのような記述にしていますが、無くても問題ありません。

次に各タクソノミーディレクトリ直下の_index.mdの例を紹介します。

  • contentディレクトリ内のディレクトリは_index.md内でrender = trueにしない限りはそのままpublicに反映されません(/content/test/を作ってビルドしても/public/test/とはならない)が、例では分類しやすいようにディレクトリには個別タクソノミー名を付与しています。

/content/個別タクソノミー名/_index.md

+++
title = "これは個別タクソノミーディレクトリ直下の_index.mdです"
transparent = true
render = false
redirect_to = "categories/web"
+++

上記ではtransparentrenderredirect_toを新たに設定しています。

transparent

transparentはそのセクションにある記事を親セクションに渡すかどうかを設定できるオプションです。

例としてwebという個別タクソノミー(カテゴリ)があった場合、transparent = trueにすると親セクション(ここではindex)でも表示することができます。

しかしtransparent = falseの場合はそのセクションは親に継承されないため、記事は個別タクソノミーページでしか表示されません。

transparentはデフォルトでfalseとなっているため、トップページなどで記事一覧を表示する際にはtrueを設定しておく必要があります。

render

次にrenderですが、これはそのディレクトリ内の記事一覧(セクション)を表示するかどうかの設定です。

例えばrender = trueの場合、/content/個別タクソノミー名/内の記事はビルド時に/public/個別タクソノミー名/記事名として公開されます。

これは一見便利そうに見えますが、config.tomlでタクソノミーを設定した場合はpublicディレクトリ内にタクソノミー名(カテゴリやタグなど)ディレクトリが作成されるため、contentディレクトリ直下の記事一覧とは異なり、重複コンテンツとなりがちです。

そのため、例ではrender = falseを設定しています。

redirect_to

当該セクションを不要にするためにrender = falseとしたい場合でも、そのディレクトリへのアクセス自体は可能であるため、正しいコンテンツへのリダイレクト(転送)設定があったほうがユーザビリティ上では好ましいです。

仮にウェブサーバ側でリダイレクト設定ができない場合はrender = trueでセクションを有効にしつつ、redirect_to = "タクソノミー名/個別タクソノミー名"を記述することで、当該ディレクトリ(不要な方)にアクセスがあった際のリダイレクトを設定できます。

ここではドメイン名/個別タクソノミー名にアクセスがあった場合、ドメイン名/タクソノミー名/個別タクソノミー名にリダイレクトするようにしています。

当ページだとhttps://www.pr1sm.com/web/にアクセスがあれば、https://www.pr1sm.com/categories/web/にリダイレクトします。

ただし、Zolaのリダイレクトはmeta refreshを用いたhtmlでの簡易リダイレクトであるため、可能であればNginx等のウェブサーバで直接301リダイレクトを設定する方が望ましいでしょう。

以下はNginxで301リダイレクトを行う際の一例です。

/etc/nginx/conf.d/ドメイン名.conf

    location = /個別タクソノミー名/ {
        return 301 https://ドメイン名/タクソノミー名/個別タクソノミー名/;
    }

    # 当サイトでの例
    #location = /web/ {
    #    return 301 https://www.pr1sm.com/categories/web/;
    #}

上記設定により、当該ディレクトリにアクセスがあった場合には、設定したディレクトリへの301リダイレクトがNginxによって行われます。

config.toml

Zolaでの様々な設定に用いられるファイルです。

以下は設定の一例です。

# ウェブサイトのURL
base_url = "https://example.com"

# ウェブサイトのタイトルとその概要
title = "ウェブサイト名"
description = "ウェブサイト概要"

# Sassを使うかどうか
compile_sass = false

# JavaScript libraryによる検索インデックスをビルドするかどうか
build_search_index = false

# 出力するhtmlをminify(縮小化)するかどうか
minify_html = true

# RSSフィードを出力するかどうか
generate_feed = false

# RSSフィードに使われるファイル名
feed_filename = "atom.xml"

# staticディレクトリ内のファイルをハードリンクするかどうか
hard_link_static = true

# ※重要※ タクソノミーを設定する
# 例ではタクソノミー名"categories"および"tags"を作成し、それに属する記事は10記事ごとにページネーションする。
# そしてRSSは無効化
taxonomies = [
    {name = "categories", paginate_by = 10, rss = false},
    {name = "tags", paginate_by = 10, rss = false},
]

[markdown]
# シンタックスハイライトを使うかどうか
highlight_code = false


[slugify]
# ※重要※ 各要素をスラッグ化するかどうか
# Zolaでは日本語等のマルチバイト文字は自動的にアルファベットに変換されてしまう
# そのため、自動変換を無効にする場合は"off"にしておく
# 例ではリンクのみを"off"にしているが、タクソノミーやパスに日本語を使う場合はそれぞれ"off"が必須
paths = "on"
taxonomies = "on"
anchors = "off"

[extra]
# ユーザが自由に定義する追加要素

上記でも指摘していますが、特に重要となるのがtaxonomiesslugifyです。

新しくカテゴリやタグといった要素を作成する場合にはtaxonomiesで最上位タクソノミーとして設定する必要があります。

設定後は記事作成時のテンプレート(記事名.md)に、タクソノミー名および個別タクソノミー名を指定することで機能します。

slugifyはZolaが自動的に行うマルチバイト文字の変換有無についての設定です。

自動変換されると日本語ではなく中国語っぽい表記でアルファベットに変換されるため、不格好と感じる場合には設定でoffにすることをおすすめします。

記事を作成と公開

では実際に記事を作成してみましょう。

ZolaのPageに関するドキュメントは以下に記載されています。

※例ではconfig.tomlでタクソノミー(categories)を作成したものとして進めていきます。

  1. まず/home/ユーザ名/プロジェクト名/content/内に個別カテゴリとなるディレクトリをFTPであるいは$ mkdir /home/ユーザ名/プロジェクト名/content/最上位タクソノミー名/個別タクソノミー名作成します。

    1. 例では最上位タクソノミーにcategories、個別タクソノミーとしてdiaryを作ります。
    2. contentディレクトリ内のディレクトリは_index.md内でrender = trueにしない限りはそのままpublicに反映されません(/content/test/を作ってビルドしても/public/test/とはならない)が、例では分類しやすいようにディレクトリにはカテゴリ名を付与しています。
  2. diaryディレクトリが作成されたので、$ cd /home/ユーザ名/プロジェクト名/content/categories/diarydiaryディレクトリに移動します。

  3. 次に$ vim 記事名.mdで記事を作成します。

    1. この記事名はウェブサイト公開時のURLに表記する名前として使用されます(example.com/最上位タクソノミー名/個別タクソノミー名/記事名)。
    2. このファイルはFTPで作成および送信しても構いません。

記事名.md

+++

# 記事のタイトル
title = "記事のタイトル"

# 記事の概要
description = "記事の概要"

# 投稿日
date = 2022-01-01

# 記事の更新日(updatedのみや月日が"00"の場合はエラーが発生するので追記時に記入する)
updated = 2022-01-01

# 記事の下書き。"true"だとビルド時に公開されない
draft = true

# 個別タクソノミー設定
# 例では"config.toml"の"taxonomies"に"categories"および"tags"を追加
[taxonomies]
# 記事内で"web"という"categories"に属する個別タクソノミーを作成
categories = ["web"]

# 記事内で"html"、"test"、"example"の"tags"に属する個別タクソノミーを作成
tags = ["html","test","example"]

# ユーザが自由に定義できる要素
[extra]

# thumbというユーザ定義項目に"thumbnail.jpg"という要素を追加
thumb = "thumbnail.jpg"

+++

ここから本文が表示される。

基本的にマークダウンで記述を行う。

<test>特殊なタグはマークダウンに対応しておらず、手打ちで入力する必要がある。</test>

何も無ければ各行は`<p>`タグで囲まれている。

上記の例では各カテゴリとタグの設定を行い、ユーザ定義としてthumb(page.extra.thumb)を設定しています。

記事の作成が完了すれば、あとはウェブサイトのビルド($ zola --root /home/ユーザ名/プロジェクト名/ build)を行うことで記事が公開されます(ウェブサーバの公開ルートも指定しておくこと)。

他にも$ zola --root /home/ユーザ名/プロジェクト名/ serveで、ローカルサーバ内で構築することも可能です。

次回はNginxリバースプロキシによる静的コンテンツ専用サーバの作成

Zolaは最初に一定の学習が必要となっているため、扱うには少し難しいかもしれませんが、各設定を理解すればすぐにブログを作成することが可能です。

ビルド速度も非常に高速なので、とてもストレスフリーであり、静的コンテンツなので表示速度もかなり素早くなっています。

基本的な構築についての解説はZolaやTeraのドキュメント、そして各テーマに加えてZolaのGithubでのissueページなどを参照すればある程度は解決することができます。

やや荒削りな記事となりましたが、Zolaでのブログ作成の参考になれば幸いです。

そして次回はNginxリバースプロキシを使った静的コンテンツ専用サーバの作成を行います。

これは画像やCSS、JSなどの静的コンテンツを別サーバでキャッシュしてクライアントに配信を行うという、本体サーバの負荷軽減を目的としたものです。

記事そのものは旧サイトで解説していましたが、それを今のサイトに移植しつつ、一部を現在の方法に置き換えて公開します。

参考資料