Published 2026.04.16 21:02

Hugo 是什么:从静态网站生成器到我的博客引擎

Hugo 是目前最快的静态网站生成器。这篇文章从背景、原理、代码架构三个层面拆解 Hugo,并结合我自己的博客项目讲清楚它在整套发布系统里到底扮演什么角色。

3672 字 9 分钟阅读 Claude Opus4.6
A clean and minimalist workspace featuring a laptop on white desk.

封面来源: www.kaboompics.com · Pexels License · 原图链接

为什么写这篇

之前有朋友问我博客是用什么搭的,我说"Hugo"。对方的反应是:“Hugo 是什么?跟 WordPress 有什么区别?”

这个问题看起来简单,但真要讲清楚,得从"静态网站"这个概念说起,再一路讲到 Hugo 的设计思路、项目结构、模板机制,以及它在我自己这套"Obsidian 写作 → Hugo 编译 → 服务器部署"链路里扮演的具体角色。

这篇文章就是试图把这些事情讲明白。不是 Hugo 教程,不会手把手教你装 Hugo、建项目,而是帮你建立一个清晰的认知框架:Hugo 是怎么工作的,它为什么被设计成这样,以及它适合什么样的场景。

从一个最基本的问题开始:网页是怎么被"生成"的

你在浏览器里打开一个网页,看到标题、正文、图片、导航栏——这些东西最终都是 HTML。浏览器只认 HTML、CSS 和 JavaScript 这三种语言。所有你在网上看到的页面,不管它背后用了什么技术栈,最终送到浏览器里的都是这三样东西。

问题在于:这些 HTML 是什么时候生成的?

这个"什么时候"决定了网站的整个技术架构。大体上有两种路线。

路线一:每次有人访问时现场生成

WordPress 就是这个路线的代表。用户在浏览器输入网址,请求到达服务器,服务器上跑着 PHP,PHP 连数据库,查出文章内容,套上模板,拼成一页 HTML,再发回给浏览器。

每一次页面访问都经历这个过程。访问量大的时候,服务器需要反复做同样的事情。为了应对这个问题,WordPress 生态里发展出了各种缓存插件、CDN 方案、数据库优化手段。

这种方式的优势是灵活——你可以做评论、做用户登录、做后台管理,因为有服务器在"活着"。但代价是:你得维护一个数据库、一个 PHP 运行环境、一套复杂的运维体系,而且安全面很大——WordPress 的漏洞和被黑事件在互联网上数不胜数。

路线二:提前生成好,直接发文件

这就是"静态网站"的路线。

你在本地写好内容,运行一个命令,工具帮你把所有页面一次性编译成 HTML 文件。编译完之后,你把这堆 HTML 文件往服务器上一放,服务器的工作就变成了最简单的事情:有人访问哪个路径,就把对应的 HTML 文件发过去。

没有数据库,没有 PHP,没有服务端逻辑。服务器只做一件事:发文件。

这种方式的劣势也很明显:没有"后台",没有评论系统,没有用户登录。但对于个人博客来说,这些东西本来就不一定需要。而它的优势是巨大的:快、安全、几乎零运维

Hugo 就是第二种路线里的工具。

Hugo 的来历

Hugo 诞生于 2013 年,作者是 Steve Francia——这个名字你可能不熟悉,但他后来去了 Google 领导 Go 语言的产品方向。Hugo 是用 Go 语言写的,这一点直接决定了它最显著的特征:

Hugo 之前已经有不少静态网站生成器了,最出名的是 Jekyll。Jekyll 是 GitHub Pages 默认支持的工具,Ruby 写的,在技术博客圈子里用了很多年。但 Jekyll 有一个让人头疼的问题——一旦文章数量上去,构建速度就会变得非常慢。几百篇文章的博客,构建一次可能要几十秒甚至几分钟。

Hugo 用 Go 重写了整个生成逻辑,把构建速度做到了毫秒级。我的博客有 9 篇文章,构建时间是 146 毫秒。即使文章增长到几百篇,Hugo 的构建时间通常也不会超过 1 秒。这不是优化出来的,而是 Go 语言本身的性能优势和 Hugo 在架构层面做的设计决策共同决定的。

截至目前,Hugo 是 GitHub 上星标数最高的静态网站生成器之一。它不需要 Node.js,不需要 Ruby,不需要 Python——下载一个二进制文件就能用。这种"零依赖"的特性在实际使用中特别舒服:不用管版本冲突、不用配环境变量、不用等 npm install 跑完。

Hugo 的核心原理:一句话版本

如果要用一句话概括 Hugo 做的事情:

读取 Markdown 内容 + 读取 HTML 模板 → 输出完整的静态网站。

就这么简单。它是一个编译器——输入是你的文章和模板,输出是一整个网站。

但这个"简单"背后藏着一套设计得相当精致的机制。下面展开来讲。

Hugo 项目长什么样

一个 Hugo 项目的目录结构通常是这样的:

capootech-blog/
├── hugo.toml          ← 站点配置文件
├── content/           ← 你写的文章(Markdown)
│   └── posts/         ← 博客文章
├── layouts/           ← 页面模板(HTML + Go 模板语法)
│   ├── _default/      ← 通用模板(基础布局、文章页、列表页)
│   ├── partials/      ← 可复用的页面片段
│   └── index.html     ← 首页模板
├── static/            ← 静态资源(CSS、JS、图片)
│   ├── css/
│   ├── js/
│   └── images/
├── archetypes/        ← 新建文章时的模板
├── data/              ← 数据文件(JSON/YAML/TOML)
├── i18n/              ← 多语言翻译
└── public/            ← Hugo 编译输出(这就是最终的网站)

每个目录都有明确的职责。Hugo 的设计哲学是约定优于配置——你把文件放对位置,Hugo 就知道该怎么处理它,不需要额外声明。

content:内容就是 Markdown

博客文章放在 content/posts/ 里,每篇文章就是一个 .md 文件。文件开头有一段 YAML 格式的元数据,叫做 front matter:

---
title: "我和 AI 一起,把个人博客和发布流程搭了起来"
date: 2026-04-06 22:33:00
slug: "building-my-blog-with-ai-and-obsidian"
tags:
  - "博客"
  - "Hugo"
  - "AI"
summary: "从动机到实现,记录整套博客系统的搭建过程。"
---

正文内容从这里开始……

Hugo 读取这个文件时,会把 front matter 解析为结构化数据,把正文部分用 Goldmark(一个 Go 实现的 Markdown 解析器)转换成 HTML。模板里通过 .Title.Date.Content 这些变量就能引用到对应的值。

slug 决定了文章的 URL 路径。比如 slug: "building-my-blog-with-ai-and-obsidian" 意味着这篇文章的网址是 blog.capootech.com/posts/building-my-blog-with-ai-and-obsidian/

layouts:模板决定页面长什么样

Hugo 用 Go 的 html/template 作为模板引擎。模板文件是 HTML 里嵌入了用双花括号包裹的逻辑片段。

最底层是 baseof.html,它定义了所有页面共享的 HTML 骨架:

<!doctype html>
<html>
  <head>
    {{ partial "head.html" . }}
  </head>
  <body>
    {{ partial "header.html" . }}
    <main>
      {{ block "main" . }}{{ end }}
    </main>
    {{ partial "footer.html" . }}
  </body>
</html>

{{ block "main" . }}{{ end }} 是一个"插槽"——不同类型的页面会往这个插槽里填不同的内容。文章详情页(single.html)、文章列表页(list.html)、首页(index.html)各自定义自己的 main 块。

{{ partial "header.html" . }} 表示引入一个可复用的页面片段。我的博客里有这些 partial:

  • header.html — 导航栏
  • footer.html — 页脚
  • post-card.html — 文章卡片组件
  • post-cover.html — 封面图处理
  • post-heatmap.html — 首页的写作热力图
  • reading-stats.html — 中文字数统计和阅读时间

这种"基础骨架 + 插槽 + 可复用片段"的结构,和前端组件化的思路其实是一回事——只不过 Hugo 是在编译时完成的,而不是在浏览器里。

static:原样复制的资源

static/ 目录里的东西会被 Hugo 原样复制到输出目录,不做任何处理。CSS 文件、JavaScript 文件、图片都放在这里。

我的博客里有两个 JS 文件:enhancements.js 负责阅读进度条、返回顶部、代码复制按钮、图片灯箱这些交互增强;search.js 负责全文搜索。样式则集中在一个 site.css 里。

hugo.toml:全局配置

Hugo 的配置文件支持 TOML、YAML 或 JSON 格式。我用的是 TOML。里面声明了站点标题、语言、导航菜单、分页大小、分类方式(taxonomy)这些全局参数。

一个有意思的细节是 [outputs] 配置:

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

这意味着首页会同时输出三种格式:HTML 给人看,RSS 给订阅器用,JSON 给站内搜索用。一个配置项同时解决了三个需求。

编译过程:Hugo 到底做了什么

当你在终端运行 hugo 命令时,Hugo 会在几百毫秒内完成以下事情:

  1. 读取配置文件 — 解析 hugo.toml,确定站点参数
  2. 扫描 content 目录 — 找到所有 Markdown 文件,解析 front matter 和正文
  3. 构建分类索引 — 根据文章的 tags 构建分类页面(比如 /tags/AI/
  4. 套用模板 — 对每篇文章用 single.html 渲染,对列表页用 list.html 渲染,首页用 index.html 渲染
  5. 处理 partial — 递归展开所有 partial 引用
  6. 复制 static — 把 static/ 里的资源原样搬到输出目录
  7. 写入 public — 把所有生成的 HTML、CSS、JS、图片写入 public/ 目录

完成之后,public/ 就是一个可以直接部署的完整网站。你可以把它丢到任何能提供静态文件服务的地方:nginx、Vercel、Netlify、GitHub Pages、甚至一个 S3 桶。

Hugo 在我的项目里扮演什么角色

理解了 Hugo 是什么之后,再来看它在我整套博客系统里的位置就清楚了。

我的博客系统不是单独一个工具,而是一条写作→编译→部署的流水线:

┌──────────┐     同步脚本      ┌──────────┐     scp + ssh       ┌──────────┐
│ Obsidian │ ──────────────→  │   Hugo   │ ──────────────────→ │ 阿里云   │
│ 写文章    │   Python 处理    │  编译网站  │   上传源码+构建      │ 跑网站    │
└──────────┘   frontmatter    └──────────┘                     └──────────┘
                图片/封面

Obsidian 是我的编辑器。我在这里写文章,管理草稿,组织知识。文章就是普通的 Markdown 文件,住在 Obsidian 的 vault 里。

Hugo 是编译器。它不负责"内容从哪来",也不负责"网站怎么部署"——它只做一件事:把 Markdown 变成网页。模板里定义了首页长什么样、文章页长什么样、标签页长什么样,Hugo 按照这些模板把内容编译成 HTML。

阿里云服务器 是运行环境。服务器上装了 Hugo 和 nginx。Hugo 编译出 public/ 目录,nginx 把 public/ 作为网站根目录对外提供访问。

连接这三层的是两个关键脚本:

  • Python 同步脚本sync_obsidian_posts.py)—— 从 Obsidian 的文章目录读取 Markdown 文件,处理 front matter、图片引用、封面图,写入 Hugo 的 content/posts/
  • PowerShell 发布脚本publish-obsidian-posts.ps1)—— 先调 Python 脚本完成同步,然后用 scp 把 Hugo 源码上传到服务器,最后 ssh 到服务器上运行 hugo 重新编译

而我在 Obsidian 里做的博客管理插件(capootech-blog-publisher),本质上就是给这两个脚本套了一层 GUI。在 Obsidian 里点一下"发布",底层就是调 PowerShell 脚本走完上面这条链路。

所以 Hugo 在我的系统里的角色非常明确:

  • 不管内容创作——那是 Obsidian 的事
  • 不管部署运维——那是服务器和脚本的事
  • 只管一件事:把 Markdown 编译成好看的网页

这种分层让每一层都可以独立演进。我可以换掉 Obsidian(虽然不太可能),可以换掉部署方式(从阿里云换到 Vercel),但 Hugo 这一层几乎不用动——因为它唯一的输入就是 Markdown 和模板,唯一的输出就是 HTML。

我为什么选 Hugo 而不是别的

最后说说选择。静态网站生成器有很多:Jekyll、Hexo、Astro、Next.js(也可以做静态导出)、Gatsby。我最后用 Hugo 主要是这几个原因:

。前面说了,146 毫秒编译整个站。这意味着我改一行 CSS 然后保存,浏览器几乎同时就刷新了。对于需要反复调样式的人来说,这个速度差距是体感上能感受到的。

零依赖。不需要 Node.js、不需要 npm、不需要 package.json。一个二进制文件就是全部。在 WSL 里 hugo server 一敲就能预览,不用等任何东西安装。

模板够用。Hugo 的模板语言不是最优雅的——Go template 的语法确实有些"反直觉",第一次写 {{ range .Pages }} 的时候会觉得怪怪的。但它功能完备,能实现我需要的所有页面逻辑。而且因为是编译时执行的,所以不存在运行时性能问题。

适合我的场景。我的博客不需要评论系统、不需要用户登录、不需要后台管理面板。它就是一个"把文章好好展示出来"的网站。Hugo 正好是做这件事的最佳工具——不多也不少。


回到最开始那个问题:“Hugo 是什么?”

Hugo 是一个编译器。你给它 Markdown 和模板,它还你一个完整的网站。它不是 CMS,不是框架,不是平台——它就是一个把内容变成网页的工具。快、简单、可靠。

对于我来说,Hugo 是整套写作系统里最"安静"的一层。它不需要关注、不需要维护、不需要升级。它就在那里,忠实地做着编译的工作,让我可以把注意力放在真正重要的事情上——写作本身。