Hugo核心概念及应用

Hugo 是一个基于 Go 语言的现代的静态站点生成器。Hugo 提供了 强大的模板引擎灵活的主题系统,并支持 ShortcodesHooks 扩展功能。


Hugo Introduction

Hugo v0.146.0 模板系统变更

  • layouts/_default/ 目录已移除,所有文件移至 layouts/ 根目录
  • layouts/partials/layouts/_partials/
  • layouts/shortcodes/layouts/_shortcodes/

安装和初始化

下面将介绍 Windows 下的安装方式,其他平台参照:MacOS, Linux

方式一:在 Git BashPowerShell中使用 winget 命令安装:

bash
winget install Hugo.Hugo.Extended

卸载命令:winget uninstall --name "Hugo (Extended)"


方式二:从Github直接下载, 然后配置环境变量即可:

  1. 下载 hugo 扩展版:github
  2. 解压到指定目录,例如 D:\Develop\env\hugo\hugo_extended_0.147.7_windows-amd64
  3. 配置环境变量:
    bash
    # 设置 HUGO_HOME
    [System.Environment]::SetEnvironmentVariable("HUGO_HOME", "D:\Develop\env\hugo\hugo_extended_0.147.7_windows-amd64", "Machine")
    
    # 安全追加到 PATH
    $currentPath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
    $newPath = if ($currentPath -like "*$env:HUGO_HOME*") { $currentPath } else { "$currentPath;$env:HUGO_HOME" }
    [System.Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")

注意:需要以管理员身份运行 PowerShell 才能修改 Machine 级别的环境变量。除了使用命令,也可以在系统设置中搜索环境变量,然后手动添加环境变量。


为什么推荐 Hugo 的 “Extended” 版本?

  • Extended 版本内置了 LibSass 编译器 (C/C++ 实现的 Sass 编译器)
  • 支持将图片转换为 WebP 格式

Hugo 为什么不需要 Go 的开发环境?

  • Go 是一种编译型语言,Go 编译器通常会进行静态链接,这意味着程序运行所需的所有库(标准库和一些第三方库,如果它们也是纯 Go 实现的话)都会被直接编译进最终的可执行文件中。
  • Go 语言可以将程序编译成一个单一的、原生的、不依赖外部运行时的可执行文件

初始化 Hugo 项目

bash
hugo new site myblog

在生成的目录中,找到 layouts 目录,在其下创建 home.html 文件,用于自定义首页内容。在其中随意写一些HTML,然后启动项目:

bash
hugo server --bind 0.0.0.0 --port 1313

访问 http://localhost:1313 即可看到首页内容


Hugo配置文件

Hugo 的配置文件是站点所有设置的核心。默认情况下,Hugo 会在站点根目录下查找名为 hugo.toml (首选), hugo.yaml, hugo.json, config.toml, config.yaml, 或 config.json 的文件。

Hugo 支持三种主要的配置文件格式:

  • TOML (Tom’s Obvious, Minimal Language): .toml 文件。这是 Hugo 官方文档和很多示例中常用的格式。
    toml
    baseURL = "https://example.com/"
    languageCode = "en-us"
    title = "My New Hugo Site"
    
    [params]
      author = "John Doe"
      description = "A great website."
  • YAML (YAML Ain’t Markup Language): .yaml.yml 文件。使用缩进来表示层级结构。
    yaml
    baseURL: "https://example.com/"
    languageCode: "en-us"
    title: "My New Hugo Site"
    
    params:
      author: "John Doe"
      description: "A great website."
  • JSON (JavaScript Object Notation): .json 文件。虽然也支持,但在 Hugo 配置中不如 TOML 或 YAML 常见。
    json
    {
      "baseURL": "https://example.com/",
      "languageCode": "en-us",
      "title": "My New Hugo Site",
      "params": {
        "author": "John Doe",
        "description": "A great website."
      }
    }

以下是一些最基本和常用的配置项:

  • baseURL: (字符串) 必需项。你网站的根 URL,例如 https://example.com/https://myusername.github.io/myproject/ (如果部署在子目录)。部署到生产环境时必须正确设置此项,否则链接、RSS、站点地图等会出错。

  • languageCode: (字符串) 网站的主要语言代码,例如 en-us, zh-cn

  • title: (字符串) 网站的全局标题。

  • theme: (字符串) 如果你使用一个下载的或创建的 Hugo 主题,在这里指定主题的名称。主题必须放在站点根目录下的 themes/ 文件夹中。例如 theme = "ananke"。 如果自己在项目根目录的 layouts/ 中开发模板,则不需要设置此项或将其注释掉。

  • [params]: (表/对象) 用于存放所有主题特定或用户自定义的参数。这些参数可以在模板中通过 .Site.Params.yourParamName 来访问。这是配置主题外观、功能和自定义信息的常用位置。

  • [menu]: (表数组) 用于定义导航菜单。例如 [[menu.main]] 定义主导航。

  • [taxonomies]: (表) 用于声明网站使用的分类法,例如 category = "categories"tag = "tags"

  • [markup]: (表) 配置 Markdown 渲染器的行为,例如代码高亮、目录生成等。

    toml
    [markup]
      [markup.goldmark.renderer]
        unsafe = true # 允许在 Markdown 中使用原始 HTML (谨慎使用)
      [markup.highlight]
        # style = "monokai"
        noClasses = false # 使用 class 而不是内联 style 进行代码高亮
      [markup.tableOfContents]
        startLevel = 2
        endLevel = 3
        ordered = false
  • [outputs][outputFormats]: (表) 控制 Hugo 为不同类型的页面生成哪些输出格式 (例如 HTML, RSS, JSON, AMP)。

  • [imaging]: (表) 配置 Hugo Pipes 图片处理的默认参数。

  • [services]: (表) 配置与第三方服务的集成,例如 [services.googleAnalytics][services.disqus]

  • [privacy]: (表) 配置与隐私相关的设置,例如 [privacy.youtube] 是否使用 no-cookie 域名。


配置文件的拆分与组织 (Configuration Directory): 对于复杂的站点,Hugo 推荐使用配置目录来拆分和组织配置文件。

  • 在站点根目录下创建 config/ 目录。在 config/ 下创建 _default/ 目录,用于存放基础/默认配置。
  • _default/ 目录中,你可以将配置拆分成多个文件,例如:
    • config.toml (或 hugo.toml): 存放核心配置如 baseURL, title, languageCode
    • params.toml: 专门存放所有 [params] 下的参数。
    • menus.toml: 专门存放所有 [menu] 定义。
    • markup.toml: 存放 [markup] 配置。
    • languages.en.toml, menus.en.toml (如果有多语言): 特定语言的配置。
  • Hugo 会自动加载并合并 config/_default/ 目录下的所有配置文件。
  • 按环境配置: 你还可以在 config/ 目录下为不同环境创建子目录,例如 config/development/config/production/。这些特定环境的配置会覆盖或合并到 _default/ 的配置之上。
    • 例如,在 config/development/config.toml 中可以设置 baseURL = "http://localhost:1313/"
    • config/production/config.toml 中可以设置 baseURL = "https://yourdomain.com/"
    • 通过 hugo server -e developmenthugo -e production (或设置 HUGO_ENV 环境变量) 来指定当前环境。

查看最终配置: 可以使用以下命令来查看 Hugo 在合并所有配置文件和应用主题默认设置后的最终配置:

bash
hugo config

更多配置文件内容参照:Hugo configuration


自定义样式

在 Hugo 中添加和管理自定义样式主要有两种方式:直接使用 CSS 文件,或者使用 SCSS/SASS并通过 Hugo Pipes 进行编译。

1. 直接使用 CSS 文件 (简单场景)

如果你有一个或多个预先编写好的 CSS 文件,不希望进行预处理或复杂的构建步骤。

  • 存放位置:

    • 将你的 CSS 文件(例如 style.css, custom.css)放在站点或主题根目录下的 static/css/ 目录中。
      • 例如:kblog/static/css/theme.css
  • 在模板中引入: 在你的基础模板 (layouts/baseof.html) 或头部局部模板 (layouts/_partials/head-content.html) 的 <head> 标签内,像普通 HTML 一样链接它们:

    html
    {{/* layouts/_partials/head-content.html */}}
    <head>
        {{/* ...其他 meta 和 title 标签... */}}
        <link rel="stylesheet" href="{{ "css/theme.css" | relURL }}">
        {{/* 如果有多个 CSS 文件 */}}
        {{/* <link rel="stylesheet" href="{{ "css/another-style.css" | relURL }}"> */}}
    </head>
    • "css/theme.css": 路径是相对于 static/ 目录的。
    • | relURL: Hugo 模板函数,确保生成的 URL 是相对于站点根目录的正确路径,无论你的 baseURL 是否包含子目录。
  • 优点: 简单直接,易于理解。

  • 缺点:

    • CSS 文件不会被 Hugo Pipes 处理(例如压缩、指纹化等),除非你使用外部工具。
    • 不利于使用 SCSS/SASS 的高级特性(变量、嵌套、混合等)。
    • 多个 CSS 文件会导致多次 HTTP 请求(除非手动合并或使用 HTTP/2)。

2. 使用 SCSS/SASS 与 Hugo Pipes

Hugo Extended 版本内置了 LibSass 编译器,允许你直接在项目中使用 SCSS/SASS 文件,并通过 Hugo Pipes 进行编译、压缩、指纹化等优化。

  • 存放位置:

    • 将你的 SCSS/SASS 主入口文件(例如 main.scss)放在站点或主题根目录下的 assets/scss/ (或 assets/css/ 也可以,只要 resources.Get 的路径正确) 目录中。
      • 例如:kblog/assets/scss/main.scss
    • 你可以将 SCSS 拆分成多个 partials (文件名以下划线 _ 开头,如 _variables.scss, _header.scss),然后在主 main.scss 文件中使用 @import 指令引入它们。
      text
      kblog/
      └── assets/
          └── scss/
              ├── main.scss         <-- 主入口文件
              ├── abstracts/
              │   └── _variables.scss
              │   └── _mixins.scss
              ├── components/
              │   └── _buttons.scss
              │   └── _cards.scss
              └── layout/
                  └── _header.scss
                  └── _footer.scss
      main.scss 文件内容可能像这样:
      scss
      // abstracts
      @import 'abstracts/variables';
      @import 'abstracts/mixins';
      
      // layout
      @import 'layout/header';
      @import 'layout/footer';
      // ...
  • 在模板中引入和处理 (通常在 layouts/_partials/head/css.html):

    go-html-template
    {{/* kblog/layouts/_partials/head/css.html */}}
    {{- $cssResourcePath := "scss/main.scss" -}} {{/* SCSS主入口文件路径 */}}
    {{- $styles := resources.Get $cssResourcePath -}}
    
    {{- if $styles -}}
        {{- $cssOptions := (dict "targetPath" "css/style.css" "enableSourceMap" hugo.IsDevelopment ) -}}
        {{- /* 使用 css.Sass (Hugo v0.128.0+ 推荐) */}}
        {{- $processedStyles := $styles | css.Sass $cssOptions -}}
    
        {{- if not hugo.IsDevelopment -}} {{/* 仅在生产环境压缩和指纹化 */}}
            {{- $processedStyles = $processedStyles | minify | fingerprint "sha256" -}}
        {{- end -}}
        <link rel="stylesheet" href="{{ $processedStyles.RelPermalink }}" {{ if not hugo.IsDevelopment }}integrity="{{ $processedStyles.Data.Integrity }}"{{ end }}>
    {{- else -}}
        {{- warnf "SCSS resource not found at 'assets/%s'." $cssResourcePath -}}
    {{- end -}}

    Hugo Pipes 详解:

    • $cssResourcePath: 定义 SCSS 主入口文件的路径 (相对于 assets/ 目录)。
    • resources.Get $cssResourcePath: 获取 SCSS 资源对象。
    • if $styles: 检查资源是否存在。
    • $cssOptions (字典):
      • targetPath: Hugo Pipes 处理后生成的 CSS 文件在最终 publishDir (通常是 public) 中的路径。例如,所有 SCSS 文件会被编译并合并到 public/css/style.css
      • enableSourceMap: 在开发模式 (hugo.IsDevelopmenttrue) 下是否生成 Source Map,方便调试。
    • css.Sass $cssOptions: 核心函数 (需要 Hugo Extended 版本)。将获取到的 SCSS 资源对象($styles)通过 Sass 编译器处理,应用 $cssOptions。它会处理所有 @import 并编译成一个 CSS 资源对象。
    • minify: 压缩生成的 CSS。
    • fingerprint "sha256": 为文件名添加基于内容的哈希指纹 (例如 style.a1b2c3d4.css),并计算 SRI (Subresource Integrity) 哈希。
    • .RelPermalink: 获取处理后(可能已指纹化)的 CSS 文件的相对 URL。
    • .Data.Integrity: 获取 SRI 哈希。
  • 优点:

    • 可以使用 SCSS/SASS 的所有强大功能。
    • Hugo Pipes 自动处理编译、压缩、指纹化,无需外部构建工具。
    • 更好的性能(通过压缩和指纹化实现有效的浏览器缓存)。
    • 更易于维护和组织大型 CSS 代码库。

选择哪种方式?

  • 对于非常简单的项目或快速原型,直接使用 static/css/ 可能足够。
  • 对于绝大多数现代网站和主题开发,强烈推荐使用 assets/ 目录和 Hugo Pipes (SCSS/SASS) 的方式,因为它提供了更好的开发体验和最终的性能优化。

静态资源管理

static/ 目录用于存放那些不需要 Hugo 进行任何处理、直接原样复制到最终输出目录(publishDir,通常是 public/)根级别的文件

  • 存放位置:

    • 站点根目录下的 static/ 文件夹。
    • 主题根目录下的 static/ 文件夹。
  • 工作原理:

    • 当 Hugo 构建站点时,它会将 static/ 目录中的所有文件和文件夹按原样复制到 publishDir 的根目录。
    • 路径映射:
      • static/images/logo.png -> public/images/logo.png (URL: yoursite.com/images/logo.png)
      • static/js/vendor.js -> public/js/vendor.js (URL: yoursite.com/js/vendor.js)
      • static/favicon.ico -> public/favicon.ico (URL: yoursite.com/favicon.ico)
      • static/robots.txt -> public/robots.txt (URL: yoursite.com/robots.txt) (除非你通过 enableRobotsTXT = true 让 Hugo 生成它)
  • 适用场景:

    • 图片 (Images): 网站 Logo、文章配图(如果这些图片不需要 Hugo Pipes 的图片处理功能,如缩放、裁剪、格式转换)。
    • 字体文件 (Fonts): 自定义字体文件 (.woff, .woff2, .ttf 等)。
    • 第三方 JavaScript/CSS 库: 如果你使用的某些第三方库已经提供了预编译和压缩好的版本,可以直接放在 static/js/static/css/ 下。
    • favicon.ico: 网站图标。
    • robots.txt: 如果你希望手动管理 robots.txt 文件 (而不是让 Hugo 生成它)。
    • ads.txt, humans.txt 等验证或说明文件
    • PDF, MP3, 视频文件等其他媒体文件
  • 在模板中引用 static/ 目录下的资源:

    • 使用相对于站点根目录的路径,并通常配合 relURLabsURL
    go-html-template
    <link rel="icon" href="{{ "favicon.ico" | relURL }}">
    <img src="{{ "images/my-logo.png" | relURL }}" alt="My Logo">
    <script src="{{ "js/some-vendor-library.js" | relURL }}"></script>
  • 主题和项目的 static/ 目录合并:

    • 如果你的项目和使用的主题都有 static/ 目录,并且其中有相同路径的文件,项目根目录 static/ 下的文件会覆盖主题 static/ 目录下的同名文件
    • 如果路径不同,则它们的内容会被合并到最终的 public/ 目录中。

static/ vs assets/ 的关键区别:

特性 static/ 目录 assets/ 目录
处理方式 文件被原样复制public/ 根目录。 文件由 Hugo Pipes 处理(编译、转换、压缩、指纹化等)。
适用内容 不需要处理的静态文件:图片、字体、预编译的JS/CSS库等。 需要处理的源文件:SCSS/SASS, JS模块, 需要优化的图片等。
模板引用 通常通过硬编码路径 + relURL/absURL 通过 resources.Get 获取资源对象,然后处理并获取 .RelPermalink
优化 Hugo 不对其进行优化。 Hugo Pipes 提供强大的优化能力。

Hugo模板与常用变量

Hugo 模板基于 Go 语言的 html/templatetext/template 包。

  1. 什么是模板?

模板是位于项目 layouts/ 目录(或主题的 layouts/ 目录)下的 HTML 文件(或其他格式的文件,如 xml, json)。 它们包含了页面的基本结构和一些特殊的模板标签 {{ }},用于插入动态内容或执行逻辑。

  1. 模板标签 {{ }} (Delimiters):
  • 所有 Hugo/Go 模板的逻辑和变量都包裹在双花括号 {{}} 之间。
  • {{- ... -}} (去除空白): 在标签的任意一侧或两侧添加连字符 - 可以去除该侧由模板标签自身产生的空白字符(包括换行和空格)。
  1. 上下文 (Context - The Dot .):
  • 在模板的任何给定点,都有一个“当前上下文”,通过一个点 (.) 来引用。

  • 上下文的值可以是单个数据(如字符串、数字),也可以是一个包含多个字段和方法的复杂对象(如 Hugo 的页面对象或站点对象)。

    示例: {{ .Title }} - 如果当前上下文是一个页面对象,这将输出该页面的标题。

  • 上下文的改变: 在 range 循环或 with 语句块内部,. 的上下文会改变,指向当前循环的元素或 with 语句绑定的值。

  • 访问顶层上下文 ($.): 在被改变的上下文中(如 rangewith 块内),可以使用美元符号加点 ($.) 来引用传递到该模板最顶层的上下文。例如,在 range 循环内部,{{ $.Site.Title }} 仍然可以访问站点标题。

  1. 注释: {{/* 这是一个模板注释,不会被渲染 */}}

学习和调试技巧:

  • 官方文档: Hugo 的官方文档 (gohugo.io/documentation/) 。
  • printf "%#v" .: 在模板中临时打印 {{ printf "%#v" . }} 可以输出当前上下文 . 的详细结构和所有可用字段,对于理解数据结构非常有帮助。
  • warnf / errorf: 使用这些函数在 Hugo 控制台打印调试信息或在特定条件下中断构建。

常用站点变量

站点变量 提供了访问整个站点配置和全局信息的能力。可以在模板中通过 .Site (如果当前上下文是页面对象) 或直接通过全局变量 site (在大多数情况下) 来访问。

  • .Site.Title: (字符串) 站点的全局标题 (来自 hugo.toml)。
  • .Site.BaseURL: (字符串) 站点的根 URL (来自 hugo.toml)。
  • .Site.LanguageCode: (字符串) 站点的主要语言代码。
  • .Site.Params: (映射) 包含 hugo.toml[params] 部分定义的所有自定义参数。
  • .Site.Menus: (映射) 包含所有已定义菜单的映射。例如,.Site.Menus.main 是一个包含 “main” 菜单所有条目的列表。
  • .Site.Taxonomies: (映射) 包含站点所有分类法的映射。键是分类法的单数名称 (例如 “category”, “tag”),值是另一个映射,该映射的键是词条名称,值是包含该词条下所有页面的加权列表。
  • .Site.Pages: (页面集合) 包含站点所有页面(包括常规页、区段页、首页、分类法页等)的集合。
  • .Site.RegularPages: (页面集合) 包含站点所有常规内容页面的集合(不包括列表页等)。这是最常用于遍历所有文章的集合。
  • .Site.Sections: (页面集合) 包含站点所有顶级区段的页面对象(即顶级目录 _index.md 对应的页面)。
  • .Site.Data: (映射) 允许访问 data/ 目录下所有数据文件(JSON, YAML, TOML)的内容。
  • .Site.Lastmod: (时间对象) 整个站点的最后修改时间(通常是最新内容的修改时间)。
  • .Site.Config: (配置对象) 提供对站点完整配置的访问,包括语言特定配置。例如 .Site.Config.Services.GoogleAnalytics.ID
  • .Site.Languages: (语言集合) 在多语言站点中,包含所有已配置语言的列表。
  • .Site.Home: (页面对象) 指向站点首页的页面对象。
  • site.GetPage "path/or/kind" "identifier": 用于获取站点中任何类型的特定页面对象。
    • {{ $blogSection := site.GetPage "section" "posts" }}
    • {{ $aboutPage := site.GetPage "about.md" }} (如果 about.md 在 content 根目录)
  • site.Data.filename.key: 访问 data/filename.toml (或 .yaml, .json) 中 key 的值。

常用页面变量

当 Hugo 渲染一个页面时,当前上下文 . 通常是一个页面对象 (Page Object),它包含了关于该页面的大量信息。

  • .Title: (字符串) 页面的标题,通常来自前置元数据的 title 字段。
  • .Content: (HTML) 页面主要的 Markdown 内容渲染后的 HTML。
  • .Summary: (HTML) Hugo 自动生成的页面摘要(默认前70个词),或来自前置元数据的 summary 字段。
  • .Description: (字符串) 页面的描述,通常来自前置元数据的 description 字段,常用于 SEO。
  • .Permalink: (字符串) 页面的绝对永久链接 (URL),包含 baseURL
  • .RelPermalink: (字符串) 页面相对于站点根目录的永久链接。推荐用于内部链接。
  • .Date: (时间对象) 页面的主要日期,通常来自前置元数据的 date 字段。可以使用 .Format 方法格式化。
  • .PublishDate: (时间对象) 页面的发布日期,来自 publishDate 字段。如果未设置,则回退到 date
  • .Lastmod: (时间对象) 页面的最后修改日期,可以来自 Git 信息或前置元数据的 lastmod 字段。
  • .Params: (映射) 一个包含页面前置元数据中所有自定义参数的映射。例如,如果前置元数据中有 author: "Alice",则可以通过 .Params.author 访问。
  • .WordCount: (整数) 页面内容的字数。
  • .ReadingTime: (整数) 估计的阅读时间(分钟)。
  • .TableOfContents: (HTML) Hugo 根据页面 Markdown 内容中的 H2-H6 标题自动生成的 HTML 目录。可以在 hugo.toml[markup.tableOfContents] 中配置其行为。
  • .Kind: (字符串) 页面的种类,例如 "page", "home", "section", "taxonomy", "term"
  • .Type: (字符串) 页面的内容类型,通常由其所在区段的第一级目录名决定,或由前置元数据的 type 字段指定。
  • .Layout: (字符串) 页面前置元数据中指定的布局名称 (如果有)。
  • .Section: (字符串) 页面所属的顶级区段的名称 (例如 content/blog/my-post.md.Section"blog")。
  • .CurrentSection: (页面对象) 当前页面所属的顶级区段的页面对象 (即该区段 _index.md 对应的 Page 对象)。非常适用于在单页中链接到同区段的其他文章。(Hugo 0.93.0+)
  • .Parent: (页面对象) 当前页面的父级页面对象(如果它是嵌套在某个 _index.md 下的,或者它是区段的直接子页面)。
  • .IsHome: (布尔值) 如果当前页面是站点首页,则为 true
  • .IsPage: (布尔值) 如果当前页面是一个常规的内容页面 (.Kind"page"), 则为 true
  • .IsSection: (布尔值) 如果当前页面是一个区段列表页 (.Kind"section"), 则为 true
  • .File: (文件对象) 提供关于源文件的信息,例如 .File.Path, .File.LogicalName, .File.UniqueID
  • .Resources: (资源集合) 如果当前页面是一个页面包 (Page Bundle) (即 content/my-post/index.md 结构),.Resources 提供了访问该包内所有资源的方法。
  • .Translations: (页面集合) 一个包含当前页面所有已翻译版本的页面对象的列表(用于多语言站点)。
  • .OutputFormats: (输出格式集合) 获取当前页面可用的输出格式列表。
  • .AlternativeOutputFormats: (输出格式集合) 获取当前页面除了 HTML 之外的其他输出格式。
  • .Site: (站点对象) 从页面上下文中访问全局站点信息和配置。

常用语法与函数

  1. 变量定义:

    • {{ $myVariable := "一些值" }}: 声明并初始化一个新变量。变量名以 $ 开头。
    • {{ $myVariable = "新值" }}: 给一个已声明的变量重新赋值 (注意,作用域可能受限)。
  2. 输出变量:

    • {{ .VariableName }}: 输出变量 VariableName 的值。
    • {{ .Params.customField }}: 输出页面前置元数据中 params 下的 customField 的值。
    • {{ site.Title }}{{ .Site.Title }}: 输出站点标题。(site 是一个全局可用的变量,指向站点对象;在页面上下文中,.Site 也可以访问)。
  3. 管道符 |:

    • 用于将一个表达式的结果传递给下一个函数作为其最后一个参数(或在某些情况下是第一个)
    • {{ .Content | markdownify }}: 将页面的 Markdown 内容 .Content 传递给 markdownify 函数进行渲染。

    示例:{{ "hello world" | upper | printf "Formatted: %s" }}: “hello world” -> “HELLO WORLD” -> “Formatted: HELLO WORLD”

  4. 比较函数:

    • eq: 等于 (e.g., {{ if eq .Title "首页" }})
    • ne: 不等于
    • lt: 小于
    • le: 小于等于
    • gt: 大于
    • ge: 大于等于
  5. 逻辑操作符:

    • {{ if and (gt .WordCount 100) .Params.hasSummary }} (逻辑与)
    • {{ if or .IsHome .IsSection }} (逻辑或)
    • {{ if not .IsPage }} (逻辑非)

条件与循环语句

  1. 条件语句 (if, else if, else, end):

    go-html-template
      {{ if .Params.showAuthor }}
        <p>作者: {{ .Params.authorName }}</p>
      {{ else if .Params.showEditor }}
        <p>编辑: {{ .Params.editorName }}</p>
      {{ else }}
        <p>作者信息未提供。</p>
      {{ end }}

    条件可以是布尔值、指针、数组、切片、字符串、映射等。非零数字、非空字符串/集合/映射、非 nil 指针被视为 true

  2. with 语句 (with/else/end): 用于检查一个变量是否有“真”值,并在其块内将上下文 . 重新绑定到该变量的值。如果变量为“假”(如 nil 或空字符串),则 with 块不执行,可以有 else 块。

    go-html-template
      {{ with .Params.featuredImage }}
        <img src="{{ .RelPermalink }}" alt="特色图片"> {{/* 这里的 . 指向 featuredImage 对象 */}}
      {{ else }}
        <p>没有特色图片。</p>
      {{ end }}
  3. 循环 (range, else, end): 用于遍历数组、切片、映射或通道。

    go-html-template
      {{ range .Site.Menus.main }}
        <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{/* 这里的 . 指向当前菜单项 */}}
      {{ else }}
        <p>没有菜单项。</p>
      {{ end }}

    遍历映射时:{{ range $key, $value := .Params }}


Hugo模板函数

  • 字符串处理: upper, lower, title (首字母大写), humanize, urlize, substr, replace, strings.HasPrefix, strings.Contains, strings.TrimSuffix, chomp (移除末尾换行)
  • 数学: add, sub, mul, div, mod, math.Max, math.Min
  • 日期和时间: now (当前时间对象), .Date.Format "layout", time "string" (将字符串转为时间对象)
  • 内容处理: markdownify (渲染 Markdown), plainify (移除 HTML 标签), safeHTML, safeCSS, safeJS, safeURL (标记内容为安全,避免转义)
  • URL 和路径: relURL, absURL, relLangURL, absLangURL
  • 数据处理: jsonify, transform.Unmarshal
  • 集合操作: first N, last N, after N, slice collection start [end], in collection item, intersect coll1 coll2, union coll1 coll2, uniq collection, len collection
  • 条件: default value fallback, isset object "key"
  • 字典/映射: dict "key1" val1 "key2" val2
  • 资源处理 (Hugo Pipes): resources.Get, resources.FromString, minify, fingerprint, images.Resize, css.Sass, js.Build 等。
  • 调试: printf, warnf, errorf

模板组织与继承

Hugo 的模板系统通过一套优雅的继承和组合机制,允许开发者创建既灵活又易于维护的网站布局。核心概念包括基础模板 (baseof.html)、块 (block)、定义 (define) 和局部模板 (_partials/)。

1. baseof.html - 网站的统一骨架

baseof.html 文件是 Hugo 模板继承系统的基石。它通常位于 layouts/ 目录(或 layouts/_default/ 在旧版 Hugo 中,但新版推荐直接在 layouts/)。

  • 作用:

    • 定义整个网站所有(或大部分)页面共享的HTML骨架结构。这包括 <html>, <head>, <body> 标签,以及通常不变的页眉 (header)、页脚 (footer)、主要的导航结构等。
    • 通过块 (block) 机制,在骨架中预留出可被具体页面模板填充或覆盖的区域。
  • 示例 layouts/baseof.html:

    go-html-template
    <!DOCTYPE html>
    <html lang="{{ .Site.LanguageCode | default "en-us" }}">
    <head>
        {{- partial "head-content.html" . -}} {{/* 引入通用的 head 内容 */}}
        {{- block "head-extra" . }}{{ end -}} {{/* 为特定页面预留的额外 head 内容区 */}}
    </head>
    <body class="flex flex-col min-h-screen">
        <div id="page-content-wrapper" class="flex-grow">
            <header id="site-header" role="banner">
                {{- partial "header.html" . -}} {{/* 引入通用页眉 */}}
            </header>
    
            {{/* 主体内容块,由子模板决定其内部结构 */}}
            {{- block "body-content" . -}}
                {{/* 默认行为:单栏主内容区 */}}
                <div class="container mx-auto py-8">
                    <main id="main-content-default" role="main">
                        {{- block "main" . }}{{/* 主要内容填充区 */}}{{- end -}}
                    </main>
                </div>
            {{- end -}}
        </div>
    
        <footer id="site-footer" role="contentinfo">
            {{- partial "footer.html" . -}} {{/* 引入通用页脚 */}}
        </footer>
        {{- partialCached "body-scripts.html" . -}} {{/* 引入通用的 body 底部脚本 */}}
    </body>
    </html>
  • 最佳实践: 一个站点通常只需要一个主 baseof.html 文件。

    你也可以为特定的区段或内容类型创建不同的 baseof.html 文件 (例如 layouts/posts/baseof.html),它们会覆盖全局的 baseof.html,为该区段提供完全不同的骨架。但这种做法相对少见,通常通过在一个 baseof.html 中定义多个 block 来实现不同页面的差异化。

2. {{ block "name" . }} - 在骨架中定义可覆盖区域

block 关键字用于在基础模板 (baseof.html) 中定义一个命名区域,这个区域的内容可以被子模板(如 page.html, home.html 等)提供或覆盖。

  • 语法: {{ block "blockName" .Context }} 可选的默认内容 {{ end }}

    • "blockName": 你为这个块指定一个唯一的名称(字符串)。
    • .Context: 通常传递当前上下文 (.) 给这个块,这样默认内容或子模板填充的内容可以访问它。
    • 可选的默认内容: 如果子模板没有定义(define)这个块,那么块内部的默认内容(如果有的话)将会被渲染。
  • 示例 (在 baseof.html 中):

    go-html-template
    <head>
        <title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title> {{/* "title" 块,默认显示站点标题 */}}
        {{ block "head-extra" . }}{{ end }} {{/* "head-extra" 块,默认无内容 */}}
    </head>
    <body>
        {{ block "main" . }}
            <p>这是 main 块的默认内容,如果子模板没有定义 main 块,你就会看到我。</p>
        {{ end }}
    </body>
  • 嵌套块: 你可以在一个 block 内部定义另一个 block,实现更细粒度的控制。

    go-html-template
    {{ block "content" . }}
        <article>
            {{ block "article-header" . }}<h2>默认文章标题</h2>{{ end }}
            {{ block "article-body" . }}<p>默认文章内容。</p>{{ end }}
        </article>
    {{ end }}

3. {{ define "name" . }} - 在子模板中填充或覆盖区域

define 关键字用于在子模板中(例如 layouts/page.html, layouts/home.html, layouts/section.html 等)提供 baseof.html 中同名 block 的具体实现。

  • 语法: {{ define "blockName" .Context }} 此块的具体 HTML 和模板逻辑 {{ end }}

    • "blockName": 必须与 baseof.htmlblock 的名称完全匹配。
    • .Context: 通常是 .,表示这个 define 块内的代码将使用子模板自身的上下文(通常是当前页面对象)。
  • 工作原理: 当 Hugo 渲染一个页面时(例如,渲染一篇博客文章):

    1. Hugo 根据页面的类型、区段、布局等因素选择一个最终的内容模板 (例如 layouts/posts/single.html 或回退到 layouts/page.html)。
    2. 然后,Hugo 查找这个内容模板所基于的基础模板 (baseof.html)。
    3. Hugo 会先处理 baseof.html。当遇到 {{ block "name" . }} 时,它会检查最终的内容模板是否 {{ define "name" . }} 了这个块。
      • 如果内容模板中对应的 define,则 baseof.html 中的 block 区域将被内容模板中 define 的内容替换
      • 如果内容模板中没有对应的 define,则 baseof.html 中的 block 区域将使用其自身的默认内容(如果块定义时提供了默认内容)。如果块定义时也没有默认内容(如 {{ block "name" . }}{{ end }}),则该区域为空。
  • 示例 layouts/page.html:

    go-html-template
    {{/* layouts/page.html */}}
    {{/* 这个模板隐式地继承了 layouts/baseof.html */}}
    
    {{/* 覆盖 baseof.html 中的 "title" 块 */}}
    {{ define "title" }}
        {{ .Title }} | {{ .Site.Title }} {{/* 文章页标题格式 */}}
    {{ end }}
    
    {{/* 覆盖 baseof.html 中的 "main" 块 */}}
    {{ define "main" }}
        <article>
            <h1>{{ .Title }}</h1>
            <div class="content">
                {{ .Content }}
            </div>
        </article>
    {{ end }}
    
    {{/* page.html 没有 define "head-extra",所以如果 baseof.html 的 head-extra 块是空的,
         那么最终渲染的 HTML 中,head-extra 部分也会是空的。
         如果 baseof.html 的 head-extra 块有默认内容,则会使用那个默认内容。
    */}}
  • define 块的位置: define 块可以出现在子模板文件的任何顶层位置,它们的顺序不重要。Hugo 会先解析所有 define,然后再与 baseof.html 组合。

  • 注意: 在 {{ define "blockName" . }}{{ end }} 语句之外,只允许出现模板注释 ({{/* ... */}})。任何其他文本(包括 HTML 注释 <!-- --> 或普通文本)都可能导致模板解析错误或意外的空白输出。

4. layouts/_partials/ - 可复用的 HTML 片段 (局部模板)

局部模板 (Partials) 是存放在 layouts/_partials/ 目录(或主题的 themes/THEME_NAME/layouts/_partials/)下的小块 HTML 模板代码。它们用于封装那些可能在多个不同模板中重复使用的 UI 组件或代码片段。

  • 作用:

    • 代码复用 (DRY - Don’t Repeat Yourself): 避免在多个模板中复制粘贴相同的 HTML 结构。
    • 模块化和组织性: 将复杂的页面分解为更小、更易于管理的部分。
    • 易于维护: 修改一个局部模板,所有引用它的地方都会自动更新。
  • 创建: 在 layouts/_partials/ 目录下创建 .html 文件,例如:

    • layouts/_partials/site-header.html
    • layouts/_partials/post-card.html
    • layouts/_partials/sidebar/categories-widget.html (可以有子目录)
  • 调用: 在任何其他模板文件中(包括 baseof.html、页面模板、甚至其他局部模板中),使用 partialpartialCached 函数来嵌入一个局部模板。

    • {{ partial "path/to/partialName.html" .Context }}:

      • "path/to/partialName.html": 局部模板文件相对于 layouts/_partials/ 目录的路径。如果 partial 在 _partials/ 根目录下,则直接用文件名,例如 "footer.html"。如果在子目录中,例如 _partials/components/button.html,则用 "components/button.html"
      • .Context: 非常重要! 这是传递给局部模板的上下文。通常我们传递 . (当前上下文),这样局部模板就能访问到调用它的模板所拥有的数据(例如当前页面对象 ., 站点对象 .Site 等)。如果不传递上下文,局部模板内部的 . 将是 nil 或一个空的上下文。
    • {{ partialCached "path/to/partialName.html" .Context [cacheKeyParam1] [cacheKeyParam2] ... }}:

      • partial 类似,但 Hugo 会尝试缓存这个局部模板的渲染结果
      • 如果一个局部模板的内容不经常变化,或者其输出仅依赖于少数几个参数,使用 partialCached 可以显著提高构建速度,尤其是在大型站点或复杂 partial 中。
      • [cacheKeyParam...]: (可选) 提供额外的参数作为缓存键的一部分。如果这些参数的值发生变化,Hugo 会重新渲染该 partial。如果上下文 . 很复杂且经常变化,而 partial 的输出仅依赖于其中一小部分,那么明确提供这些小的依赖作为缓存键会更有效。
  • 示例: layouts/_partials/copyright.html:

    go-html-template
    <p>&copy; {{ now.Year }} {{ .Site.Title }}</p>

    layouts/_partials/footer.html 中调用:

    go-html-template
    <footer>
        {{ partial "copyright.html" . }}
        <p>All rights reserved.</p>
    </footer>
  • 局部模板的上下文:

    • 局部模板接收调用它时传递的上下文。
    • 在局部模板内部,. 指向这个传递过来的上下文。
    • 如果需要访问调用 partial 函数的那个模板的顶层上下文(例如,如果 partial 被嵌套调用了),可以使用 $.
  • 局部模板返回值: 局部模板不仅可以输出 HTML,还可以通过 {{ return $value }} 语句返回一个值 (任何类型)。这使得 partial 可以像函数一样使用。 layouts/_partials/get-featured-image.html:

    go-html-template
    {{ $image := "" }}
    {{ with .Resources.GetMatch "featured.*" }}
        {{ $image = .RelPermalink }}
    {{ else with .Site.Params.defaultFeaturedImage }}
        {{ $image = . | relURL }}
    {{ end }}
    {{ return $image }}

    调用: {{ $featuredImgSrc := partial "get-featured-image.html" . }}


核心页面模板

在 Hugo 中,layouts/ 目录下的不同 HTML 文件充当了渲染不同种类和类型页面的“蓝图”。Hugo 通过一套精密的模板查找顺序 (Template Lookup Order) 来为每一个需要生成的页面选择最合适的模板。

所有这些具体的页面模板(如 section.html, page.html)通常都会隐式地或显式地继承 layouts/baseof.html(如果存在)。它们通过 {{ define "blockName" . }} 来填充或覆盖 baseof.html 中定义的 {{ block "blockName" . }} 区域。

以下是 Hugo中最常用的一些具体页面模板文件名及其详细描述:

1. layouts/home.html (首页模板)

  • layouts/home.html (首页模板)作用:
    • 专门用于渲染你网站的首页 (即访问你网站根 URL baseURL/ 时看到的页面)。
    • 这是唯一一个 Hugo 页面种类 (Page Kind) 为 home 的页面。
  • 原理:
    • 当 Hugo 构建首页时,它会优先查找 layouts/home.html
    • 模板的上下文 . 是代表首页的页面对象,其内容和元数据通常来自站点根目录下的 content/_index.md 文件 (如果存在)。
    • 它会填充 baseof.html 中的块。
  • 典型内容:
    • 网站的欢迎信息、品牌展示。
    • 最新文章列表、特色内容摘要。
    • 指向网站其他重要部分的链接。
  • 示例 (layouts/home.html):
    go-html-template
    {{ define "title" }}{{ .Site.Title }}{{ with .Site.Params.tagline }} - {{ . }}{{ end }}{{ end }}
    {{ define "main" }}
        <h1>{{ .Site.Title }}</h1>
        {{ with .Site.Params.description }}<p>{{ . }}</p>{{ end }}
        {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }} {{/* 来自 content/_index.md */}}
        <h2>最新博文</h2>
        <ul>
            {{ range first 5 (where site.RegularPages "Type" "posts") }}
                <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
            {{ end }}
        </ul>
    {{ end }}

2. layouts/page.htmllayouts/single.html

  • layouts/page.html (单页模板 - 推荐的通用名称)或 layouts/single.html (单页模板 - 传统名称)作用:
    • 用于渲染单个、独立的内容页面,例如一篇博客文章、一个“关于我们”页面、一个产品详情页等。
    • 对应 Hugo 页面种类 (Page Kind) 中的 page
    • 注意: Hugo v0.146.0+ 倾向于使用 page.html 作为比 single.html 优先级更高的通用单页模板名。如果两者都存在于同一查找路径,page.html 会被优先选择。通常,在一个项目中,你会选择使用其中一个作为主要的通用单页模板。
  • 原理:
    • 当 Hugo 渲染一个常规内容文件 (例如 content/posts/my-article.md 或一个 Leaf Bundle 如 content/about/index.md) 时,它会查找对应的单页模板。
    • 模板的上下文 . 是代表当前这篇文章或页面的页面对象。
    • 它会填充 baseof.html 中的块。
  • 典型内容:
    • 文章标题、发布日期、作者、分类、标签等元信息。
    • 文章的主要内容 (.Content)。
    • 相关文章链接、评论区等。
    • (如我们教程中)左侧区段导航、右侧内容目录。
  • 示例 (layouts/page.html):
    go-html-template
    {{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }}
    {{ define "main" }}
        <article>
            <h1>{{ .Title }}</h1>
            <p>发布于: {{ .Date.Format "January 2, 2006" }}</p>
            <div>{{ .Content }}</div>
        </article>
    {{ end }}

3. layouts/section.html

  • layouts/section.html (区段列表模板)作用:
    • 用于渲染区段 (Section) 的列表页面。区段通常对应于 content/ 目录下的顶级文件夹(如 content/posts/)或嵌套文件夹(如 content/docs/v1/)。
    • 这个页面会列出该区段下的内容(文章、子区段等)。
    • 对应 Hugo 页面种类 (Page Kind) 中的 section
  • 原理:
    • 当用户访问一个区段的 URL (例如 yoursite.com/posts/yoursite.com/docs/v1/) 时,如果该区段目录下存在 _index.md 文件,Hugo 会使用这个 _index.md 作为该区段列表页的内容源和元数据源。
    • Hugo 会查找用于渲染此区段列表页的模板。
    • 模板的上下文 . 是代表当前区段列表页的页面对象 (来自其 _index.md)。
    • 它会填充 baseof.html 中的块。
  • 典型内容:
    • 区段的标题和描述 (来自 _index.md)。
    • 该区段下所有文章或子页面的列表(通常带分页)。
    • 指向子区段的链接 (如果存在)。
  • 示例 (layouts/section.html):
    go-html-template
    {{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }}
    {{ define "main" }}
        <h1>{{ .Title }}</h1>
        <div>{{ .Content | markdownify }}</div>
        <ul>
            {{ $paginator := .Paginate .RegularPages.ByDate.Reverse }}
            {{ range $paginator.Pages }}
                <li>
                    <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
                    <p>{{ .Summary }}</p>
                </li>
            {{ end }}
        </ul>
        {{ partial "pagination.html" (dict "paginator" $paginator) }}
    {{ end }}

4. layouts/taxonomy.html

  • layouts/taxonomy.html` (分类法列表模板)作用:
    • 用于渲染一个分类法 (Taxonomy) 本身的列表页面。这个页面通常会列出该分类法下的所有词条 (Terms)
    • 例如,访问 /categories/ 会显示所有已定义的分类(如 “技术”, “生活”);访问 /tags/ 会显示所有已定义的标签。
    • 对应 Hugo 页面种类 (Page Kind) 中的 taxonomy
  • 原理:
    • 当用户访问一个分类法的根 URL (例如 yoursite.com/categories/) 时,Hugo 会查找此模板。
    • 如果存在对应的 content/taxonomyName/_index.md (例如 content/categories/_index.md),它将作为该页面的内容源。
    • 模板的上下文 . 是代表当前分类法列表页的页面对象。
    • 它会填充 baseof.html 中的块。
  • 典型内容:
    • 分类法的标题 (例如 “所有分类”)。
    • 该分类法下所有词条的列表,通常每个词条会显示其名称、链接以及关联的文章数量。
  • 示例 (layouts/taxonomy.html):
    go-html-template
    {{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }}
    {{ define "main" }}
        <h1>{{ .Title }}</h1>
        {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }}
        <ul>
            {{ range .Data.Terms.ByCount }} {{/* 按文章数量排序词条 */}}
                <li>
                    <a href="{{ .Page.RelPermalink }}">{{ .Page.Title | humanize }}</a> 
                    <span>({{ .Count }})</span>
                </li>
            {{ end }}
        </ul>
    {{ end }}

5. layouts/term.html

  • layouts/term.html (词条列表模板)作用:
    • 用于渲染属于某个特定分类法词条 (Term) 的所有内容页面的列表。
    • 例如,访问 /categories/hugo/ 会显示所有被归类到 “hugo” 这个分类下的文章。
    • 对应 Hugo 页面种类 (Page Kind) 中的 term
  • 原理:
    • 当用户访问一个特定词条的 URL (例如 yoursite.com/categories/hugo/) 时,Hugo 会查找此模板。
    • 如果存在对应的 content/taxonomyName/termName/_index.md (例如 content/categories/hugo/_index.md),它将作为该页面的内容源。
    • 模板的上下文 . 是代表当前词条列表页的页面对象。
    • 它会填充 baseof.html 中的块。
  • 典型内容:
    • 词条的标题 (例如 “分类:Hugo”)。
    • 列出所有属于该词条的文章(通常带分页)。
  • 示例 (layouts/term.html):
    go-html-template
    {{ define "title" }}{{ .Title }} ({{ .Data.Singular | humanize }}) | {{ .Site.Title }}{{ end }}
    {{ define "main" }}
        <h1>{{ .Title }}</h1>
        <p>属于 "{{ .Data.Plural | humanize }}" 分类法</p>
        {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }}
        <ul>
            {{ $paginator := .Paginate .Pages.ByDate.Reverse }}
            {{ range $paginator.Pages }}
                <li>
                    <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
                    <p>{{ .Summary }}</p>
                </li>
            {{ end }}
        </ul>
        {{ partial "pagination.html" (dict "paginator" $paginator) }}
    {{ end }}

6. layouts/list.html

  • layouts/list.html (通用列表模板)作用:
    • 这是一个通用的回退列表模板。如果 Hugo 在为某个列表类型的页面(如 section, taxonomy, term)查找更具体的模板(如 section.htmltaxonomy.html)时没有找到,它最终会尝试使用 layouts/list.html
  • 原理:
    • 它的上下文 . 会是当前正在渲染的列表页面对象(可能是区段、分类法或词条)。
    • 因此,list.html 需要编写得足够通用,能够处理不同列表类型的共性(例如,它们都有 .Title, .Content, .Pages.Data.Terms 等)。
  • 典型内容:
    • 显示 .Title.Content
    • 根据 .Kind 或其他条件来决定是遍历 .Pages (对于 section, term) 还是 .Data.Terms (对于 taxonomy)。
    • 包含分页逻辑。
  • 何时使用:
    • 如果你的所有列表页(或大部分)具有非常相似的布局和功能,你可以只创建一个 list.html,而不是为 section, taxonomy, term 分别创建模板。
    • 或者,你可以创建 section.html, taxonomy.html, term.html 作为主要的列表模板,而将 list.html 作为一种不太可能被触发的最终安全网,或者用于一些你没有预料到的自定义列表类型。

模板的查找顺序与特化

Hugo 有一个非常明确的模板查找顺序,它总是从最具体到最通用

简化版的查找逻辑 (以渲染 /posts/my-category/_index.md 这个子区段列表页为例):

  1. layouts/posts/my-category/section.html (最具体:特定路径下的特定类型)
  2. layouts/posts/my-category/list.html
  3. layouts/my-category/section.html (按类型或区段名)
  4. layouts/my-category/list.html
  5. layouts/posts/section.html (父区段的)
  6. layouts/posts/list.html
  7. layouts/section.html (全局默认区段模板)
  8. layouts/list.html (全局默认列表模板)
  9. themes/THEME_NAME/layouts/... (在主题中按类似顺序查找)

这意味着:

  • 你可以通过在更具体的路径下创建模板来覆盖 (override) 更通用的模板。
  • 例如,如果你有一个通用的 layouts/section.html,但希望 /posts/ 区段有完全不同的列表布局,你只需创建 layouts/posts/section.html 即可。

通过前置元数据指定模板:

除了基于路径和类型的查找,你还可以在任何内容文件(包括 _index.mdindex.md)的前置元数据中通过 layouttype 参数来强制 Hugo 使用特定的模板文件

  • layout: "my-custom-layout": Hugo 会查找 layouts/my-custom-layout.html (或者根据页面种类,如 layouts/_default/my-custom-layout.html 在旧版中,或直接在 layouts/ 下查找)。layout 参数指定的模板通常会覆盖基于种类和区段的查找。
  • type: "product": Hugo 会在 layouts/product/ 目录下查找对应的模板 (例如 layouts/product/list.htmllayouts/product/single.html),这个查找优先级很高。

Shortcodes 和 Hooks

Hugo 不仅仅是一个简单的 Markdown 到 HTML 的转换器。它提供了强大的扩展机制,如短代码 (Shortcodes) 和渲染钩子 (Render Hooks),能够轻松地在内容中嵌入复杂元素或自定义 Markdown 的渲染行为。

短代码 (Shortcodes)

短代码 (Shortcodes) - 能在内容中更方便的添加丰富的功能,如视频播放器、图片库、提示框、自定义组件等。短代码主要分为下列两种类型:

  • 简单短代码 (Simple Shortcodes): 不包含结束标签,通常用于插入简单的、自闭合的 HTML 元素或简短内容。

    Markdown 中的调用方式: {{/* < shortcodeName param1="value1" param2="value2" > */}} 。 或者使用位置参数: {{/*< shortcodeName "value1" "value2" >*/}}

  • 闭合短代码 (Closing Shortcodes): 包含开始标签和结束标签,它们之间的内容(称为 .Inner 内容)可以被短代码模板捕获和处理。Markdown 中的调用方式:

    markdown
    {{< shortcodeName param1="value1" >}}
    这里是 **Markdown** 格式的 _内部内容_{{< /shortcodeName >}}

一. 如何创建自定义短代码

1. 自定义短代码的存放位置:

  • 在项目或主题根目录下的 layouts/_shortcodes/ 目录中创建 .html 文件。文件名(不含扩展名)即为短代码的名称。例如,layouts/_shortcodes/youtube.html 将定义一个名为 youtube 的短代码。
  • 可以在 _shortcodes/ 下创建子目录来组织它们,调用时需要指定相对路径,例如 {{< media/video src="..." >}} 对应 layouts/_shortcodes/media/video.html

短代码的查找顺序:

Hugo 会首先在项目根目录的 layouts/_shortcodes/ 中查找,然后在主题的 layouts/_shortcodes/ 中查找。这意味着可以通过在项目中创建同名文件来覆盖主题提供的短代码。

2. 短代码模板 (.html 文件) 的内容: 短代码模板是标准的Hugo模板,可使用所有可用的模板变量和函数

  • 访问参数: 使用.Get "paramName": 获取命名参数的值。

    • .Get N: 获取位置参数的值 (N 是从0开始的索引)。
    • .Params: 一个包含所有传递给短代码的参数的集合。
      • 如果使用命名参数,.Params 是一个映射 (map)。
      • 如果使用位置参数,.Params 是一个切片 (slice)。
    • .IsNamedParams: 布尔值,判断调用时是否使用了命名参数。
  • 访问内部内容 (.Inner): 仅对闭合短代码有效。

    • .Inner 变量包含开始和结束短代码标签之间的原始文本内容。
    • 通常需要用 | markdownify (如果内部内容是 Markdown) 或 .Page.RenderString (更通用的渲染方式,也会处理短代码) 来处理 .Inner 以得到渲染后的 HTML。
      go-html-template
      {{ .Inner | .Page.RenderString (dict "display" "block") }}
  • 访问页面上下文 (.Page): .Page 变量提供了对调用该短代码的那个页面的完整页面对象的访问权限。这允许短代码根据其所在的页面上下文来调整行为。例如,{{ .Page.Title }}

  • 其他有用的短代码变量:

    • .Name: 短代码的文件名(不含扩展名)。
    • .Ordinal: 短代码在其父级内容中的顺序(从0开始)。
    • .Parent: 如果短代码被嵌套在另一个短代码内部,.Parent 提供了对父短代码上下文的访问。
    • .Position: 调用短代码的源文件名和行号,非常适用于错误报告 (warnf, errorf)。
    • .Scratch: 短代码作用域的“草稿本”,用于存储和操作临时数据。

示例1:简单的 youtube.html 短代码: layouts/_shortcodes/youtube.html:

go-html-template
{{- $id := .Get 0 | default (.Get "id") -}}
{{- if $id -}}
<div class="youtube-embed-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%;">
    <iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
            src="https://www.youtube.com/embed/{{ $id }}"
            title="YouTube video player"
            frameborder="0"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            allowfullscreen>
    </iframe>
</div>
{{- else -}}
    {{- warnf "YouTube shortcode called without an ID. Position: %s" .Position -}}
{{- end -}}

在 Markdown 中使用: {{< youtube "VIDEO_ID_HERE" >}}{{< youtube id="VIDEO_ID_HERE" >}}


示例2:自定义提示框 alert.html (闭合短代码): layouts/_shortcodes/alert.html:

go-html-template
{{ $type := .Get "type" | default "info" }}
<div class="alert alert-{{ $type }}" role="alert">
    {{ with .Get "title" }}<h4>{{ . }}</h4>{{ end }}
    {{ .Inner | .Page.RenderString (dict "display" "block") }}
</div>

在 Markdown 中使用:

markdown
{{< alert type="warning" title="重要提示" >}}
这是一个**警告**信息,请仔细阅读。
列表:
*   项目一
*   项目二
{{< /alert >}}

二. Hugo 内置短代码

Hugo 提供了一些有用的内置短代码,例如:

  • figure: 用于插入带标题和属性的图片。
  • gist: 嵌入 GitHub Gist。
  • highlight: 高亮显示代码块 (虽然现在更推荐使用 Markdown 的代码围栏)。
  • refrelref: 创建对其他内部内容页面的健壮链接。
  • tweet: 嵌入推文。 查阅 Hugo 官方文档以获取完整的内置短代码列表及其用法。

渲染钩子 (Render Hooks)

当想改变某些 Markdown 元素(如图片、链接、标题、代码块)默认渲染成 HTML 的方式时,可以使用渲染钩子 (Render Hooks)

  • 渲染钩子允许你为特定的 Markdown 元素提供自定义的 HTML 模板。当 Hugo 的 Markdown 处理器 (Goldmark 是默认的)遇到这些元素时,它会使用你的自定义模板来生成 HTML,而不是使用其内置的默认渲染逻辑。

  • Hugo 支持以下几种元素的渲染钩子:

    • 链接 (Link): render-link.html
    • 图片 (Image): render-image.html
    • 标题 (Heading): render-heading.html
    • 代码块 (Code Block): render-codeblock.html (以及更具体的 render-codeblock-LANGUAGE.html)

何时使用渲染钩子?

当需要全局性地、统一地改变某种 Markdown 元素的默认 HTML 输出时。 例如:所有外部链接都自动在新标签页打开;所有图片都自动包裹在 <figure> 中并添加特定的 CSS 类;所有标题都自动带有锚链接。


如何创建渲染钩子

  1. 存放位置: 在项目或主题根目录下的 layouts/_markup/ 目录中创建与钩子类型对应的.html文件

    • 例如,自定义链接渲染:layouts/_markup/render-link.html
    • 例如,自定义图片渲染:layouts/_markup/render-image.html
  2. 渲染钩子模板的内容: 钩子模板会接收一些特定的上下文变量,这些变量包含了关于正在被渲染的 Markdown 元素的信息。

  • render-link.html 上下文:

    • .Destination: 链接的目标 URL。
    • .Text: 链接的显示文本(Markdown: [Text](Destination))。
    • .Title: 链接的 title 属性(Markdown: [Text](Destination "Title"))。
    • .Page: 当前页面的 Page 对象。

    示例: (为外部链接添加 target="_blank"rel="noopener noreferrer"):

    go-html-template
    <a href="{{ .Destination | safeURL }}"
        {{- with .Title }} title="{{ . }}"{{ end -}}
        {{- if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener noreferrer"{{ end -}}
    >{{ .Text | safeHTML }}</a>
  • render-image.html 上下文:

    • .Destination: 图片的 URL。
    • .Text: 图片的 alt 文本(Markdown: ![Text](Destination))。
    • .Title: 图片的 title 属性(Markdown: ![Text](Destination "Title"))。
    • .Page: 当前页面的 Page 对象。
    • .Ordinal: 图片在当前内容中的顺序。

    示例 (layouts/_markup/render-image.html - 为图片添加一个包裹 div 和figcaption):

    go-html-template
    <figure class="render-image-figure">
        <img src="{{ .Destination | safeURL }}" alt="{{ .Text | plainify }}"
            {{- with .Title }} title="{{ . }}"{{ end -}}
        />
        {{- with .Title -}}
            <figcaption>{{ . | markdownify }}</figcaption>
        {{- end -}}
    </figure>
  • render-heading.html 上下文:

    • .Level: 标题级别 (整数 1-6)。
    • .AnchorID: Hugo 为该标题生成的 ID (用于锚链接)。
    • .Text: 标题的文本内容 (已处理 Markdown)。
    • .Page: 当前页面的 Page 对象。

    示例 (layouts/_markup/render-heading.html - 为标题添加一个可点击的锚链接图标):

    go-html-template
    <h{{ .Level }} id="{{ .AnchorID }}">
        {{ .Text | safeHTML }}
        <a href="#{{ .AnchorID }}" class="anchor-link" aria-label="锚链接到此标题">#</a>
    </h{{ .Level }}>

    (还需要为 .anchor-link 添加 CSS 样式使其在悬停时可见等)

  • render-codeblock.htmlrender-codeblock-LANGUAGE.html 上下文:

    • .Type: 代码块的类型(通常是语言名称)。
    • .Inner: 代码块的原始文本内容。
    • .Options: 从代码围栏信息字符串中解析出的选项(例如 hl_lines="1 3-5")。
    • .Ordinal: 代码块在内容中的顺序。
    • .Page: 当前页面的 Page 对象。

    示例 (layouts/_markup/render-codeblock.html - 添加一个复制按钮,实际复制功能需 JS):

    go-html-template
    {{ $code := .Inner }}
    {{ $lang := .Type | default "" }}
    <div class="codeblock-wrapper">
        <div class="codeblock-header">
            <span class="codeblock-language">{{ $lang }}</span>
            <button class="copy-code-button" aria-label="复制代码">复制</button>
        </div>
        {{/* 使用 Hugo 内置的 highlight 函数处理代码高亮 */}}
        {{ highlight $code $lang (.Options | default "") }}
    </div>