核心结论
Hugo 的 content 目录不是简单的 Markdown 文件仓库,而是一棵 Page Tree。目录层级和特殊文件名会决定页面的 Kind、Section、Type、Bundle 类型、模板选择、资源归属以及默认 URL。
设计 content 结构时,不应只考虑最终 URL,而要先判断当前节点是一个具体内容页,还是一个可以包含其他页面的集合。
| |
Hugo 的五种 Page Kind
Kind 表示页面在站点内容模型中的结构角色。它主要由文件位置和 Hugo 内置机制决定,不是一个通常应在 Front Matter 中随意指定的展示参数。
| Kind | 典型内容路径 | 含义 | 常用模板类型 |
|---|---|---|---|
home | content/_index.md | 站点根页面 | home.html |
page | content/about.md 或 content/about/index.md | 普通内容页 | single.html、page.html |
section | content/post/ 或 content/post/_index.md | 内容集合 | section.html、list.html |
taxonomy | content/tags/_index.md | 某个 Taxonomy 的 Term 总览 | taxonomy.html、list.html |
term | content/tags/hugo/_index.md | 某个具体 Term 的内容列表 | term.html、list.html |
在模板中可以通过以下字段检查当前页面:
| |
普通文件、index.md 与 _index.md
这三种形式最容易混淆。
普通 Markdown 文件
| |
它表示一个普通页面,适合没有同目录资源的简单内容。
| |
index.md:Leaf Bundle
| |
index.md 表示当前目录是一篇具体页面,同时让同目录图片、视频和附件成为该页面的 Page Resources。
| |
模板可以通过 .Resources 获取这些资源:
| |
Leaf Bundle 是内容树的叶子,不应该再包含需要独立渲染的子页面。目录中的其他 Markdown 文件通常会成为 Page Resource,而不是独立 Page。
_index.md:Branch Bundle
| |
_index.md 表示当前目录是一个集合节点,可以拥有子页面和子 Section。
| |
可以把两者记成:
| |
Section 如何形成
content 下的顶层内容目录天然是 Root Section。例如:
| |
这些文章的 Root Section 都是 post:
| |
Go、Linux 和 C++ 等中间目录如果没有 _index.md,通常只是文件组织和 URL 路径的一部分,不是具有独立列表页的 Section。如果需要让某个目录成为嵌套 Section,应为它创建 _index.md。
| |
此时 /post/go/ 才具有明确的 Section Page,并能拥有自己的标题、描述、菜单和子页面集合。
Kind、Type、Section 与 Layout 的区别
这几个概念属于不同维度。
| 字段 | 回答的问题 | 主要来源 |
|---|---|---|
Kind | 这是普通页、Section 还是 Taxonomy? | 内容树结构和 Hugo 内置机制 |
Section | 这个页面属于哪个 Root Section? | content 下的顶层 Section |
Type | 应优先使用哪一组类型模板? | Front Matter 的 type,否则通常来自 Root Section |
Layout | 在当前 Type 中选择哪个具体模板? | Front Matter 的 layout |
Slug | URL 最后一个路径段是什么? | Front Matter 或文件名 |
例如 Stack 的搜索页:
| |
Front Matter:
| |
对应的核心属性为:
| |
于是可以匹配 Stack 的:
| |
如果移动页面后 Type 不再符合主题约定,可以在 Front Matter 中显式设置:
| |
layout 不是全局模板开关,它会与 Kind、Type、语言和 Output Format 一起参与 Template Lookup。
每类 Markdown 文件的 Kind、Type 和 Section
Kind、Type 和 Section 不是三个需要在每篇 Front Matter 中同时手工填写的字段。Hugo 会先根据内容路径和内置页面类型确定 Kind 与 Section,再根据 Front Matter 或 Root Section 推导 Type。
可以按照下面的顺序理解:
- Hugo 先判断当前文件是否对应首页、普通页面、Section、Taxonomy 根页或 Term 页,由此得到
Kind。 - Hugo 根据页面所在的顶层内容目录确定
Section;首页没有 Section,Taxonomy 页面使用 Taxonomy 的 plural 名称。 - 如果 Front Matter 显式设置了
type,Hugo将其作为Type;否则普通内容通常继承 Root Section 名称,根目录普通页和首页默认使用page,Taxonomy 与 Term 默认使用 Taxonomy 的 plural 名称。 layout、slug、url、outputs和menu不负责设置这三个字段,它们分别影响模板选择、发布地址、输出格式和菜单。
当前站点主要文件的实际值如下:
| Markdown 文件 | 页面角色 | Kind | Type | Section |
|---|---|---|---|---|
content/_index.md | 站点首页 | home | page | 空字符串 |
content/page/about/index.md | 关于我普通页面 | page | page | page |
content/page/archives/index.md | 归档普通页面 | page | page | page |
content/page/search/index.md | 搜索普通页面 | page | page | page |
content/tags/_index.md | Tag Taxonomy 根页 | taxonomy | tags | tags |
content/categories/_index.md | Category Taxonomy 根页 | taxonomy | categories | categories |
content/post/Misc/Hugo-content目录的页面类型与组织方式.md | 普通博客文章 | page | post | post |
content/post/Hugo/example/index.md | Leaf Bundle 形式的博客文章 | page | post | post |
content/post/_index.md | post Root Section 元信息 | section | post | post |
content/post/Go/_index.md | post 下的嵌套 Section 元信息 | section | post | post |
content/tags/hugo/_index.md | hugo Tag Term 元信息 | term | tags | tags |
content/categories/misc/_index.md | misc Category Term 元信息 | term | categories | categories |
表中 content/post/Hugo/example/index.md、content/post/_index.md、content/post/Go/_index.md、content/tags/hugo/_index.md 和 content/categories/misc/_index.md 用于说明对应结构,当前站点不一定已经创建这些具体示例文件。
首页
content 根目录的 _index.md 是 Hugo 站点首页的元信息文件,因此其属性为:
| |
这里的 _index.md 不会得到 Kind=section,因为 content 根节点在 Hugo 页面模型中具有特殊的首页身份。它当前设置的 menu 只负责把首页加入主菜单,不影响 Kind、Type 或 Section。
关于我
“关于我”使用 content/page/about/index.md,因此 content/page/about/ 是一个 Leaf Bundle,当前节点是一篇具体页面:
| |
Kind=page 来自 index.md 的 Leaf Page 语义,Section=page 来自顶层目录 content/page。由于 Front Matter 没有显式设置 type,Type 默认继承 Root Section,因此也是 page。
当前 Front Matter 中的字段分别负责:
| |
这些字段都不会改变 Kind=page、Type=page、Section=page。
归档
归档页同样是位于 page Root Section 下的 Leaf Page,content/page/archives/index.md:
| |
layout: archives 使它选择 Stack 的归档布局,归档数据由该布局读取并组织。页面仍然是普通 page,并不存在 archives 这种 Hugo 内置 Kind。
slug: archives 只参与 /archives/ URL 的生成,不会把 Type 或 Section 设置为 archives。
搜索
搜索页 content/page/search/index.md 也是 Leaf Page:
| |
它的特殊之处不在 Kind,而在自定义布局和 Output Format:
| |
layout: search 使 HTML 输出匹配 Stack 的 layouts/page/search.html,JSON 输出匹配对应的搜索数据模板。outputs 只决定同一个 Page 需要生成哪些输出格式,不会创建新的 Kind,也不会改变 Type 或 Section。
Tag Taxonomy 根页
Hugo 默认配置中存在 tag = "tags",因此逻辑路径 /tags 被识别为 Tag Taxonomy 根节点 content/tags/_index.md:
| |
这里的 Kind=taxonomy 不是单独由 _index.md 决定的,而是由“启用了 plural 名称为 tags 的 Taxonomy”与“文件位于 content/tags/_index.md”共同决定。_index.md 为 Hugo 自动生成的 Taxonomy Page 补充标题、菜单等元信息。
如果没有这个文件,只要 Tag Taxonomy 已启用并且文章使用了 tags,Hugo 仍然可以生成 /tags/。缺少的只是自定义标题、菜单和其他页面元信息。
Category Taxonomy 根页
Hugo 默认配置中存在 category = "categories",因此该文件对应 Category Taxonomy 根页面 content/categories/_index.md:
| |
Type 和 Section 使用 plural 名称 categories,而不是 singular 名称 category。文章 Front Matter 也使用 plural 字段建立关系:
| |
当前文件中的 layout: categories、slug: categories 和 menu 都不会创建 Taxonomy;真正让页面获得 Kind=taxonomy 的是 Hugo Taxonomy 配置和逻辑路径。
普通博客文章
普通博客文章自身位于:
| |
它是普通 Markdown 内容文件,并且位于 post Root Section 下,因此:
| |
中间目录 Misc 不会把 Type 或 Section 改为 Misc。它当前只是文件组织和 URL 逻辑路径的一部分;文章的 categories: [Misc] 是独立的 Taxonomy 关系,也不会改变 Section。
普通文件与 Leaf Bundle 在这三个值上可以完全相同:
| |
二者通常都是:
| |
区别在于后者是 Leaf Bundle,可以把同目录图片和附件作为 Page Resources 管理,而不是 Kind、Type 或 Section 不同。
post 目录下的 _index.md
如果创建:
| |
它会成为 post Root Section 的元信息页面:
| |
如果进一步创建:
| |
Go 会成为嵌套 Section,但 .Section 仍然返回 Root Section post:
| |
需要获得当前嵌套 Section Page 时应使用 .CurrentSection,不能只依赖 .Section 区分 post/Go 与 post/Linux。
Tag Term 页
如果为具体 Tag 创建元信息文件:
| |
它对应 /tags/hugo/,属性为:
| |
Kind=term 表示它是一个具体 Term 的文章集合,.Pages 通常是所有声明了 tags: [Hugo] 的文章。Type 与 Section 仍然使用 Taxonomy plural 名称 tags,而不是 Term 名称 hugo。
Category Term 采用相同规则:
| |
Front Matter 能修改哪些值
三个字段的可控方式并不相同:
| 字段 | 能否通过 Front Matter 修改 | 正确控制方式 |
|---|---|---|
Kind | 不能直接修改 | 调整文件形态、内容路径,或者启用相应 Hugo 内置机制 |
Type | 可以 | 设置 type;未设置时由 Root Section 等信息推导 |
Section | 不能直接修改 | 调整文件所在的 Root Section 或使用 Hugo Module mount |
例如在“关于我”页面设置:
| |
结果会变成:
| |
只有 Type 被覆盖,页面仍然是普通 Page,也仍然位于 page Section。
类似地,下面这些字段都不能改变 Page Kind:
| |
它们可以改变模板候选或发布 URL,但不能把 content/page/tags/index.md 转换为 Taxonomy Page。
把 Taxonomy 文件移动到 page 后会发生什么
正确位置:
| |
如果移动并改成:
| |
如果移动后仍使用 _index.md:
| |
即使通过 slug、url 或 permalink 让它们最终发布到 /tags/,它们也不会恢复 Kind=taxonomy。与此同时,Hugo 仍会为真正的 Tag Taxonomy 生成 /tags/,从而产生重复输出路径。因此 Taxonomy 元信息文件应保持在 content/tags/_index.md 和 content/categories/_index.md。
在模板中验证实际值
判断页面类型时应直接查看 Hugo Page 对象,而不是根据浏览器地址猜测。可以临时在模板中输出:
| |
其中 printf "%q" 可以明确显示首页的空 Section。当前站点还可以使用 hugo list all 查看内容文件的 kind、section 和最终 permalink,再结合 .Type 的推导规则定位模板匹配问题。
搜索页为什么必须使用 index.md
正确结构是:
| |
如果误写成:
| |
搜索节点会从普通页面变成 Section:
| |
Stack 的搜索功能依赖 layouts/page/search.html 和 layouts/page/search.json,还需要加载 search.tsx。一旦页面变成 Section,模板查找方向就会改变,最终可能只得到空白列表页或无法生成正确的 JSON 搜索索引。
Taxonomy 页面为什么使用 _index.md
Hugo 默认提供:
| |
文章通过 plural 字段关联 Term:
| |
Hugo 会自动生成:
| |
对应的可选元信息文件是:
| |
这些节点都是集合,所以使用 _index.md。文件负责补充标题、描述、封面和菜单,真正的文章归属关系来自每篇文章的 Front Matter。
如果把 content/tags/_index.md 写成 content/tags/index.md,就会把 /tags/ 声明成普通叶子 Page,与 Hugo 自动生成的 Taxonomy 根页发生内容模型冲突。
内容路径与最终 URL 可以分离
内容文件所在目录不会绝对决定最终 URL。Hugo 还会考虑 slug、url 和站点的 permalinks 配置。
当前站点使用:
| |
因此:
| |
这说明目录主要用于表达内容模型和模板类型,而 permalink 可以独立控制发布路径。不要为了缩短 URL 随意移动内容目录,应优先使用 permalink 或 url 配置。
Stack 主题下的推荐结构
| |
这里的职责划分是:
| 路径 | 职责 |
|---|---|
content/post | 首页、归档和 RSS 中的主要文章 |
content/page | 搜索、关于、归档等独立功能页 |
content/categories | Category Taxonomy 元信息 |
content/tags | Tag Taxonomy 和 Term 元信息 |
Stack 通过 mainSections = ["post"] 识别主要文章,因此普通功能页不会混入首页文章列表和搜索索引。
组织页面时的判断顺序
新增页面前可以依次判断:
- 这是具体内容还是集合?具体内容使用
foo.md或index.md,集合使用_index.md。 - 是否需要与页面绑定图片和附件?需要时使用 Leaf Bundle,即
目录/index.md。 - 是否需要子页面和独立列表页?需要时使用 Branch Bundle,即
目录/_index.md。 - 它属于哪个业务类型?博客文章放在
post,功能页放在page,不要只根据 URL 选择目录。 - 是否属于 Taxonomy?标签和分类关系写入文章 Front Matter,
content/tags与content/categories只保存集合元信息。 - 主题模板位于哪个 Type 目录?例如
layouts/page/search.html要求搜索页以Type=page参与查找。 - 最终 URL 是否符合需求?不符合时优先调整 permalink、
slug或url,不要先破坏内容模型。
常见错误
| 错误 | 后果 | 正确方式 |
|---|---|---|
用 _index.md 创建普通功能页 | 页面变成 Section,使用 list 语义 | 改为 index.md |
用 index.md 创建标签根页 | 普通 Page 与 Taxonomy 根页冲突 | 改为 tags/_index.md |
| 为了修改 URL 随意移动顶层目录 | Type 和模板匹配可能改变 | 配置 permalink 或显式 type |
认为 layout 可以脱离 Type 独立工作 | 找不到主题中的类型模板 | 同时检查 Kind、Type 和 Layout |
| 在 Leaf Bundle 中放独立 Markdown 子页面 | 子 Markdown 可能变成 Page Resource | 改用 Branch Bundle |
| 手工维护标签文章列表 | 与 Hugo 自动 Taxonomy 重复 | 在文章 Front Matter 中声明 tags |