从零构建 Hugo + Tailwind 技术博客:以 INFINI Labs Blog 为例
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.tomltheme = "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-*.css、static/assets/index-*.js 和 pizza_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?
有两个配置实现了这一点:
-
Hugo 构建统计 – 在
hugo.toml中启用[build.buildStats] enable = trueHugo 在渲染模板时会输出
hugo_stats.json,记录生成的 HTML 中使用的每个类/标记。 -
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 & Modules →
themes/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 的流程:
- 收集插件 CSS – 来自
hugo.toml中的params.plugins.css。 - 编译
scss/main.scss– 需要 Hugo Extended。 resources.Concat– 合并文件。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。该模板遍历站点页面,将 title、URL、tags、category、description 以及纯文本内容等字段打包成一个 JSON 数组。这个文件是静态站点客户端搜索的极佳数据源。
2) 搜索 UI 从哪里来?
themes/hugoplate/layouts/_default/baseof.html 硬编码了对 static/assets/ 中资源的导入。这些文件会被 Hugo 复制到 public/assets/,因此浏览器在部署后可以直接请求它们。
运行时流程
- 浏览器加载静态页面。
- 加载搜索 UI 的 CSS/JS。
- UI 获取
index.json(以及可能的 WASM 资源),在浏览器中构建索引。 - 搜索在本地执行——无需后端 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 执行以下步骤:
- 检出仓库。
- 安装 Node。
- 下载 Hugo Extended。
- 安装 Go。
- 运行
npm run project-setup。 - 安装依赖(
npm install)。 - 构建(
npm run build)。 - 将
public上传为 Pages 构件并部署。
3) Vercel
vercel-build.sh 在构建机器上运行:
- 安装 Go。
- 安装 Hugo Extended。
- 运行
npm run project-setup。 - 安装依赖(
npm install)。 - 构建(
npm run build)。
所有平台最终都会:
- 配置工具链(Hugo + Go + Node)。
- 生成静态的
public/目录。
总结:构建时与运行时的边界
| 阶段 | 发生了什么 |
|---|---|
| 构建时 | Hugo 将内容、模板、模块和资源编译为静态文件。同时执行压缩、指纹化和索引生成。 |
| 运行时 | CDN/静态服务器仅提供这些文件。浏览器运行少量前端 JS(例如搜索 UI)。不需要后端 API。 |
一旦你理解了这条边界,扩展站点就变得自然:在添加新功能时,首先问 “我们能在构建时生成数据吗?”,然后再问 “浏览器能消费这些数据吗?”