从零构建 Hugo + Tailwind 技术博客:以 INFINI Labs Blog 为例

发布: (2025年12月20日 GMT+8 14:40)
10 min read
原文: Dev.to

Source: Dev.to

Source:

1. 目录结构:内容、主题、配置和资源分别放在哪里?

你可以把这个仓库看作四层结构:

1) 内容层 – content/

content/english/posts/      # 博客文章(Markdown + front matter)
content/english/authors/    # 作者页面(Markdown + front matter)

每篇文章都以 front matter 开头(支持 YAML/TOML/JSON;本仓库使用 YAML)。常见字段包括:

字段用途
title / description页面标题和 SEO
date发布时间(影响排序和显示)
categories / tags用于分类页和标签页
image封面图片
author作者名称(链接到对应的作者页面)

模板会读取这些字段。例如,themes/hugoplate/layouts/posts/single.html 会显示封面图片、作者、分类、发布日期、正文内容以及目录(TOC)。

2) 配置层 – hugo.toml + config/_default/*

Hugo 支持在 config/ 目录下拆分配置。本项目使用:

  • 根配置: hugo.toml

    theme = "hugoplate"
    
    [outputs]
      home = ["HTML", "JSON"]   # 生成 public/index.json
    
    [build]
      [build.buildStats]
        enable = true           # 精准的 Tailwind 扫描(后文解释)
  • 语言配置: config/_default/languages.toml

    [languages.en]
      languageName = "English"
      weight = 1
      contentDir = "content/english"
  • 站点参数: config/_default/params.toml – 定义 logo、favicon、主题颜色、公告栏、Cookie 横幅、侧边栏小部件等。

  • Hugo 模块: config/_default/module.toml – 导入模块,例如

    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/images"
    
    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/pwa"
    
    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/seo-tools/basic-seo"
    
    [[imports]]
      path = "github.com/hugomods/mermaid"

    (这些导入是 CI 需要 Go 的原因。)

3) 主题层 – themes/hugoplate/

主题定义页面结构和 Hugo Pipes:

文件用途
themes/hugoplate/layouts/_default/baseof.html基础布局骨架
themes/hugoplate/layouts/index.html首页列表
themes/hugoplate/layouts/posts/single.html文章详情页
themes/hugoplate/layouts/index.json生成 JSON 搜索索引的模板(重要)

注意: 你会在模板中看到 {{ partial "image" ... }},但主题目录里没有 partials/image.html。该 partial 来自 module.toml 中导入的 hugo‑modules/images 模块。这是一种常见模式:主题 + 模块化能力

4) 资源层 – assets/static/

这两个目录在 Hugo 中的行为不同:

目录Hugo 处理方式
assets/由 Hugo Pipes 处理(编译、指纹、压缩等)。
* 图片放在 assets/images/...,构建时会输出到 public/images/...
* 样式/脚本源文件放在 themes/hugoplate/assets/*,通过 Hugo Pipes 打包。
static/原样复制到 public/
包含 static/assets/index-*.cssstatic/assets/index-*.jspizza_wasm_bg-*.wasm
baseof.html 硬编码了对 /assets/index-*.css/assets/index-*.js 的引用。

2. 构建流水线:从 Markdown 到最终 HTML/CSS/JS 的过程是什么?

1) 构建入口点 – package.json 脚本

{
  "scripts": {
    "dev": "hugo server",
    "build": "hugo --gc --minify --templateMetrics --templateMetricsHints --forceSyncStatic",
    "project-setup": "node ./scripts/projectSetup.js"
  }
}
  • dev 运行本地 Hugo 服务器。
  • build 驱动整个生产构建;Node 主要用于 PostCSS/Tailwind。

project-setup 脚本来自 Hugoplate 模板,用于将 exampleSite/ 布局移动到真实项目布局中。由于该仓库已经有 themes/ 目录,脚本通常会打印 “Project already setup” 并不执行任何操作——在 CI 中保留是安全的。

2) Hugo 模块 – 为什么 CI 要安装 Go?

config/_default/module.toml 导入了多个模块(images、pwa、seo‑tools、mermaid 等)。这些模块在 Hugo 构建期间 被获取并参与渲染。仓库的 go.mod 锁定了模块版本,充当 Hugo 模块的依赖清单。

职责划分

工具角色
Hugo渲染站点
Go拉取并管理 Hugo 模块依赖

这就是 CI 同时安装 Hugo Go 的原因(参见 netlify.toml.github/workflows/hugo.yml)。

3) Tailwind 如何只生成实际使用的 CSS?

有两个配置实现了这一点:

  1. Hugo 构建统计 – 在 hugo.toml 中启用

    [build.buildStats]
      enable = true

    Hugo 在渲染模板时会输出 hugo_stats.json,记录生成的 HTML 中使用的每个类/标记。

  2. Tailwind 配置 – 读取统计文件

    // tailwind.config.js
    module.exports = {
      content: ["./hugo_stats.json"],
      // …other Tailwind settings…
    };

Tailwind 读取 hugo_stats.json,能够 非常精准地仅生成所需的实用类,避免完整树扫描导致的误报和更大的 CSS 包。

此外,hugo.toml 挂载统计文件,使其对 Tailwind 可用:

[[module.mounts]]
  source = "hugo_stats.json"
  target = "assets/watching/hugo_stats.json"

摘要

  • Content → Markdown(位于 content/)→ Hugo 渲染为 HTML。
  • Theme & Modulesthemes/hugoplate/ + Hugo 模块(通过 module.toml 导入)。
  • Assets → 由 Hugo Pipes 处理(assets/)或原样复制(static/)。
  • Tailwind → 使用 hugo_stats.json 进行精确的 CSS 生成。
  • Search → 从专用模板生成 index.json,由 static/assets/ 中的预构建 UI 使用。

有了这种结构,站点可以完全使用静态工具构建,部署到任何静态托管平台,并且仍然能够享受强大的功能,如图像处理、PWA 支持、SEO 辅助工具和离线搜索。

缓存破坏配置,以便正确触发 CSS 重建

这是在 基于 Hugoplate 的设置中已经成熟的集成方案。

1) CSS/JS 是如何被打包、压缩和指纹化的?

themes/hugoplate/layouts/partials/essentials/style.html 中可以看到 Hugo Pipes 的流程:

  1. 收集插件 CSS – 来自 hugo.toml 中的 params.plugins.css
  2. 编译 scss/main.scss – 需要 Hugo Extended。
  3. resources.Concat – 合并文件。
  4. css.PostCSS – 运行 PostCSS(Tailwind + autoprefixer)。
    在生产环境下:minify | fingerprint | resources.PostProcess

输出的形式为:

<link href="/assets/css/main-<hash>.css" integrity="..." rel="stylesheet">

JS 的处理方式类似,位于 themes/hugoplate/layouts/partials/essentials/script.html

运行时事实

  • 部署后站点是 纯静态文件(HTML/CSS/JS/图片);不会进行运行时编译。
  • 指纹化 稳定缓存:当内容变化时资产 URL 会改变,浏览器/CDN 就不会提供陈旧的资源。

3. 搜索:index.json/static/assets/* 如何协同工作?

1) 索引从哪里来?

hugo.toml 设置

outputs.home = ["HTML", "RSS", "WebAppManifest", "JSON"]

并通过 baseName = "index" 配置 JSON 输出。在构建过程中,Hugo 会根据模板 themes/hugoplate/layouts/index.json 生成 public/index.json。该模板遍历站点页面,将 titleURLtagscategorydescription 以及纯文本内容等字段打包成一个 JSON 数组。这个文件是静态站点客户端搜索的极佳数据源。

2) 搜索 UI 从哪里来?

themes/hugoplate/layouts/_default/baseof.html 硬编码了对 static/assets/ 中资源的导入。这些文件会被 Hugo 复制到 public/assets/,因此浏览器在部署后可以直接请求它们。

运行时流程

  1. 浏览器加载静态页面。
  2. 加载搜索 UI 的 CSS/JS。
  3. UI 获取 index.json(以及可能的 WASM 资源),在浏览器中构建索引。
  4. 搜索在本地执行——无需后端 API。

这种 “构建时生成,运行时消费” 的模式在增强静态站点时非常常见。

4. 从头构建类似项目(最小可行步骤)

1) 准备工具链

  • Hugo Extended (>= 0.139.2)
  • Go(CI 使用 1.23.3 作为 Hugo Modules)
  • Node(用于 PostCSS/Tailwind;CI 使用 Node 20,本地使用任意 LTS 版本均可)

2) 安装依赖并开始开发

仓库声明使用 pnpm,但脚本同样支持 npm/yarn。

pnpm install
pnpm dev          # or: npm install && npm run dev

这将启动 Hugo 开发服务器。

3) 添加新文章

content/english/posts/YYYY/ 目录下创建一个 Markdown 文件,例如:

---
title: "My First Post"
description: "A short summary"
date: "2025-12-20T09:00:00+08:00"
categories: ["Engineering"]
tags: ["Hugo"]
image: "/images/posts/2025/some-folder/cover.jpg"
author: "Rain9"
lang: "en"
category: "Technology"
subcategory: "Engineering"
draft: true
---

# Hello

Write something here.

提示: 将图片放在 assets/images/posts/... 下,并使用 /images/posts/... 进行引用。构建后它们会输出到 public/images/posts/...

4) 为生产环境构建

pnpm build

输出目录为 public/(参见 netlify.toml: publish = "public")。

5. 部署与 CI/CD:Netlify / GitHub Pages / Vercel 如何构建

该仓库支持多个托管平台。

1) Netlify

netlify.toml

[build]
  command = "yarn project-setup; yarn build"
  publish = "public"

[build.environment]
  HUGO_VERSION = "0.139.2"
  GO_VERSION   = "1.23.3"

2) GitHub Pages(GitHub Actions)

.github/workflows/hugo.yml 执行以下步骤:

  1. 检出仓库。
  2. 安装 Node。
  3. 下载 Hugo Extended。
  4. 安装 Go。
  5. 运行 npm run project-setup
  6. 安装依赖(npm install)。
  7. 构建(npm run build)。
  8. public 上传为 Pages 构件并部署。

3) Vercel

vercel-build.sh 在构建机器上运行:

  1. 安装 Go。
  2. 安装 Hugo Extended。
  3. 运行 npm run project-setup
  4. 安装依赖(npm install)。
  5. 构建(npm run build)。

所有平台最终都会:

  • 配置工具链(Hugo + Go + Node)。
  • 生成静态的 public/ 目录。

总结:构建时与运行时的边界

阶段发生了什么
构建时Hugo 将内容、模板、模块和资源编译为静态文件。同时执行压缩、指纹化和索引生成。
运行时CDN/静态服务器仅提供这些文件。浏览器运行少量前端 JS(例如搜索 UI)。不需要后端 API。

一旦你理解了这条边界,扩展站点就变得自然:在添加新功能时,首先问 “我们能在构建时生成数据吗?”,然后再问 “浏览器能消费这些数据吗?”

Back to Blog

相关文章

阅读更多 »