Featured image of post Hugo 博客维护踩坑记:主题迁移、暗色模式与 SCSS 优先级

Hugo 博客维护踩坑记:主题迁移、暗色模式与 SCSS 优先级

记录将 Hugo 博客主题从直接嵌入改为 git submodule 的过程,以及在此过程中遇到的 Hugo v0.152+ 废弃 API、SCSS 导入优先级等问题的排查与解决。

为什么要用 Git Submodule

最初搭建博客时,我把 Hugo Theme Stack 的所有文件直接复制、提交到了博客仓库里。主题文件成百上千,和自己的文章、配置搅在一起。这带来几个问题:

  1. 无法追踪上游更新:上游修了 bug、加了新功能,我完全不知道,知道了也没法一键合并。唯一的选择是手动下载新版本的主题,重新覆盖,然后祈祷不要把自己改过的地方冲掉。

  2. 提交历史被污染:几百个主题文件的增删改和自己的博客文章混在同一个 git log 里,一眼看去完全分不清哪些是自己的创作、哪些是第三方代码。

  3. 仓库臃肿:主题文件占了大头,真正属于我的内容反而被淹没了。

Git Submodule 是什么

Git Submodule 允许一个 Git 仓库(主仓库)引用另一个 Git 仓库(子模块)的特定 commit,而不是把它的文件复制过来。

可以这样理解:不用 submodule 时,你等于把别人的房子拆了,砖头一块块搬到自己院子里重新盖;用了 submodule,你只是在自家院子里插了一块路牌,写着「去那边那个房子」。

关键特性:

  • 主仓库只记录一个指针(子模块的 repo URL + commit hash),不存储子模块的实际文件
  • git clone 主仓库后,子模块目录是空的,需要 git submodule update --init 才会把子模块拉下来
  • 子模块有自己独立的 .git 目录、独立的 commit 历史、独立的 remote
  • 子模块的版本是精确锁定的——它指向一个具体 commit,不会自动更新

Submodule 的实际优势

直接嵌入主题Git Submodule
查看改动git diff 里几百个主题文件混在一起git diff 只显示指针 commit 的变化
更新主题手动下载、覆盖、手动合并cd themes/xxx && git pull
上游修复不知道、合并痛苦git fetch origin && git merge
回滚混在博客历史中难以定位主仓库 git checkout 一个旧 commit 即可恢复当时引用的主题版本
定制直接改文件通过 Hugo 的覆盖机制(layouts/assets/static/)在不碰主题源码的前提下定制

背景

基于以上原因,我决定把主题改成 git submodule 管理。但过程并不顺利,踩了四个坑,逐一记录。


第一步:把嵌入的主题改成 submodule + fork

思路很清晰:

  1. Fork 一份 CaiJimmy/hugo-theme-stack 到自己的 GitHub
  2. 在 fork 里改好暗色模式的颜色(背景 #000000,卡片 #121317
  3. 博客主仓库 git rm -r themes/hugo-theme-stack
  4. git submodule add 指向上一步的 fork

这一步还算顺利。具体改动见 commit 52b389e


坑一:submodule 指向的是 fork 的 v4.x,和博客用的 v3.9.x 不兼容

我的 fork 克隆的是上游 main 分支(v4.x),但博客一直用的是 v3.9.x。两个版本的目录结构、CSS 变量、模板写法都不一样。

现象:切 submodule 后,暗色模式的颜色没了,整体布局也有些差异。

最初的想法:切回 fork 的 v3.9.2 tag。但 fork 是直接从上游 fork 的 main,在 GitHub 上没法直接指定 fork 时的分支。

最终方案:submodule 直接指向上游 CaiJimmy/hugo-theme-stack 的 v3.9.2 tag,不要再维护一个 fork 了。主题定制统一通过 Hugo 的覆盖机制实现,不动主题源码。

1
2
3
4
# 切换 submodule
git checkout -- .gitmodules
git submodule add --force https://github.com/CaiJimmy/hugo-theme-stack.git themes/hugo-theme-stack
cd themes/hugo-theme-stack && git checkout v3.9.2 && cd ../..

--force 是因为 .git/modules/themes/ 里还缓存着旧的 git 目录。不加 --force 会报 A git directory for 'themes/hugo-theme-stack' is found locally


坑二:Hugo v0.152.2 废弃 API,直接编译失败

GitHub Actions 用的 Hugo 版本是 0.152.2。在这个版本中:

  • resources.ToCSS → 已移除,需用 css.Sass
  • .Site.LastChange → 已移除,需用 .Site.Lastmod

但 hugo-theme-stack v3.9.2 仍然在用这些废弃 API。Workflow 直接报错退出。

解决方案:Hugo 布局文件的优先级是「项目 layouts/ > 主题 layouts/」。因此直接照搬主题中的三个文件到项目目录,修改一行即可:

文件修改
layouts/partials/head/style.htmlresources.ToCSScss.Sass
layouts/partials/comments/provider/disqusjs.htmlresources.ToCSScss.Sass
layouts/partials/head/opengraph/provider/base.html.Site.LastChange.Site.Lastmod
1
2
-{{ $style := $sass | resources.ToCSS | minify | resources.Fingerprint "sha256" }}
+{{ $style := $sass | css.Sass | minify | resources.Fingerprint "sha256" }}
1
2
-{{- if not .Site.LastChange.IsZero -}}
+{{- if not .Site.Lastmod.IsZero -}}

坑三:assets/scss/custom.scss 暗色模式覆盖不生效(重点)

编译通过了,但暗色模式还是默认的 #303030 / #424242

排查过程:我在项目根目录创建了 assets/scss/custom.scss,写入:

1
2
3
4
[data-scheme="dark"] {
    --body-background: #000000;
    --card-background: #121317;
}

主题的 assets/scss/style.scss 末尾有 @import "custom.scss",理论上 Hugo 应该优先加载项目的 assets/scss/custom.scss

但实际情况是:主题的 assets/scss/ 目录下也有一个 custom.scss,虽然它只是一个空注释:

1
/* Place your custom SCSS in HUGO_SITE_FOLDER/assets/scss/custom.scss */

Hugo 的 SCSS @import 解析器在遇到 @import "custom.scss" 时,会先在当前文件所在目录查找(即主题的 assets/scss/)。它找到了主题的那个空文件,加载了它,就停了。项目里的同名文件根本没被看到。

踩坑记录

  1. ❌ 尝试覆盖 style.scss,把 @import "custom.scss" 改成 @import "site-custom.scss",然后创建 site-custom.scss。这个方案理论上可行,但实际操作中因为之前的编译产物一致(都是默认色),GitHub Actions 的部署环节 peaceiris/actions-gh-pages 检测到 nothing to commit,跳过了部署,导致没有生效。

  2. ❌ 尝试直接修改 submodule 内的主题文件 —— 本地能跑,但 CI 重新 clone 子模块后又还原了。

  3. 最终方案:直接覆盖 assets/scss/variables.scss

Hugo 的资源查找(resources.Get)逻辑和 SCSS @import 解析逻辑不同。resources.Get 会优先使用项目中的文件而不是主题中的。所以只要创建一个包含暗色模式的 assets/scss/variables.scss,Hugo 就会用它代替主题的版本。

1
2
3
4
5
6
# 拷贝主题的 variables.scss 到项目
cp themes/hugo-theme-stack/assets/scss/variables.scss assets/scss/variables.scss

# 修改暗色模式部分
# --body-background: #303030 → #000000
# --card-background: #424242   → #121317

这个方案不仅解决了 @import 优先级问题,而且改动集中在一个文件里,不依赖 custom.scss 的加载顺序。


坑四:部署日志显示成功,但 .github.io 仓库没更新

Workflow 日志里 Deploy Web 步骤显示:

1
2
3
4
git commit -m auto deploy ...
On branch main
nothing to commit, working tree clean
[INFO] skip commit

原因peaceiris/actions-gh-pages@v4 默认行为会在目标仓库克隆一份,git rm -r * 清空,再 cp 新的构建产物进去,然后 commit。如果新的产物和上次完全一致(文件树 hash 相同),git 会认为没有变化,跳过 commit。

这发生在上一个坑的排查中 — 我们的 style.scss 覆盖方案编译出来的结果和默认主题一样(都是 #303030 / #424242),所以部署被跳过了。

只要修复真正生效(编译出不同的 CSS),部署就会重新触发。


总结:Hugo 主题定制的优先级规则

场景Hugo 的行为推荐做法
布局覆盖 (layouts/)项目的 layouts/ 优先于主题直接复制主题文件到项目,修改
SCSS @import先从导入文件的目录查找不要和主题的同名文件冲突,避免用通用的名字如 custom.scss
resources.Get + SCSS项目的 assets/ 优先于主题覆盖 variables.scss 是最可靠的
静态资源 (static/)项目的 static/ 覆盖主题同名文件static/img/avatar.png

关键教训:在 Hugo 中给主题改样式,特别是改 CSS 变量,最稳妥的方式永远是 直接在项目中放置 assets/scss/variables.scss,而不是依赖 custom.scss@import 机制。


附:本次改动总览

1
2
3
4
5
6
7
ff7709a  switch theme submodule to upstream v3.9.2
d25696a  move particles.js to head/custom.html
54a32e0  add dark mode overrides via custom.scss    ← 没生效
60fd094  fix deprecated Hugo APIs (ToCSS, LastChange)
a03c80b  override style.scss → site-custom.scss     ← 编译成功但部署被跳过
84e15c4  trigger rebuild                            ← 同上
6c366ed  fix: override variables.scss directly      ← 最终生效 ✅
Licensed under CC BY-NC-SA 4.0
written by LyricalWander