Zola 分类法(Taxonomies)模板开发指南:从配置、术语渲染到分页与 Feed 的完整实战
【免费下载链接】zolaA fast static site generator in a single binary with everything built-in. https://www.getzola.org项目地址: https://gitcode.com/GitHub_Trending/zo/zola
Zola 内置的 Taxonomies(分类法)机制允许你按自定义维度对内容分组,并在构建期为每个分类与术语生成独立的列表页、术语页和订阅 Feed。本文将基于 Zola 官方模板文档,结合仓库源码与测试站点,完整讲解分类法模板的文件查找规则、TaxonomyConfig与TaxonomyTerm的数据结构、list.html与single.html的变量体系,以及分页(paginator)与 Feed 的集成方式,让你能直接写出可运行、可复用的分类模板。
分类法模板的作用与适用前提
在 Zola 中,分类法(Taxonomy)是用户自定义的分类维度,术语(Term)是某个维度下的具体分组,而值(Value)则是被关联到术语的内容条目。构建时,Zola 会为每个分类生成两类页面:
- 分类列表页:列出该分类下的所有术语,例如
/tags/; - 术语页:列出归属于某个术语的所有页面,例如
/tags/rust/。
模板文档明确指出:只有在至少一个分类法设置了render = true时,分类法模板才是必需的。也就是说,如果某个分类只用于聚合内容而不需要生成页面(例如仅用于 Feed 或内部查询),可以关闭渲染;反之,只要有一个分类需要页面输出,就必须提供对应的模板(或使用内置回退模板)。
模板文件的查找规则:专用目录与通用回退
Zola 会先在templates目录下按分类法名称查找专用模板:
$TAXONOMY_NAME/single.html(分类法名称为子目录)$TAXONOMY_NAME/list.html
如果找不到,则回退到通用模板:
taxonomy_single.htmltaxonomy_list.html
这一点在仓库的渲染实现中有直接印证:components/render/src/renderer.rs中,渲染术语页时使用cached.single_template.as_deref().unwrap_or("taxonomy_single.html"),渲染列表页时使用cached.list_template.as_deref().unwrap_or("taxonomy_list.html"),即优先采用按分类法缓存的模板名,缺失时回退到通用模板名。
在官方文档站点docs/中就能看到实际用法:docs/config.toml定义了taxonomies = [{ name = "theme-tags" }],对应的专用模板放在docs/templates/theme-tags/目录下(single.html与list.html),按分类法名组织模板目录。
注意:模板文件必须放在站点根目录的
templates/下;主题(theme)的模板也遵循同样的查找顺序。
模板可用的数据类型
在编写模板之前,需要先理解 Zola 暴露给模板的两个核心类型:TaxonomyTerm与TaxonomyConfig。
TaxonomyTerm:单个术语对象
name: String; // 术语名称(原始写法,如 "Guillermo Del Toro") slug: String; // 术语的 slug(用于 URL,如 "guillermo-del-toro") path: String; // 术语页的路径 permalink: String; // 术语页的完整永久链接 pages: Array<Page>; // 归属于该术语的页面数组 page_count: Number; // 归属于该术语的页面数量从源码components/content/src/taxonomies.rs可以确认,TaxonomyTerm内部字段即为name、slug、path、permalink与pages,序列化后额外暴露page_count(等于item.pages.len())。术语页面的排序由sort_pages(taxo_pages, SortBy::Date)按日期完成,无日期的页面会追加到末尾——这符合分类法"几乎总是用于博客"的定位。
TaxonomyConfig:分类法的配置对象
name: String; // 分类法名称(通常用复数,如 tags) paginate_by: Number?; // 若为正数,每个术语页按此数量分页 paginate_path: String?;// 分页路径,默认 "page",页码追加其后 feed: Bool; // 是否为每个术语生成 Feed(默认 false) render: Bool; // 是否渲染分类与术语页面(默认 true)源码components/config/src/config/taxonomies.rs中TaxonomyConfig的默认值为:paginate_by: None、render: true、feed: false;paginate_path未设置时默认取"page"(即默认分页链接形如/tags/rust/page/1)。同时is_paginated()要求paginate_by必须是大于 0 的数才真正启用分页。
分类列表模板(list.html)
list.html渲染分类法下的所有术语,该模板永远不会被分页,因此在所有情况下都获得以下变量:
config: Config; // 站点配置 taxonomy: TaxonomyConfig; // 该分类法的配置数据 current_url: String; // 当前页面的完整永久链接 current_path: String; // 当前页面的路径 terms: Array<TaxonomyTerm>;// 该分类法下的所有术语 lang: String; // 当前页面语言一个典型的列表模板会遍历terms,输出每个术语的名称、数量与链接:
<ul> {% for term in terms %} <li> <a href="{{ term.permalink | safe }}">{{ term.name }}</a> ({{ term.page_count }}) </li> {% endfor %} </ul>仓库测试站点test_site/templates/tags/list.html提供了一个更精简的真实示例:
{% for tag in terms %} {{ tag.name }} {{ tag.slug }} {{ tag.pages | length }} {% endfor %}可以看到terms中每个元素都具备name、slug、pages等字段,与文档声明的TaxonomyTerm结构一致。
单个术语模板(single.html)
single.html渲染某个具体术语下的所有页面,获得以下变量:
config: Config; // 站点配置 taxonomy: TaxonomyConfig; // 该分类法的配置数据 current_url: String; // 当前页面的完整永久链接 current_path: String; // 当前页面的路径 term: TaxonomyTerm; // 当前正在渲染的术语 lang: String; // 当前页面语言如果该术语启用了分页(paginate_by为正数),模板还会额外获得一个paginator变量,其结构与分区(section)分页完全一致,详见 分页模板文档。
测试站点test_site/templates/tags/single.html展示了同时兼容分页与非分页的写法:
{% if not paginator %} Tag: {{ term.name }} {% for page in term.pages %} <article> <h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3> </article> {% endfor %} {% else %} Tag: {{ term.name }} {% for page in paginator.pages %} {{ page.title | safe }} {% endfor %} Num pagers: {{ paginator.number_pagers }} Page size: {{ paginator.paginate_by }} Current index: {{ paginator.current_index }} {% if paginator.previous %}has_prev{% endif %} {% if paginator.next %}has_next{% endif %} {% endif %}关键点在于:未分页时页面在term.pages中,分页后页面在paginator.pages中,模板必须用{% if not paginator %}分流处理。
官方文档站点自己的docs/templates/theme-tags/single.html是术语页的真实生产案例:它遍历term.pages,为每个主题生成卡片链接:
<h1>Zola themes in {{ term.name }}</h1> <div class="themes"> {% for theme in term.pages %} <a class="theme" href="{{ theme.permalink }}"> <img src="{{ theme.permalink }}screenshot.png" alt="Screenshot of {{ theme.title }}"> <span>{{ theme.title }}</span> </a> {% endfor %} </div>这展示了术语页的典型用途:term.name作为页面标题,term.pages作为内容列表。
分页变量(paginator)
分页术语页得到的paginator变量类型为Pager,核心字段如下:
paginate_by: Number; // 每页条目数 base_url: String; // 分页基础 URL,可拼接整数得到任意页码链接 number_pagers: Number; // 分页总数 first: String; // 第一页链接 last: String; // 最后一页链接 previous: String?; // 上一页链接(若有) next: String?; // 下一页链接(若有) pages: Array<Page>; // 当前页的所有页面 current_index: Number; // 当前页码(从 1 开始) total_pages: Number; // 全部分页中的页面总数文档明确提醒:当paginate_by未设置为正数时,paginator变量不会被定义,因此模板中必须用{% if paginator %}或{% if not paginator %}进行判空。分页链接的经典写法如下:
<nav class="pagination"> {% if paginator.previous %} <a class="previous" href="{{ paginator.previous }}">‹ Previous</a> {% endif %} {% if paginator.next %} <a class="next" href="{{ paginator.next }}">Next ›</a> {% endif %} </nav>如果需要给每个分页生成链接,可以借助paginator.base_url与paginator.number_pagers拼接:
{% for i in range(end=paginator.number_pagers) %} <a href="{{ paginator.base_url }}{{ i + 1 }}">{{ i + 1 }}</a> {% endfor %}从配置到输出的完整工作流
1. 在配置文件中声明分类法
分类法必须声明在zola.toml的主 section(即[extra]之外)中,例如:
taxonomies = [ { name = "director", feed = true }, { name = "genres", feed = true }, { name = "awards", feed = true }, { name = "release-year", feed = true }, ]多语言站点需要同时在对应语言 section 下重复声明:
taxonomies = [ { name = "director", feed = true }, { name = "genres", feed = true }, ] [languages.fr] taxonomies = [ { name = "director", feed = true }, { name = "genres", feed = true }, ]这里feed = true表示每个术语都会生成 Atom Feed(默认格式)。
2. 在页面 front matter 中标记术语
配置完成后,在内容页的 front matter 中通过[taxonomies]指定归属:
+++ title = "Shape of water" date = 2019-08-15 [taxonomies] director = ["Guillermo Del Toro"] genres = ["Thriller", "Drama"] awards = ["Golden Globe", "Academy award", "BAFTA"] release-year = ["2017"] +++3. 提供模板并构建
创建templates/tags/list.html与templates/tags/single.html(或通用回退模板taxonomy_list.html/taxonomy_single.html),然后运行zola build即可。仓库渲染器会依次调用render_taxonomy_list与render_taxonomy_term(见components/render/src/renderer.rs),分别注入terms/term、taxonomy、config、lang、current_url、current_path等变量。
输出路径与大小写合并规则
分类法页面的输出路径遵循以下规则:
$BASE_URL/$NAME/ (分类列表页) $BASE_URL/$NAME/$SLUG (术语页)- 分类法名称从不进行 slugify,URL 中直接使用配置里的
name; - 术语会进行 slugify(当配置
slugify.taxonomies = "on"时,这是默认值),见 配置文档。
若设置了taxonomy_root配置项,则所有分类路径都会加上该前缀:
$BASE_URL/$TAXONOMY_ROOT/$NAME/ (分类列表页) $BASE_URL/$TAXONOMY_ROOT/$NAME/$SLUG (术语页)例如taxonomy_root = "blog"、分类tags、术语rust时:
- 分类列表页:
$BASE_URL/blog/tags/ - 术语页:
$BASE_URL/blog/tags/rust/
该行为在源码测试components/content/src/taxonomies.rs中被直接验证:taxonomy_path_with_taxonomy_root断言tax.path == "/blog/tags/"、term.path == "/blog/tags/rust/",而未设置taxonomy_root时路径为/tags/与/tags/rust/。
另一个重要规则是分类法不区分大小写:slug 相同的术语会被合并。测试merges_terms_with_different_case验证了"League of legends"与"League of Legends"两个术语最终合并为唯一一项(slug 为league-of-legends),且两个页面都被保留。此外,如果术语 slugify 后为空字符串(例如术语仅含;这类特殊字符),构建会直接报错(对应测试taxonomy_slug_is_empty_errors),因此术语命名应避免纯特殊字符。
Feed 与 SEO 最佳实践
当分类配置了feed = true时,Zola 会为每个术语生成 Atom Feed(例如/tags/rust/atom.xml),订阅者可以只关注特定分类下的内容更新。渲染器中的render_taxonomy_feed(见components/render/src/renderer.rs)负责这一输出,模板层无需额外处理。
关于 SEO,官方分页文档特别建议:不要将分页页面纳入 sitemap,因为分页页是非 canonical 页面。在zola.toml中设置:
exclude_paginated_pages_in_sitemap = "all"即可将全部分页页面从 sitemap 中排除。
小结
Zola 的分类法模板体系可以总结为:一个配置声明、两组模板文件、两类渲染上下文。
- 配置层:在主 section 声明
taxonomies,每个分类可设置name、paginate_by、paginate_path、feed、render、lang; - 模板层:按分类名组织
$TAXONOMY_NAME/{list,single}.html,或使用通用回退模板taxonomy_{list,single}.html; - 数据层:
list.html拿到全部terms,single.html拿到当前term,启用分页时额外获得paginator。
掌握这些规则后,你既能写出官网主题目录(theme-tags)那样的术语聚合页,也能为博客搭建带分页、带 Feed 的标签系统。更深入的模板变量细节,可继续阅读 模板概览、页面与分区模板 与 分页模板 等相关文档。
【免费下载链接】zolaA fast static site generator in a single binary with everything built-in. https://www.getzola.org项目地址: https://gitcode.com/GitHub_Trending/zo/zola
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考