为什么写这篇
之前有朋友问我博客是用什么搭的,我说"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 会在几百毫秒内完成以下事情:
- 读取配置文件 — 解析
hugo.toml,确定站点参数 - 扫描 content 目录 — 找到所有 Markdown 文件,解析 front matter 和正文
- 构建分类索引 — 根据文章的 tags 构建分类页面(比如
/tags/AI/) - 套用模板 — 对每篇文章用
single.html渲染,对列表页用list.html渲染,首页用index.html渲染 - 处理 partial — 递归展开所有 partial 引用
- 复制 static — 把
static/里的资源原样搬到输出目录 - 写入 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 是整套写作系统里最"安静"的一层。它不需要关注、不需要维护、不需要升级。它就在那里,忠实地做着编译的工作,让我可以把注意力放在真正重要的事情上——写作本身。