为什么选 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"]   ← 只匹配标题和摘要,不搜正文

搜索页面需要三个要素:

  1. content/search.md — 声明 layout: search
  2. layouts/_default/search.html — 搜索框 + 结果列表 + 加载 Fuse.js
  3. assets/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/

踩过的坑

  1. PaperMod 版本兼容:Hugo 0.92(apt 默认)太老 → 从 GitHub 下载 0.146.5 的 deb,scp 上传手动装。PaperMod 最新版要求 Hugo 0.146+,恰好踩在边界上。

  2. Gitee 镜像不带 taggit clone 拉 PaperMod 主题时 GitHub 超时 → Gitee 镜像拉下来是最新版,没有旧 tag 可选。最后配对了 Hugo 0.146.5 + PaperMod(Gitee 镜像),刚好兼容。

  3. 搜索不出结果:漏了 outputs.homeJSON 项 → Hugo 不生成 index.json → Fuse.js 没数据可搜。

  4. 搜索太泛:默认搜全文 → “你"这个字出现在所有文章里,搜什么都出一堆。改成只搜 ["title", "summary"],体验好多了。

  5. sudo 无密码:部署脚本里的 sudo rm/cp 在非交互 SSH 模式下需要输密码 → 配了 /etc/sudoers.d/renbudang-deploy 免密。

  6. config.yml 缩进:YAML 对缩进极其敏感,profileMode 的二级配置缩进不对直接报 did not find expected key

当前状态与后续计划

  • Hugo + PaperMod 博客上线(IP 访问)
  • 首页 profileMode 按钮布局
  • 四分类(技术 / 杂文 / 美食 / 健身)
  • 标签页(空白问题已修,需文章有 tags)
  • 搜索页(标题+摘要匹配,Fuse.js 模糊搜索)
  • 时间轴(年月三级排列)
  • 一键发布脚本
  • 域名备案通过 → 绑域名 + HTTPS
  • 评论系统(Waline / Giscus)
  • 图片 CDN
  • 自动部署(GitHub Actions)

这篇记录本身也在博客上——吃了自己做的饭。