靜態網站產生器 Zola
Tags: zola, ssg
Published: 2021-11-08 12:28:59

[Zola](https://www.getzola.org/) 是一個以 Rust 語言開發的靜態網站產生器，特色是速度超快，了解靜態網站產生器的人都知道，每次寫完新的頁面都要經過重新建置的過程，這個重新建置的過程包含了幫我們把新頁面的連結加到所有應該有它出現的地方以及一些其它的編譯工作，這個過程會隨著網站頁面的增多而越來越久，而得益於 Rust 那媲美 C 的高效能，Zola 能夠更快速的處理完這個重新建置的工作。

## 安裝

在 elementary OS 下，用 snap 是最簡單的方式，在裝好 snapd 後，即可利用 `snap` 指令或是 [Snap 商店來安裝 Zola](https://snapcraft.io/zola)：

```sh
sudo snap install zola --edge
```

裝完之後 Zola 會在 /snap/zola/ 內，有可能要重開機讓 PATH 環境變數重載才能正常使用 zola 命令。

如果是 macOS 則是透過 Homebrew 來安裝 Zola：

```sh
brew install zola
```

## 初始化一個專案

在自己電腦的網站原始檔案，我稱之為專案，建置後發布出去的稱為網站。

假設我們要初始化一個名為 ax.dev 的專案，並且這個專案預計會發布到 https://ax.space/（純屬虛構），請服用以下指令：

```sh
zola init ax.space
```

會開始以目前最夯的對話式界面來逐步初始化這個專案：

```
> What is the URL of your site? (https://example.com): https://ax.space
> Do you want to enable Sass compilation? [Y/n]: Y
> Do you want to enable syntax highlighting? [y/N]: Y
> Do you want to build a search index of the content? [y/N]: N
 
Done! Your site was created in "/home/leon/Projects/ax.space"
 
Get started by moving into the directory and using the built-in server: `zola serve`
Visit https://www.getzola.org for the full documentation.
```

裡面的問題可以依實際需求回覆，與這幾個問題相對的設定最後會儲存在專案資料夾內的 config.toml 檔案內，事後是可以更改的。

特別注意最後一題的搜尋索引功能對華文目前是無效的，因為 Zola 是用 Elasticlunr.js 來做搜尋索引，而 Elasticlunr.js 看起來自 2017 年以後已經開發停滯，看起來不能索引華文的問題也不會做修正了，殘念です。

前面 Zola 初始化的最後它讓我們執行 zola serve 來讓這個新的專案跑起來用瀏覽器開看看：

```sh
zola serve
```

跑起來會看到下面的訊息：

```
Building site...
-> Creating 0 pages (0 orphan), 0 sections, and processing 0 images
Done in 230ms.
 
Listening for changes in /home/leon/Projects/ax.dev/{content, config.toml, static, templates, themes, sass}
Press Ctrl+C to stop
 
Web server is available at http://127.0.0.1:1111
```

用瀏覽器開 http://127.0.0.1:1111/ 應該會看到如下畫面：

![Welcome to Zola!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knobwj2n8xq4qg9almsq.png)

## 專案結構

新的專案應該會長這樣：

```
.
├── content/
├── sass/
├── static/
├── templates/
├── themes/
└── config.toml
```

- content/：專案的內容存放區，大多會用加上 front-matter 的 Markdown 來撰寫。
- sass/：專案的 SASS 樣式檔存放區，這裡面的樣式檔會被 SASS 處理器編譯過。
- static/：專案會用到的靜態檔案存放區，放在這裡的檔案不會被 zola 做修改，通常用來放圖檔。
- templates/：專案的模板檔存放區，專案建置時會把模板檔＋內容檔＋SASS 組裝成 HTML 與相關的 CSS 檔案。
- themes/：網路上抓的 zola 佈景主題檔的存放區。
- config.toml：專案的組態檔，包括初始化問答時做的設定都會在這裡。

對目前的新專案來說，除了 config.toml 有內容之外，其它幾個資料夾目前都是空的。

## 佈景主題

Zola 也有佈景主題，在 [Zola themes](https://www.getzola.org/themes/)，不過選擇比起火紅的 Hugo 要少得多…。

因為 Zola 的佈景實在是不忍直視，電梯直接向下吧！

## 模板－基礎與網站首頁

Zola 用的模板系統是同樣以 Rust 開發的 [Tera](https://tera.netlify.app/)，和大多數的模板系統一樣，Tera 也是以某個模板為基礎，在基礎模板內保留部份內容區域，讓其它模板填入那個保留的內容區域。

Tera 的語法長的像 Jinja 或 Twig 或 Liquid（其實我覺的模板語言都大同小異），模板內的原始碼也是大多由 HTML 碼構成，只在某些需要插值或邏輯處理的地方才用 Tera 的代碼插入，如果是插值，則以 `{{ variable }}` 的格式插入；如果是邏輯處理，則以 `{% if %}`、`{% endif %}` 的格式插入；如果是註解，則以 `{# comment #}` 的格式插入。

我們從基礎模板開始，在這裡建一個基礎模板並將它命名為 templates/base.html：

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AX.dev</title>
</head>
<body>
  <section>
    <div>
      {% block content %}
      {% endblock %}
    </div>
  </section>
</body>
</html>
```

這個 base.html 裡面包含了一個網頁必備的幾個元素，但有點不一樣的地方是 `{% block content %}` 與 `{% endblock %}`，這兩個 Tera 語法表示此處設定了一組名為 `content` 的區塊，而這 `content` 區塊會由其它模板或內容來做填入。

有了 base.html 這樣的基礎模板，我們就不用在每個模板頁都寫入重複的 `<head>` 等內容，未來如果要修改 `<head>` 內的資訊也只要對 base.html 做一次修改即可，透過 Zola 的建置指令它會幫我們把所有有參考到 base.html 模板的頁面都更新一遍。

接下來做第二個模板，放在 templates/index.html，顧名思義，這個模板就是我們訪客來到網站的首頁，內如如下：

```html
{% extends "base.html" %}
 
{% block content %}
<h1>
  This is my blog made with Zola.
</h1>
{% endblock content %}
```

這邊同樣也用了 Tera 的語法。首先它宣告了要用 base.html 為基礎做擴展，再來它也定義了 `content` 區塊以及內容。根據以上的模板，Zola 就會把 index.html 模板加上 base.html 模板，合併建置出網頁的 index.html，此時用瀏覽器開 http://127.0.0.1:1111/ 應該就可以看到新的 index.html 網頁。

## 內容－section 首頁

截至目前為止 content/ 還空無一物，我們來增加幾個資料夾和檔案如下：

```
content/
└── article/
    └── _index.md
```

這裡的 article 資料夾可以用任何的字眼替換，譬如說 blog，而裡面的 _index.html 則讓 Zola 知道這個資料夾是一個 section，section 是 Zola 的一種網站組織結構，以一個普通的公司網站為例，除了一般首頁、關於我們等單頁的頁面，會定期上稿的有可能是網誌與新聞稿，這時候就可以分別為它們建立 blog 與 press_release 兩個 section，兩個 section 可以各自指定它們的模板檔與一些其它參數，各自滿足不同的讀者需求。

Zola 的 section 的另外一個特性是 section 會有 section 自己的首頁，通常是放該 section 的子頁清單，可以稱為目錄頁或是 WordPress 稱呼的彙整頁，在這裡我直接稱為 section 首頁。

回到檔案身上，對 content/article/_index.md 編輯如下內容：

```toml
+++
title = "List of articles"
sort_by = "date"
template = "article.html"
page_template = "article-page.html"
+++
```

這個由 `+++` 包圍起來的區塊，在靜態網站產生器的世界裡稱為 front-matter，front-matter 用於儲存一些在網頁內容以外的設定，靜態網站產生器會讀入這些設定並做出相對的動作。

各家靜態網站產生器吃的 front-matter 格式各異，Zola 吃的是 TOML。在上面的例子裡，我們設定了幾項參數：

- title：該 _index.md 頁面的標題，因為 Markdown 是沒有像 HTML 的 `<title>` 標籤可以使用的，以及就算是改用 _index.html，我們的模板也已經把 `<head>` 都定義了，故即便是 _index.html 也是要另外在 front-matter 內設定 title 供 Zola 加工出正確的網頁標題。
- sort_by：設定該 section 首頁內要如何對旗下子頁面的呈現做排序。
- template：指定該 section 首頁要用的模板。
- page_template：指定該 section 之子頁面要用的模板。

## 模板－section 首頁

前面我們指定了 section 首頁的模板，現在就來鼓搗他，在 templates/article.html 編輯以下內容：

```html
{% extends "base.html" %}
 
{% block content %}
<h1 class="title">
  {{ section.title }}
</h1>
<ul>
  {% for page in section.pages %}
  <li><a href="{{ page.permalink }}">{{ page.title }}</a></li>
  {% endfor %}
</ul>
{% endblock content %}
```

在這個模板內我們再次以 base.html 模板為基礎做擴展。

模板內的 `{{ section.title }}` 會被前面提過的 front-matter 的 `title` 值插入。

後面利用了 `{% for %}` 迴圈幫我們把 section 內所有子頁面都一一陳列出來。

至此，用瀏覽器開 http://127.0.0.1:1111/article/ 應該會出現這個 section 首頁，但目前尚未有子頁面。

## 內容與模板－section 子頁

目前 article 這個 section 只有首頁，還沒有子內容頁，現在來新增一個 content/article/article1.md：

```markdown
+++
title = "My first post"
date = 2019-11-27
+++

This is my first article.
```

這篇文稿本身是 Markdown 的格式，最前面也有 front-matter 設定了標題與日期。

關於日期，是用來給 section 做排序的依據，但並不是上稿與否的參數，Zola 只是靜態網站產生器，並不是 CMS，不具備有預約上稿的能力，如果要控制某篇文稿是否要上，會利用在 front-matter 內設定發布狀態的方式來達成。

有了內容，還需要搭配模板才能組合成一個完整的頁面，依照前面的設定，我們新增 templates/article-page.html 這個模板：

```html
{% extends "base.html" %}
 
{% block content %}
<h1>
  {{ page.title }}
</h1>
<p><strong>{{ page.date }}</strong></p>
<p>{{ page.content | safe }}</p>
{% endblock content %}
```

這個模板裡面所使用的 Tera 標籤應該是可以很直觀的了解，唯一需要多做說明的大概就是 `{{ page.content | safe }}` 這塊，`page.content` 指的是內容檔案內真正的內容區，也就是 front-matter 以下的內容區。

再接再厲，來個 article2.md：

```markdown
+++
title = "My second post"
date = 2019-11-28
+++
 
This is my second post.
```

現在再用瀏覽器開 http://127.0.0.1:1111/article/ 應該就會看到前面剛打的兩篇文稿，並且它們是依照日期做排序的。

## 建置與佈署

在模板、內容都陸續產出之後，我們用 Zola 幫我們產出所有的 HTML 頁面：

```sh
zola build
```

所有建置完的靜態 HTML 會在專案的 public/ 內：

```
public/
├── 404.html
├── article/
│   ├── article1/
│   │   └── index.html
│   ├── article2/
│   │   └── index.html
│   └── index.html
├── elasticlunr.min.js
├── index.html
├── robots.txt
├── search_index.en.js
└── sitemap.xml
```

可以看到，Zola 的建置命令還幫我們做了 robots.txt、sitemap.xml 等一個完整的網站需要的檔案。

透過一些像是 GitLab（或 GitHub 或 Netlify 或 Vercel 或 Cloudflare Workers）這類服務的能力，整個專案可以透過 Git 推送到遠端的 Git repository，再利用遠端服務的 CI/CD 能力執行 `zola build` 與後面的佈署靜態網頁的工作。

## 參考資料

- [matter 在英語-中文（繁體）詞典中的翻譯](https://dictionary.cambridge.org/zht/%E8%A9%9E%E5%85%B8/%E8%8B%B1%E8%AA%9E-%E6%BC%A2%E8%AA%9E-%E7%B9%81%E9%AB%94/matter)