为什么选 Hugo
Hugo 是一个用 Go 写的静态网站生成器。它和 WordPress 这类动态博客的区别是:
| Hugo | WordPress | |
|---|---|---|
| 原理 | Markdown → 预生成 HTML 文件 | 每次访问查数据库 + 渲染 PHP |
| 服务器要求 | 纯静态文件,Nginx 直接 serve | 需要 PHP + MySQL |
| 内存占用 | 0(只有 Nginx 的几 MB) | MySQL 400M+ |
| 安全 | 没有后台、没有数据库,攻击面极窄 | PHP + 插件 = 常见攻击入口 |
| 写作方式 | VS Code 写 Markdown | 浏览器里写 |
| 速度 | 秒开 | 看服务器性能 |
| 版本控制 | Git | 数据库导出,不优雅 |
对于一台 2C2G 的轻量服务器,Hugo 是唯一正确的选择。
技术栈
云服务器:腾讯云轻量应用服务器 2C2G 3Mbps(Ubuntu 22.04)
静态生成:Hugo 0.146.5
主题:PaperMod(Gitee 镜像)
Web 服务器:Nginx
部署方式:Hugo 生成 HTML → Nginx 直接 serve
HTTPS:等域名备案下来后 Let's Encrypt 免费证书
博客目录结构
~/myblog/
├── config.yml ← 博客配置(核心文件)
├── content/ ← 所有文章和页面
│ ├── posts/
│ │ ├── 技术/
│ │ ├── 杂文/
│ │ ├── 美食/
│ │ └── 健身/
│ ├── about.md ← 关于页面
│ ├── archives.md ← 时间轴页面
│ ├── search.md ← 搜索页面
│ └── tags/_index.md ← 标签页面
├── layouts/ ← 自定义布局(覆盖主题默认)
│ ├── _default/
│ │ ├── archives.html ← 时间轴模板(来自 sulv-hugo-papermod)
│ │ └── search.html ← 搜索模板
│ └── partials/
│ └── index_profile.html ← 首页 profileMode 模板
├── assets/js/ ← 自定义 JS
│ ├── fastsearch.js
│ └── fuse.basic.min.js
├── static/img/ ← 静态资源(头像、二维码等)
├── themes/PaperMod/ ← Hugo 主题(不改动)
└── deploy.sh ← 构建+部署脚本
config.yml 关键配置
首页:profileMode
PaperMod 内置两种首页模式。homeInfoParams 是纯文字,profileMode 是头像 + 按钮布局。选择后者:
params:
profileMode:
enabled: true
title: "你好,我是不当人了 👋"
subtitle: "立志成为一个猫娘"
imageUrl: "img/avatar.jpg"
imageWidth: 150
imageHeight: 150
buttons:
- name: 👨🏻💻 技术
url: posts/技术
- name: 💭 杂文
url: posts/杂文
- name: 🍳 美食
url: posts/美食
- name: 🏋️ 健身
url: posts/健身
profileMode 的按钮是 PaperMod 内置功能,但 sulv-hugo-papermod 通过自定义 layouts/partials/index_profile.html 改良了按钮样式——我们在博客里也用了这份改良版。
搜索:Fuse.js + 标题摘要匹配
outputs:
home:
- HTML
- RSS
- JSON ← 必须加 JSON,Hugo 才会生成搜索索引 index.json
params:
fuseOpts:
isCaseSensitive: false
shouldSort: true
threshold: 0.3
minMatchCharLength: 2
keys: ["title", "summary"] ← 只匹配标题和摘要,不搜正文
搜索页面需要三个要素:
content/search.md— 声明layout: searchlayouts/_default/search.html— 搜索框 + 结果列表 + 加载 Fuse.jsassets/js/下两个 JS 文件 —fuse.basic.min.js(搜索库)+fastsearch.js(搜索逻辑)
时间轴
PaperMod 自带 archives 类型,但 sulv 覆写了 layouts/_default/archives.html,改为按 年份 → 月份 → 文章标题 三级排列的时间轴风格。
文章 front matter
每篇 .md 文件开头必须包含:
---
title: "文章标题"
date: 2026-07-29T14:00:00+08:00
draft: false ← false 才会发布
categories: ["技术"] ← 四分类之一
tags: ["Hugo", "博客"] ← 2-5 个标签
---
一键发布脚本
本地 PowerShell 跑 .\publish.ps1:
# 推送文章 → 推送布局 → 推送静态资源 → 远端 Hugo 构建 → Nginx 部署
scp -r "$blogLocal\content\*" "mycvm:/home/renbudang/myblog/content/"
# ...(推送 layouts、assets、static)
ssh mycvm "/home/renbudang/myblog/deploy.sh"
远端 deploy.sh:
cd ~/myblog
rm -rf /tmp/myblog-public/*
hugo -d /tmp/myblog-public
sudo rm -rf /var/www/html/*
sudo cp -r /tmp/myblog-public/* /var/www/html/
踩过的坑
-
PaperMod 版本兼容:Hugo 0.92(apt 默认)太老 → 从 GitHub 下载 0.146.5 的 deb,scp 上传手动装。PaperMod 最新版要求 Hugo 0.146+,恰好踩在边界上。
-
Gitee 镜像不带 tag:
git clone拉 PaperMod 主题时 GitHub 超时 → Gitee 镜像拉下来是最新版,没有旧 tag 可选。最后配对了 Hugo 0.146.5 + PaperMod(Gitee 镜像),刚好兼容。 -
搜索不出结果:漏了
outputs.home的JSON项 → Hugo 不生成index.json→ Fuse.js 没数据可搜。 -
搜索太泛:默认搜全文 → “你"这个字出现在所有文章里,搜什么都出一堆。改成只搜
["title", "summary"],体验好多了。 -
sudo 无密码:部署脚本里的
sudo rm/cp在非交互 SSH 模式下需要输密码 → 配了/etc/sudoers.d/renbudang-deploy免密。 -
config.yml 缩进:YAML 对缩进极其敏感,
profileMode的二级配置缩进不对直接报did not find expected key。
当前状态与后续计划
- Hugo + PaperMod 博客上线(IP 访问)
- 首页 profileMode 按钮布局
- 四分类(技术 / 杂文 / 美食 / 健身)
- 标签页(空白问题已修,需文章有 tags)
- 搜索页(标题+摘要匹配,Fuse.js 模糊搜索)
- 时间轴(年月三级排列)
- 一键发布脚本
- 域名备案通过 → 绑域名 + HTTPS
- 评论系统(Waline / Giscus)
- 图片 CDN
- 自动部署(GitHub Actions)
这篇记录本身也在博客上——吃了自己做的饭。