从零搭建一个 Hugo + Tailwind 的技术博客:以 INFINI Labs Blog 为例
这个blog内容用 Markdown 写,然后用Hugo(Go 生态的静态站点生成器)在构建阶段把内容渲染为 HTML,并用在构建阶段把样式打包成最终的 CSS。上线后不需要后端服务,任何静态托管(GitHub Pages / Netlify / Vercel / S3+CDN)都能跑。使用了来复用一批主题能力(如图片处理、PWA、SEO、组件等),因此 CI 里必须安装Go(用于拉取/管理模块依
项目介绍
这个 blog 是一个典型的“静态站点”博客项目:内容用 Markdown 写,然后用 Hugo(Go 生态的静态站点生成器)在构建阶段把内容渲染为 HTML,并用 TailwindCSS + PostCSS 在构建阶段把样式打包成最终的 CSS。上线后不需要后端服务,任何静态托管(GitHub Pages / Netlify / Vercel / S3+CDN)都能跑。
这个项目还有两个比较关键的点:
- 使用了 Hugo Modules 来复用一批主题能力(如图片处理、PWA、SEO、组件等),因此 CI 里必须安装 Go(用于拉取/管理模块依赖)。
- 首页会额外生成一个
index.json搜索索引,并在页面里注入了一套已经构建好的搜索 UI(/static/assets/*),让站点具备前端离线/本地搜索的能力。
下面从“怎么搭建”和“运行原理”两部分拆开讲。
一、目录结构:内容、主题、配置、资源分别放哪里?
你可以把这个仓库理解成四层:
1)内容层:content/
content/english/posts/:博客文章(Markdown + front matter)content/english/authors/:作者页(Markdown + front matter)
每篇文章最上面都会有 front matter(YAML/TOML/JSON 均可,这里用 YAML),包括:
title/description:用于页面标题与 SEOdate:发布时间(影响排序与展示)categories/tags:用于分类与标签页image:封面图author:作者名(会关联到 authors 页面)
模板会在文章页里读取这些字段,例如文章页模板 themes/hugoplate/layouts/posts/single.html 会展示封面、作者、分类、发布时间、正文和 TOC。
2)配置层:hugo.toml + config/_default/*
Hugo 支持把配置拆分到 config/ 目录,这个项目同时使用了:
-
根配置:
hugo.tomltheme = "hugoplate"outputs.home包含JSON,用于生成public/index.jsonbuild.buildStats.enable = true,用于 Tailwind 的精确扫描(后面解释)
-
语言配置:
config/_default/languages.toml- 指定英文语言
contentDir = "content/english"
- 指定英文语言
-
站点参数:
config/_default/params.tomllogo/favicon、主题色、公告条、Cookie 提示、侧边栏 widgets 等
-
Hugo Modules:
config/_default/module.toml- 引入
github.com/gethugothemes/hugo-modules/images、pwa、seo-tools等模块(这是本项目“Go 依赖”的来源)
- 引入
3)主题层:themes/hugoplate/
主题负责所有页面的 HTML 结构与 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,因为它来自 Hugo Modules(config/_default/module.toml 引入的 hugo-modules/images 模块)。这是“主题 + 模块化能力复用”的典型用法。
4)资源层:assets/ 与 static/
这两个目录在 Hugo 里含义不同:
-
assets/:走 Hugo Pipes 的资源(会参与编译、指纹、压缩等)- 本项目把图片放在
assets/images/...,构建后会输出到public/images/... - 样式/脚本源码也在
themes/hugoplate/assets/*下,通过 Hugo Pipes 打包
- 本项目把图片放在
-
static/:原样拷贝到public/的资源- 本项目有
static/assets/index-*.css/js和pizza_wasm_bg-*.wasm baseof.html里直接硬编码引入/assets/index-*.css与/assets/index-*.js
- 本项目有
二、构建链路:从 Markdown 到最终 HTML/CSS/JS 发生了什么?
1)构建入口:package.json 脚本
package.json 里核心脚本是:
dev:hugo serverbuild:hugo --gc --minify --templateMetrics --templateMetricsHints --forceSyncStatic
因此构建主导者是 Hugo;Node 主要是给 PostCSS/Tailwind 用。
同时 CI/托管平台会在 build 前执行:
project-setup:node ./scripts/projectSetup.js
这个脚本来源于 hugoplate 模板工程,用于把 exampleSite/ 的结构“搬运”成真正的站点结构;当前仓库已经有 themes/ 目录,因此脚本会直接输出 Project already setup(即对现有项目无副作用)。
2)Hugo Modules:为什么 CI 要装 Go?
因为 config/_default/module.toml 使用了 Hugo Modules,例如:
github.com/gethugothemes/hugo-modules/imagesgithub.com/gethugothemes/hugo-modules/pwagithub.com/gethugothemes/hugo-modules/seo-tools/basic-seogithub.com/hugomods/mermaid- 等等
这些模块会在构建时拉取到本地模块缓存中,并参与模板渲染。仓库里的 go.mod 则用于锁定模块版本(可视为 Hugo Modules 的依赖清单)。
所以:Hugo 负责渲染,但 Go 负责下载/解析模块依赖,这就是 CI 里同时安装 Hugo 和 Go 的原因(见 netlify.toml 与 .github/workflows/hugo.yml)。
3)Tailwind 是如何“只生成用到的 CSS”的?
关键在两个配置:
hugo.toml启用了 build stats:[build.buildStats] enable = truetailwind.config.js的扫描输入是:content: ["./hugo_stats.json"]
Hugo 在渲染模板时会生成 hugo_stats.json,里面记录了实际输出 HTML 中使用到的 class/token。Tailwind 读取这个文件后,就可以非常准确地“只生成用到的工具类”,避免扫描整个模板/内容目录导致的误判或体积膨胀。
同时 hugo.toml 里还配置了 module mount:
- 把
hugo_stats.jsonmount 到assets/watching/hugo_stats.json - 再配合 cache buster,让变更能触发 CSS 重建
这是 Hugoplate 系主题的一套成熟集成方式。
4)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="...style.<hash>.css" integrity="...">
JS 的处理类似,在 themes/hugoplate/layouts/partials/essentials/script.html。
这解释了两点“运行原理”:
- 站点上线后只有静态文件(HTML/CSS/JS/图片),没有运行时编译。
- 指纹化(fingerprint)让浏览器与 CDN 缓存更稳:文件内容变化必然导致 URL 变化。
三、搜索能力:index.json + /static/assets/* 是怎么工作的?
1)索引从哪里来?
hugo.toml 指定 outputs.home = ["HTML", "RSS", "WebAppManifest", "JSON"],并把 JSON 格式的 baseName = "index",所以构建后会生成:
public/index.json
它的生成模板在:
themes/hugoplate/layouts/index.json
模板逻辑是遍历站点页面,把文章标题、链接、tags、category、description、正文纯文本等打包成一个数组 JSON。
这非常适合作为“前端搜索”的数据源(尤其是纯静态站点)。
2)搜索 UI 从哪里来?
themes/hugoplate/layouts/_default/baseof.html 里硬编码引入:
<link rel="stylesheet" href="/assets/index-*.css"><script type="module" src="/assets/index-*.js"></script>
这些文件位于仓库的 static/assets/,会被 Hugo 原样拷贝到 public/assets/,因此上线后浏览器可直接请求并执行。
运行时(用户打开页面)发生的事情是:
- 浏览器加载站点静态页面
- 加载搜索 UI 的 JS/CSS
- 搜索 UI 在浏览器端读取
index.json(以及可能的 wasm 资源)建立索引 - 用户按快捷键或在 UI 中输入,搜索在本地完成,无需后端接口
这就是静态站点“增强交互能力”的经典方式:构建时生成数据,运行时用前端组件消费。
四、如何从零搭建一个类似项目(最小可行步骤)
下面按“复刻本仓库思路”的路线给一个实操步骤,你可以用作新项目的骨架。
1)准备环境
- Hugo extended(本项目要求
>= 0.139.2,见config/_default/module.toml) - Go(本项目 CI 用
1.23.3,用于 Hugo Modules) - Node(用于 PostCSS/Tailwind;本项目在 CI 用 Node 20,但本地常见 LTS 也能跑)
2)安装依赖并启动开发
本仓库声明的包管理器是 pnpm,但脚本同样兼容 npm/yarn。
pnpm install
pnpm dev
# 或者: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")。
五、部署与 CI/CD:Netlify / GitHub Pages / Vercel 分别怎么跑?
这个仓库同时兼容多种托管方式:
1)Netlify
netlify.toml:
- build command:
yarn project-setup; yarn build - publish:
public - env:
HUGO_VERSION=0.139.2、GO_VERSION=1.23.3
2)GitHub Pages(Actions)
.github/workflows/hugo.yml 的流程:
- checkout
- 安装 Node
- 下载 Hugo extended
- 安装 Go
npm run project-setupnpm installnpm run build- 上传
public为 Pages artifact 并部署
3)Vercel
vercel-build.sh 会在构建机上:
- 安装 Go
- 安装 Hugo extended
npm run project-setupnpm installnpm run build
不同平台本质上都在做同一件事:把构建环境凑齐(Hugo+Go+Node),最终产出 public/。
小结:理解“构建时”和“运行时”的边界
这种 Hugo 博客项目的核心思想是:
- 构建时:Hugo 把内容、模板、模块、资源管道全部编译成静态文件(并做压缩、指纹、索引生成)。
- 运行时:CDN/静态服务器只负责分发文件;浏览器负责执行少量前端 JS(例如搜索 UI),不需要后端 API。
当你理解了这个边界,你就能非常自然地扩展功能:想要新增能力,优先思考“能不能在构建时把数据准备好”,然后“运行时用 JS 去消费”。
更多推荐



所有评论(0)