项目介绍

这个 blog 是一个典型的“静态站点”博客项目:内容用 Markdown 写,然后用 Hugo(Go 生态的静态站点生成器)在构建阶段把内容渲染为 HTML,并用 TailwindCSS + PostCSS 在构建阶段把样式打包成最终的 CSS。上线后不需要后端服务,任何静态托管(GitHub Pages / Netlify / Vercel / S3+CDN)都能跑。

这个项目还有两个比较关键的点:

  1. 使用了 Hugo Modules 来复用一批主题能力(如图片处理、PWA、SEO、组件等),因此 CI 里必须安装 Go(用于拉取/管理模块依赖)。
  2. 首页会额外生成一个 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:用于页面标题与 SEO
  • date:发布时间(影响排序与展示)
  • categories/tags:用于分类与标签页
  • image:封面图
  • author:作者名(会关联到 authors 页面)

模板会在文章页里读取这些字段,例如文章页模板 themes/hugoplate/layouts/posts/single.html 会展示封面、作者、分类、发布时间、正文和 TOC。

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

Hugo 支持把配置拆分到 config/ 目录,这个项目同时使用了:

  • 根配置:hugo.toml

    • theme = "hugoplate"
    • outputs.home 包含 JSON,用于生成 public/index.json
    • build.buildStats.enable = true,用于 Tailwind 的精确扫描(后面解释)
  • 语言配置:config/_default/languages.toml

    • 指定英文语言 contentDir = "content/english"
  • 站点参数:config/_default/params.toml

    • logo/favicon、主题色、公告条、Cookie 提示、侧边栏 widgets 等
  • Hugo Modules:config/_default/module.toml

    • 引入 github.com/gethugothemes/hugo-modules/imagespwaseo-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/jspizza_wasm_bg-*.wasm
    • baseof.html 里直接硬编码引入 /assets/index-*.css/assets/index-*.js

二、构建链路:从 Markdown 到最终 HTML/CSS/JS 发生了什么?

1)构建入口:package.json 脚本

package.json 里核心脚本是:

  • dev: hugo server
  • build: 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/images
  • github.com/gethugothemes/hugo-modules/pwa
  • github.com/gethugothemes/hugo-modules/seo-tools/basic-seo
  • github.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 = true
  • tailwind.config.js 的扫描输入是:content: ["./hugo_stats.json"]

Hugo 在渲染模板时会生成 hugo_stats.json,里面记录了实际输出 HTML 中使用到的 class/token。Tailwind 读取这个文件后,就可以非常准确地“只生成用到的工具类”,避免扫描整个模板/内容目录导致的误判或体积膨胀。

同时 hugo.toml 里还配置了 module mount:

  • hugo_stats.json mount 到 assets/watching/hugo_stats.json
  • 再配合 cache buster,让变更能触发 CSS 重建

这是 Hugoplate 系主题的一套成熟集成方式。

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

themes/hugoplate/layouts/partials/essentials/style.html 里,你能看到典型的 Hugo Pipes 流水线:

  1. 收集插件 CSS(来自 hugo.tomlparams.plugins.css
  2. 编译 scss/main.scss(需要 Hugo extended)
  3. resources.Concat 合并
  4. css.PostCSS 交给 PostCSS(Tailwind + autoprefixer)
  5. 生产环境下 minify | fingerprint | resources.PostProcess
  6. 输出 <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/,因此上线后浏览器可直接请求并执行。

运行时(用户打开页面)发生的事情是:

  1. 浏览器加载站点静态页面
  2. 加载搜索 UI 的 JS/CSS
  3. 搜索 UI 在浏览器端读取 index.json(以及可能的 wasm 资源)建立索引
  4. 用户按快捷键或在 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.2GO_VERSION=1.23.3

2)GitHub Pages(Actions)

.github/workflows/hugo.yml 的流程:

  1. checkout
  2. 安装 Node
  3. 下载 Hugo extended
  4. 安装 Go
  5. npm run project-setup
  6. npm install
  7. npm run build
  8. 上传 public 为 Pages artifact 并部署

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 博客项目的核心思想是:

  • 构建时:Hugo 把内容、模板、模块、资源管道全部编译成静态文件(并做压缩、指纹、索引生成)。
  • 运行时:CDN/静态服务器只负责分发文件;浏览器负责执行少量前端 JS(例如搜索 UI),不需要后端 API。

当你理解了这个边界,你就能非常自然地扩展功能:想要新增能力,优先思考“能不能在构建时把数据准备好”,然后“运行时用 JS 去消费”。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐