Posted on ::

zola 是一个静态网站生成器(static site generator, SSG),与 HugoJekyll 等类似。

SSG 的功能是,使用模板引擎,将原始内容转换为静态 HTML 网页。SSG 适合搭建博客、知识库、落地页等。所谓静态网站,其所有的页面都是事先生成好的,而不是在用户访问时当场生成(当场生成即为「动态」)。我们日常访问的大部分网站是动态网站,它们最明显的特征是,多次访问同一个 url 时,用户看到的内容可以是不同的。

zola 支持用 CommonMark 撰写内容,并使用 Tera 模板引擎将网站内容渲染为 HTML。CommonMark 是一个严格定义的 Markdown 规范。

zola 是用 Rust 语言实现的,代码仓库在 https://github.com/getzola/zola ,20000+ 行代码,规模不算大。zola 使用 pulldown-cmark 解析 Markdown 文件。Tera 模板引擎的语法,与流行的 Jinja2 相似。

Quick Start

zola 是一个命令行工具,我们可以根据官方文档 Installation页面的提示,在各个操作系统上安装 zola。

这里我们通过 zola 搭建一个博客,展示 zola 的基本用法。

首先,我们需要初始化博客项目。

$ zola init yfamingblog

zola 会询问如下问题:

> What is the URL of your site? (https://example.com):
> Do you want to enable Sass compilation? [Y/n]:
> Do you want to enable syntax highlighting? [y/N]:
> Do you want to build a search index of the content? [y/N]:

根据你的需求回答即可。如果不明白,也可以直接按回车,zola 会选择默认答案。不用担心,以后可以在配置文件里修改。

执行完毕,我们发现 zola 已经创建了 yfamingblog 目录,这就是我们博客网站的项目目录了。

$ cd yfamingblog
$ tree
.
├── config.toml  # 配置文件
├── content      # 内容,里面放 markdown 文件
├── sass         # sass
├── static       # 静态资源 CSS,Javascript,图片什么的,都可以放这里
├── templates    # 模板
└── themes       # 主题

打开检查一下,config.toml 里的内容,正是 init 时提供的答案。而 content, sass, static, templates, themes 均为空目录。

虽然现在博客里什么内容也没有,但它已经是个完整的网站了,我们可以看看效果。

$ zola serve
Building site...
Checking all internal links with anchors.
> Successfully checked 0 internal link(s) with anchors.
-> Creating 0 pages (0 orphan) and 0 sections
Done in 49ms.

Web server is available at http://127.0.0.1:1111 (bound to 127.0.0.1:1111)

Listening for changes in /.../yfamingblog/{config.toml,content,sass,static,templates}
Press Ctrl+C to stop

根据提示,在浏览器里打开 http://127.0.0.1:1111/ ,就可以访问了。

注意截图里的那一行字,"You're seeing this page because we couldn't find a template to render"。看到这个页面,是因为 zola 找不到与这个页面对应的模板文件。如果我们没有为页面(Markdown 文件)设置好对应的模板,它就会被渲染成这样。记住这个页面,看到它就意味着哪里出问题了。

接下来需要做的事情,已经体现在这几个空目录里面了。我们需要给博客填充内容,设置模板和主题等等。

但是,我们不必等这一切完成,现在就可以构建并部署了。

$ zola build
Building site...
Checking all internal links with anchors.
> Successfully checked 0 internal link(s) with anchors.
-> Creating 0 pages (0 orphan) and 0 sections
Done in 19ms.

构建之后,项目里会增加 public 目录,这就是用来保存构建结果的地方。

我们部署网站的时候,需要把这个目录里的内容全部 copy 到服务器。一般来说,我们会通过自动化程序,比如 GitHub Actions 来帮我们完成构建和部署。

除了部署到自己的服务器,我们也可以部署到 GitHub Pages,Cloudflare Pages,Vercel,Netlify 等平台。这些平台都有免费计划,支持绑定自己的域名,支持 HTTPS,而且支持从 GitHub 仓库自动部署。这样的话,运营一个博客就只剩域名成本了,每年 $10 左右就足够。

zola 的命令行非常简洁,至此我们已经使用过 initservebuild 3 个子命令,差不多覆盖了 zola 的全部功能了。

$ zola -h
A fast static site generator with everything built-in

Usage: zola [OPTIONS] <COMMAND>

Commands:
  init        Create a new Zola project
  build       Deletes the output directory if there is one and builds the site
  serve       Serve the site. Rebuild and reload on change automatically
  check       Try to build the project without rendering it. Checks links
  completion  Generate shell completion
  help        Print this message or the help of the given subcommand(s)

Options:
  -r, --root <ROOT>      Directory to use as root of project [default: .]
  -c, --config <CONFIG>  Path to a config file other than config.toml in the root of project [default: config.toml]
  -h, --help             Print help
  -V, --version          Print version

completionhelp 都是辅助功能,可忽略。而 checkbuild 一样尝试构建页面,只是不会将结果写入 public 目录。同时 build 还会检查 Markdown 文件的所有外链。

当我们把博客网站初步搭建起来,设置了模板和主题,并且完成了 build 和部署之后,网站就算正式上线了。

在此之后,当我们发布新的文章时,只需要在 content 目录添加一个 Markdown 文件,然后进行 build 和部署操作即可。

博客规划

Quick Start 让我们对 zola 有了初步的体验。但是在正式开始搭建博客前,我们需要规划一下,确定博客需要哪些页面,展示什么内容,使用怎样的风格,支持哪些功能。

好在个人博客非常简单也很成熟,近乎标准化了,以下是我的规划。

博客需要的页面包括:

  • / 首页,最近的 10 篇博客列表,展示发布时间和标题。
  • /post/xxxx 博客文章页,包括标题、发布时间、tags、正文。或许考虑在右侧顶部固定展示文章大纲,以方便阅读长文。
  • /about 博客的介绍页。
  • /rss RSS 订阅。
  • /archives Archive 页。列出文章标题和发布时间,按年份分组。暂时不需要,超过 20 篇再考虑。
  • /tags 暂时不需要,超过 20 篇再考虑。

如果有可能,希望使用「霞鹜文楷」字体。但这个优先级不高,上线之后再折腾吧。

每个页面顶部,需要一个导航条,放上 AboutRSS 链接。以后内容更多了,再考虑在导航条上也加上 TagsArchives 链接。

网站采用单栏式布局,保持简洁。考虑到阅读长文时,展示文章大纲(toc)会更方便,在文章页采用双栏式布局也是可以的。

zola 自带了搜索功能,但是考虑到通过 Google 的 site 语法搜索特定网站已经很方便了,因此就不添加搜索了。

评论功能,暂时也不需要。主要原因是初期评论会很少,而且会招来一些 spam 流量,不划算。未来也许会考虑接入一个现成的评论系统。

访问统计功能,直接用 Google Analytics 等常用工具就行,优先级不高。

博客主题将会决定绝大部分的展示效果,挑选一个符合需求的主题,就成了最重要的事情。zola 已经有 100+ 个主题,浏览之后,我决定使用 apollo 主题。

深入了解 zola

接下来,我们一边深入了解 zola,一边完成博客搭建工作,边学边干。

个人博客是非常简单的,但读 zola 文档,时常会发现有些地方比博客复杂多了。此时我们可以想象有一个比较早期的资讯网站,它存在多个频道,每个频道都有自己的文章列表。每个频道都支持单独的 RSS 订阅,首页上还需要对频道排序等等需求。结合这个网站的需求来理解 zola,它的设计就显得合理很多了。

内容 contentsectionpage

content 目录的结构,决定了 zola 生成的静态网站的结构。content 里的每一个 Markdown 文件生成一个 HTML 文件。而且,content 的子目录和文件的层次结构,也反应在 HTML 的 URL 路径里。

比如,content 目录结构及对应的 HTML 页面如下:

$ tree content
content
├── about.md               # <base_url>/about/
└── post
    ├── _index.md          # <base_url>/post/
    ├── blog_with_zola.md  # <base_url>/post/blog-with-zola/
    └── hello_world.md     # <base_url>/post/hello-world/
└── legacy
    └── whatever.md        # <base_url>legacy/whatever.md/

content 里的 Markdown 文件,被称为 pagecontent 里的子目录,有时候我们希望为它也生成一个 HTML。此时我们需要在这个子目录里添加一个名为 _index.md 的 Markdown 文件,子目录的 HTML 内容由它决定。如果一个子目录里包含 _index.md 我们则称它为 section

zola 生成的 HTML 页面,要么是一个 section,要么是一个 page

子目录里可以继续添加子目录,所以 section 也是层次结构的,一个 section 可以有多个子 section

_index.md 用于表示 section 了,那么自然不会被视为 page 了。如果子目录里没有 _index.md 那么这个目录就不是 section,不会为这个子目录生成 HTML 文件。但是,无论子目录是不是 section,它里面的 Markdown 文件都会作为 page 生成 HTML 文件。

总结一下,一个 section 可以拥有 0 个 1 个或多个 page,一个 section 可以拥有 0 个 1 个或多个子 section。但是,一个 page 可以属于某个 section 也可以不属于任何 section(称为 orphan page)。

这里的示例中,post/ 子目录就是一个 section,而 legacy/ 不是。因此,post/ 子目录会生成一个对应的 HTML 页面,而 legacy/ 子目录则不会。但是,legacy/wahtever.md 仍然会生成一个 HTML 页面。

zola 在生成 HTML 时,如果当前生成的是 section,那么就可以在 Tera 模板里使用 section 变量,并且通过 section.subsectionssection.pages 来访问子 section 和所拥有的 page。如果当前生成的是 page,那么就可以在 Tera 模板里使用 page 变量。

值得一提的是,网站首页也被视为一个 section,无论是否存在 content/_index.md 文件。content 目录里定义的所有 section 均为首页 section 的子 section,且 content 目录里直接定义的 page 都归首页 section 所有。

我们可以在 Markdown 文件中通过 front matter 给 sectionpage 添加元信息,比如标题、发布日期、作者、tag 等等。

+++
title = "Hello World"
date = 2025-06-30
slug = "hello_world_with_underscore_instead_of_hyphen"
template = "article_v2.html"
+++

front matter 被 Markdown 工具广泛支持。它必须出现在 Markdown 文件的最前面,处在两个 --- 行,或者 +++ 行之间。而 front matter 的内容,则要遵守 YAML 或者 TOML 格式。

zola 支持的 front matter 需要放在两个 +++ 行之间,并且使用 TOML 格式。(注意,zola 也支持 --- + YAML 格式的 front matter 以方便遗留系统迁移。)

sectionpage 支持的 front matter 字段,分别见 Section - Front MatterPage - Front Matter

值得一提的是,sectionpage 都支持在 front matter 中通过 template 字段,来指定生成 HTML 时应当使用的模板文件。

section 还支持通过 page_template 字段来指定下属 page 在生成 HTML 应当使用的模板文件。当然,page 自己通过 template 指定的优先级更高一些。

为了方便 Markdown 之间的相互引用,zola 添加了一个新的链接语法。形如:

[hello world](@/post/hello_world.md)

这样就可以在 Markdown 中添加对 content/post/hello_world.md 的链接了。这里 @/ 可视为 content/,但写起来更简洁。

模板 templates

SSG 的功能用一句话描述,就是 内容(Markdown)+ 模板 ==> HTML 文件

在上一节我们了解过内容 content 的工作机制。content 目录的结构,决定了 zola 生成的静态网站的结构。而且 zola 生成的 HTML 页面,要么是 section,要么是 page

这一节我们来了解模板,即 template 目录里的内容,以及,当渲染一个 sectionpage 时,应当选择哪个模板文件。

zola 使用 Tera 作为模板引擎。与所有其他的模板引擎类似,Tera 支持变量 {{ var }},分支判断 {% if ... %}...{% endif %},循环 {% for e in ... %}...{% endfor %},还有 filter,模板继承等常见的功能。

zola 在生成 HTML 时,如果当前生成的是 section,那么就可以在 Tera 模板里使用 section 变量;如果当前生成的是 page,那么就可以在 Tera 模板里使用 page 变量。并且,我们可以在模板中用 {{ __tera_context }} 打印出渲染当前页面时能够使用的全部模板变量,开发调试时使用它非常方便。

zola 为 sectionpage 提供了相当多的字段,以便在 Tera 模板中使用,比如 page.permalinkpage.tocpage.assetssection.subsectionssection.pages 等等。具体见 Templates / Sections and Pages。有些字段是从 Markdown 文件的 front matter 里解析的,有些则是 zola 自己解析/生成/计算而得到的。

其中 section.subsections 是一个字符串数组,元素为子 section_index.md 相对路径,如 post/_index.md。将这个字符串传给 get_section 函数(详后),就可以加载子 section 并访问其更详细的信息了。

zola 在为 sectionpage 生成 HTML 页面时,使用什么规则来决定使用哪个模板文件呢?

如果当前渲染的是 page,则依次:

  • 如果 front matter 里通过 template 指定了,就使用指定的模板
  • 如果 page 所属 section_index.md 文件的 front matter 里通过 page_template 指定了,就使用这个指定的模板。
  • 如果直属 section 没有指定,但直属 section 的父 section 指定了,就使用这个指定的模板。并且递归向上找到最顶层的 section 为止。
  • 如果仍然没有指定,则使用 templates 目录里的 page.html 模板。
  • 如果还是不存在,就会用 zola 内置的默认模板。(如前面截图,页面会展示 "Welcome to Zola!",见 zola 代码 components/utils/src/default_tpl.html。)

可以看到这个规则是有优先级的。优先由 page 在其 front matter 指定,然后依次取 section 及父 section 及更高层级 section 指定的模板,最后取 templates/page.html,直到最后使用 zola 内置的默认模板。

如果当前渲染的是 section,则依次:

  • 如果 section_index.md 文件的 front matter 里通过 template 字段指定了,就使用指定的模板。(首页 section 可以没有 _index.md 文件。)
  • 如果没有指定,就使用 templates/index.html(首页 section)或 templates/section.html(非首页 section)为模板。
  • 如果模板文件仍不存在,就使用 zola 内置的默认模板(带有 "Welcome to Zola!" 字样)

总结一下,templates/index.htmltemplates/section.htmltemplates/page.html 是 zola 的「标准模板」,我们可以自定义这几个模板的内容,来达成渲染效果。同时,我们可以在 front matter 里指定当前 section/page 使用的模板,这样的优先级更高。

zola 添加了一些函数,我们可以在模板里直接使用,并用来开发一些复杂的功能。比如:

  • get_page(path),获取某个 Markdown 文件对应的 page
  • get_section(path),获取某个 _index.md 文件对应的 section
  • get_url(path),获取某个 path 的 permalink。

这几个函数都需要使用 path 参数。在 zola 中,如何根据 path 查找对应的文件,其逻辑见 File searching logic

{% set page = get_page(path="post/page2.md") %}
{% set section = get_section(path="post/_index.md") %}

{% set url = get_url(path="@/blog/_index.md") %}
{% set url = get_url(path="rss.xml") %}
{% set url = get_url(path="sitemap.xml") %}

前面提到,content 目录的结构决定了生成的静态网站的结构,我们也应当按照这个规则来组织内容。

然而,某些时候,这个规则难以满足我们的需求。比如,我们要为博客设置一个 /archives 页面,并在这个页面按时间顺序展示文章列表。然而很明显,博客文章有自己的 section(比如 content/post/_index.md),与 /archives 页面是毫无关系的。

此时,我们可以添加一个 pagecontent/archives.md,并在 front matter 里通过 template 指定使用 templates/archives.html 模板。而在 templates/archives.html 里,我们可以通过 get_section(path="post/_index.md") 来加载文章所属的 section,并且通过 section.pages 来访问所有的博客文章。

另外,我们可以通过 {{ __tera_context }} 查看当前模板可以使用的模板变量有哪些。

feed, sitemap 与 robots.txt

feed 订阅,sitemap 以及 robots.txt 等也是网站常用的页面,zola 提供了内置支持,只需要在 config.toml 里配置即可,不需要自己配置 section/page 和模板来现实。

对于 Feed 我们需要在 config.toml 添加以下配置:

generate_feeds = true
feed_filenames = ["rss.xml"]

对于 feed_filenames zola 内置支持 atom.xmlrss.xml,分别生成 Atom 1.0 和 RSS 2.0 格式的订阅页面。生成的页面 url 分别是 <base_url>/atom.xml<base_url>/rss.xml。如果 feed_filenames 添加别的值,就得自己提供模板来生成页面了。

feed_filenames 默认为 atom.xml,因此 config.toml 里不添加此配置项也是 OK 的。

需要注意的是,只有 page 设置了 date 才会出现在订阅中,可通过 front matter 设置。

而订阅里文章的 author 字段,按照以下规则确定:

  • front matter 里 authors 的第一位
  • 如果没有,则取 config.toml 里的 author
  • 仍然没有,则为 "Unknown"

section 的 front matter 里设置 generate_feeds = true 则可为 section 生成 feed。

zola 会为网站自动生成 sitemap 和 robots.txt,不需要配置。url 分别是 <base_url>/sitemap.xml<base_url>/robots.txt

taxonomies 与 tags, categories

Zola 内置支持 taxonomy(分类法),taxonomy 是用户根据用户定义的类别对内容进行分组的一种方式。通过 taxonomy 可以实现博客常见的 tags 和 categories。

taxonomy 理解起来似乎有点绕,但 Content / Taxonomies 文档里电影网站的示例非常形象,值得一看。

taxonomy 的定义包括 3 部分:

  • taxonomy,即分类法的名字。如果我们要将电影按照 director,genres,awards,release year 进行归类,那么 director,genres,awards,release year 各自就是一个 taxonomy。如果我们要将博客文章按照 tags,categories 归类,tags 和 categories 就是 taxonomy。
  • term,术语,分类法中的特定组。比如 #Rust#web 就是 tags taxonomy 的 term。
  • value,值,与 term 关联的内容。对于 zola 网站来说,就是 HTML 页面,或者说是 section / page 了。

似乎可以这么理解:

  • taxonomy 包含的「类别」称为 term,一个 taxonomy 可以有多个 term。
  • term 可以与多个 value 关联。
  • value,对于博客来说,value 就是文章了。

拿 tags 这个 taxonomy 来说,它包含的 term 可以有很多,比如 #Rust#web 等等。博客文章就是 value,当我们给文章添加 tag,实际上是“给文章添加 tag term”,建立与 term 之间的关联。

如果要启用一个 taxonomy,需要先在 config.toml 里定义它。

taxonomies = [
    { name = "tags" },
    { name = "categories" }
]

配置 taxonomies 时可以使用的字段见 Content / Taxonomies - Configuration

为了将 taxonomy term 与 HTML 页面关联起来,我们需要在 section / page 的 front matter 里添加元数据。

+++
[taxonomies]
tags = ["Rust", "axum", "backend"],
categories = ["tech"]
+++

zola 处理 taxonomy 的逻辑如下:

zola 为每个 taxonomy 生成自己的页面,url 是 <base_url>/<taxonomy_name>。使用的模板是 templates/<taxonomy_name>/list.html,如果不存在则使用 templates/taxonomy_list.html,如果仍不存在则报错。渲染此页面时,模板中可以使用 taxonomyterms 这两个模板变量。terms 是数组,元素的类型是 TaxonomyTerm,可通过它的 page_count 字段访问与 term 关联的页面数量,通过 pages 字段访问所有的关联页面。

zola 为 taxonomy 的每个 term 建立一个页面,url 是 <base_url/<taxonomy_name>/<term>。使用的模板是 templates/<taxonomy_name>/single.html,如果不存在则使用 templates/taxonomy_single.html,如果仍不存在则报错。渲染此页面时,模板中可以使用 taxonomyterm 两个模板变量。term 的类型是 TaxonomyTerm,可通过它的 page_count 字段访问与 term 关联的页面数量,通过 pages 字段访问所有的关联页面。

简单地说,zola 为每个 taxonomy 生成两类 HTML 页面。第一类是用于展示 taxonomy 的信息。第二类用于展示 term 的信息,每个 term 都有一个 HTML 页面。

从以上介绍我们也有个隐约的感受,zola 的 taxonomy 是扁平的,而非层级结构。用 taxonomy 来实现博客的 tags 非常方便,依照前面的示例就可以了。如果想要的 categories 也是扁平结构,做法也是差不多的。但如果要实现层级结构的 categories,就比较麻烦了,需要自己写代码进行处理,这里就不提了。

静态文件 static

不论是博客还是其他网站,都需要静态资源,比如 CSS,Javascript 还有图片等等。

zola build 时 static 目录下的文件和子目录,会被原样 copy 到 public 目录。static/style.css 将会变成 public/style.css;而 static/images/icon.png 将会变成 public/images/icon.png。以此类推。

sectionpage 的 Markdown 文件里,我们可以引用静态文件。比如,引用一张图片:

![CSS](/style.css)

![dog](/images/dog.jpg)

注意,这里引用是绝对路径(以 / 开头)。

对于博客或者其他小型网站,用这种方式,就足够了。为了支持大型网站,zola 提供了 asset colocation 机制。

colocate 顾名思义就是「放到一起」。把 section / page 的 Markdown 文件和所需的静态资源放到一个目录里面。

比如,以下是 content 里面的一个 section 及所属 page

└── research
    ├── latest-experiment
       ├── index.md
       └── javascript.js
    ├── _index.md
    └── research.jpg

这里 research 是一个 section(因为它里面有 _index.md)。而 latest-experiment 是一个 page

按照之前提到的规则,这个 page 必须是 research 目录下的 latest-experiment.md 文件。但这里将 latest-experiment 变成目录并在里面添加 index.md 文件,这样的话它仍然是一个 page

在此,我们需要修订一下 sectionpage 的定义:

  • content 及其子目录下的 Markdown 文件(非 _index.mdindex.md),是 page
  • content 的子目录,如果里面有 index.md 文件,仍然是 page
  • content 的子目录,如果里面有 _index.md 文件,则是 section
  • content 的其他子目录,不是 section

现在 section / page 都可以是目录了。我们可以把静态资源放到代表它们自己的的目录里面,这就是 asset colocation。在 research section_index.md 里,可以用 ![research image](research.jpg) 的方式引用这张图片。在 latest-experiment pageindex.md 里,可以用 [Javascript code](javascript.js) 来引用这个 Javascript 文件。引用 co-locate 的静态资源时,需要使用相对路径。

static 目录里,如果我们建立与 asset colocation 时同样的目录结构,也可以达到与 colocation 类似的结果。在 section/page 里,可以用与 asset colocation 相同的方式来引用静态资源(使用相对路径)。这是因为,此种方式,build 出来的结果,与 asset colocation 时是相同的。

这里简单说一下 zola build。对于 static 目录,zola build 时会将里面的内容原样 copy 到 public 目录。对于 content 目录,zola build 时,将会生成形如 section-name/section-name/../section_name/page-name/index.html 的 HTML 文件。虽然 HTML 文件名都是 index.html,但其路径里包含了所有父 section 的名字和 page 的名字。

这样,我们也就明白了,把静态资源放到 section 里与 section/page 目录层级相同的子目录里面,也可以用相对路径引用的原因了。因为 zola build 之后,与 asset colocation 的结果是相同的。

总结,处理静态资源的 3 种方式:

  • 放到 static 目录,用绝对路径引用。
  • colocation,放到 section/page 自己的目录,用相对路径引用。
  • 放到 static 里与 section/page 目录层级相同的子目录里面,用相对路径引用。

仔细的我们可能还会发现,zola build 生成的 HTML 文件的名字都是 index.html,而 zola 渲染页面时,给这些 HTML 文件生成的 url 是形如 <base_url>/.../xxx/ 的。url 以 / 结尾,但没带上 index.html。这时因为,一般的 HTTP 服务器,在进行 url 到文件的映射时,会尝试将 <base_url>/.../xxx/ 映射为 .../xxx/index.html。人们也普通更喜欢 zola 生成的 url 形式。而且,我们在访问的时候,结尾的 / 去掉也没关系的。

主题 themes

主题可以帮助我们快速定制化博客/网站,它提供了完整的 HTML 页面布局,CSS 样式等方案。如果我们没有进行页面设计的能力,不了解 CSS,HTML,Javascript,或者不愿投入过多的精力,那么使用现成的主题就是比较好的选择了。

zola 的主题就是一个完整的 zola 项目,包括前面提到的 contenttemplatesstatic 等目录及相应的文件。

使用一个主题时,需要把这个主题的 zola 项目放到 themes 目录,然后在配置文件里启动它。zola 的主题一般是个 git 仓库。使用时首先通过 git submodue 将主题添加到 themes 目录里。

# 在项目根目录执行
git submodule add https://github.com/not-matthias/apollo themes/apollo

然后在 config.toml 中指定使用这个主题:

theme = "apollo"

剩下的配置细节,则要根据这个主题提供的文档来进行了。主题常常也会提供配置参数,一般放在 config.tomlextra section 里。

微调主题

zola 的主题,就像一个项目模板。zola 将主题提供的内容,和我们自己提供的内容合并到一起,构建出最终的网站。

我们可以在项目 templatesstatic 目录里提供同名文件,覆盖主题的 templatesstatic 目录里的文件,这样就达到了微调效果。比如:

# 假定我们使用的主题是 apollo
templates/pages/post.html -> 取代 themes/apollo/templates/pages/post.html
templates/macros.html -> 取代 themes/apollo/templates/macros.html
static/js/site.js -> 取代 themes/apollo/static/js/site.js

结合 Tera 的模板继承功能,我们可以仅对模板的某个 block 进行微调。比如我们可以提供 templates/pages/post.html,对主题的 templates/pages/post.html 的某个 block 进行微调,但其他不变:

{% extends "apollo/templates/pages/post.html" %}

{% block some_block %}
Some custom data
{% endblock %}

这里关键点是,项目 templates 里的模板,可以继承主题里的某个模板。

创建自己的主题

前面已经提到,zola 的主题就是一个完整的 zola 项目。我们可以用 zola init 命令创建主题项目,然后再在项目根目录中添加一个 theme.toml 配置文件,就可以了。

theme.toml 就是 zola 主题和普通 zola 项目的唯一区别了。theme.toml 支持的配置项见Themes / Creating a theme

正因为 zola 主题是一个完整的 zola 项目,所以开发主题的时候,我们可以随时通过 zola serve 查看效果,debug 等。

开发完成后,我们可以将主题提交到 zola 的主题项目。这样的话,zola 官网的主题页面就会展示我们的主题了。

小结

至此,我们对于 zola 已经有了全面的了解了。 我们知道,SSG 的功能用一句话描述,就是 内容(Markdown)+ 模板 ==> HTML 文件

在内容方面,zola 要求将 Markdown 文件保存在 content 目录,并为每个 Markdown 文件生成一个 HTML 文件。zola 的理念是,content 目录的结构,与生成的网站的结构相一致。进一步,zola 将内容分为 sectionpage。zola 生成的 HTML 页面,要么是一个 section,要么是一个 page

模板文件保存在 templates 目录,我们可以在 Markdown 文件的 front matter 里面指定使用的模板文件,否则就要根据一套规则来确定所使用的模板文件了。渲染不同模板时,我们可以使用不同的模板变量,而且可以通过 {{ __tera_context }} 打印出渲染当前页面时能够使用的全部模板变量,开发调试时使用它非常方便。

zola 内置支持 feed,sitemap 与 robots.txt,简单配置即可。zola 还提供了 taxonomies 机制,可以用来实现 tags 与 categories。

任何网站都离不开静态资源,比如 CSS,Javascript 及图片文件等等。zola 要求我们将静态资源保存到 static 目录,或者通过 asset colocation 的方式将之保存到与 section / page 的 Markdown 文件相同的目录里。我们需要注意,引用静态资源时,有时候需要用绝对路径,有时可以用相对路径。

zola 还支持主题,主题也是正常的 zola 项目,只是多了一个 theme.toml 文件。zola 的主题,就像一个项目模板。zola 将主题提供的内容(模板,静态资源等),和我们自己提供的内容(Markdown 文件,模板,静态资源)合并到一起,构建出最终的网站。除了参照主题的文档进行配置,我们也可以在项目 templatesstatic 目录里提供同名文件,覆盖主题的 templatesstatic 目录里的文件,对主题进行微调。

有了这些知识,我们就可以完全按照自己的喜好来搭建博客了。如果想要动手写一个 SSG,这些知识也能派上用场。对于博客,最简单还是找一个喜欢的主题,然后开始配置。如果你有想法,也可以设计自己的博客主题。

部署

zola 生成的是静态网站,即 public 目录的那些 HTML(还有 CSS,Javascript,图片等等) 文件,部署是很容易的。我们既可以选择部署到自己的服务器,也可以部署到 GitHub Pages 等平台。zola 的 Deploy 文档页面列出了在各个平台部署的详细步骤,参阅即可。

我的选择是,尽量使用 Cloudflare。流量通过 Cloudflare 接入,博客部署到 Cloudflare Pages,并且使用 Cloudflare Workers 作为反向代理,进行流量路由。这是考虑到以下需求:

  • 博客需要使用的域名 yfaming.com 是一个 apex 域名。
  • yfaming.com 域名下,以后需要添加新的路径来提供其他服务。

比如,我准备近期就在这个域名下搭建一个基于 NWC 的 lightning address 服务,这样就可以将 yfaming@yfaming.com 作为我的 lightning address 了。这个服务需要使用 https://yfaming.com/.well-know/lnurlp/ 路径。这意味着,我需要至少保留按照 URL 前缀进行路由的能力。

使用 Cloudflare 接入,还可以免费享受 CDN 加速,HTTPS 证书,和一些安全防护措施(比如防 DDoS,隐藏服务器 IP)。

考虑到很快需要在自己的服务上搭建 lightning address 服务,那么直接把博客部署在自己的服务也是 OK 的。但是既然 Cloudflare Pages 满足需求,而且能够方便地做到当博客 GitHub 仓库更新后,立即触发部署,使用 Cloudflare Pages 还是有优势的。

对于开发者来说,GitHub 是我们最熟悉的平台,为什么不部署到 GitHub Pages 呢?因为我博客使用的域名 yfaming.com 是 apex 域名。apex 域名不能添加 CNAME 记录。部署到 GitHub Pages 的话,只能给这个域名添加 A 记录,其 IP 为 GitHub Pages 的 IP。但这样的话,GitHub Pages 就成为这个域名的流量入口了,无法根据 URL 进行路由。(只有使用子域名,才可以通过 CNAME 的方式绑定域名。)

Table of Contents