Featured image of post Hugo + Stack 主题建站与美化全记录

Hugo + Stack 主题建站与美化全记录

从自动部署到动画微交互,记录本站基于 Hugo 与 Stack 主题的完整建站与美化过程

前言

个人博客的意义,从来不只是"有个地方写东西"。它是一块自留地——你的域名、你的排版、你的交互——所有细节都由你掌控。选型阶段我选择了 Hugo + Stack 主题,理由是:

  • Hugo 构建极快,Markdown 写作体验流畅
  • Stack 主题设计克制,信息密度适中,暗色模式原生支持
  • 两者都有活跃社区,踩坑成本可控

本文按时间线整理从零搭建到细节美化的全过程,既是自我复盘,也希望能帮到同样折腾 Hugo 的朋友。


基础设施

GitHub Actions 自动部署

博客源码托管在 GitHub,每次推送到 main 分支自动构建并部署到 GitHub Pages。工作流文件位于 .github/workflows/deploy.yml

1
2
3
4
5
6
# 关键步骤:
# 1. actions/checkout@v4 + submodules: recursive(拉取 Stack 主题子模块)
# 2. peaceiris/actions-hugo@v3 + extended: true(Stack 需要 Hugo Extended)
# 3. actions/configure-pages@v5(自动检测自定义域名)
# 4. hugo --gc --minify
# 5. actions/deploy-pages@v4

几点注意:

  • fetch-depth: 0 确保 CI 环境能读取完整 Git 历史,配合 enableGitInfo(后文会提)自动获取文章最后修改时间
  • Hugo 子模块必须 recursive 拉取,否则主题缺失导致构建失败
  • 自定义域名 blog.areacloser.top 在仓库 Settings → Pages 中配置,configure-pages 会自动识别

主题文件覆盖策略

铁律:绝不原位修改主题文件。 所有定制通过同名文件覆盖实现——在项目根目录的 layouts/assets/i18n/ 下创建与主题路径一致的文件,Hugo 会自动优先读取。


中文适配

分类/标签页标题翻译

访问 /tags/gallery/ 这样的分类子路由时,页面顶部会显示全大写 “TAGS” 而非中文。问题有两层:

层面原因
模板layouts/list.html{{ .Parent.Title }} 返回 Hugo 默认的英文分类名
样式general.scss.section-title { text-transform: uppercase; } 把小写强制转大写

修改:

  1. i18n/zh.toml 新增:
1
2
3
[taxonomy]
    tags       = "标签"
    categories = "分类"
  1. layouts/list.html 覆写模板,将 {{ .Parent.Title }} 替换为:
1
2
{{ $taxonomyKey := print "taxonomy." .Parent.Data.Plural }}
{{ default .Parent.Title (T $taxonomyKey) }}

逻辑:拼接 i18n key(如 taxonomy.tags)查找中文,找不到则回退到原始标题。

  1. assets/scss/custom.scss 取消大写:
1
2
3
.section-title {
    text-transform: none;
}

页面英文路由

归档(关于)和友链页面原本的路由是 /关于//链接/。原因在于 hugo.tomlpermalinks.page = "/:slug/",而这两个页面的 front matter 没有指定 slug,Hugo 便从中文 title 自动推导。

修改:content/page/about/index.mdcontent/page/links/index.md 的 front matter 中添加:

1
slug: about   # 或 links

路由变为 /about//links/,标题仍保持中文不变。


交互与动画

所有动画集中在 assets/scss/custom.scss,遵循 0.6s ease 过渡 + scale(1.03) 放大 的统一风格。

主页文章卡片

1
2
3
4
5
6
7
.article-list article {
  transition: .6s ease;
}

.article-list article:hover {
  transform: scale(1.03, 1.03);
}

整卡可点击

默认只有标题和图片可点击跳转。用 ::after 伪元素拉伸标题链接覆盖图片以外的整个卡片区域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.article-list .article-details {
  position: relative;
}

.article-list .article-title a::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
}

// 悬停卡片时标题不变色(由卡片缩放提供反馈)
.article-list .article-title a:hover {
  opacity: 1;
}

// 分类标签浮到遮罩上方,保持可独立点击
.article-list .article-category a {
  position: relative;
  z-index: 1;
}

归档页与友链页

归档页改为两栏网格布局,友链页三栏,卡片悬停缩放:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 归档页两栏
@media (min-width: 1024px) {
  .article-list--compact {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.4rem;

    article {
      background: var(--card-background);
      border-radius: 16px;
      box-shadow: var(--shadow-l2);
    }
  }
}

.article-list--compact article {
  transition: .6s ease;
}
.article-list--compact article:hover {
  transform: scale(1.02, 1.02);
}

// 友链三栏
@media (min-width: 1024px) {
  .article-list--compact.links {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 1rem;
  }
}

归档的缩放为 1.02 而非 1.03,因为两栏间距较小,放大过多会显得拥挤。

侧边栏互动

搜索框、归档列表、标签云均有悬停缩放:

1
2
3
.search-form.widget:hover   { transform: scale(1.1);  }
.widget.archives .widget-archive--list:hover { transform: scale(1.05); }
.tagCloud .tagCloud-tags a:hover { transform: scale(1.1);  }

图片圆角统一

分类页顶部缩略图、归档/友链的卡片右侧图片,统一添加 8px 圆角:

1
2
3
4
5
.section-image img,
.article-list--compact .article-image img,
.article-list--tile .article-image img {
  border-radius: 8px;
}

首页欢迎横幅

仅在第一页显示({{ if eq $paginator.PageNumber 1 }}),分页不重复。

layouts/index.html 覆盖主题 home.html,在文章列表前插入:

1
2
3
<div class="welcome">
  <p>👋 你若三冬来,换我一城雪白 👋</p>
</div>

卡片式风格,悬停同样有缩放效果。文字可在模板中自由更换。


文章内信息增强

作者显示

在文章详情区(日期和阅读时长之间)添加作者信息,仅在 front matter 指定 author 时显示。

覆盖 layouts/_partials/article/components/details.html

1
2
3
4
{{ if $showAuthor }}
    {{ partial "helper/icon" "user" }}
    <span class="article-author">{{ $Page.Params.author }}</span>
{{ end }}

在任意文章的 front matter 中添加一行 author: Xalok 即可生效。

文章尾部信息

覆盖 layouts/_partials/article/components/footer.html,尾部从上到下依次为:

  • 标签
  • 👁 访问统计(Waline pageview,加载时显示"···“占位)
  • 最后更新于(仅在 .Lastmod ≠ .Date 时显示,图标为编辑笔)
  • © 授权信息

最近修改自动检测

hugo.toml 中启用:

1
enableGitInfo = true

Hugo 的 .Lastmod 解析优先级:front matter lastmod 字段 → Git 提交时间 → .Date(回退)。启用后,只要内容文件有新的 Git 提交,Lastmod 就会自动更新,无需手动维护 front matter 中的 lastmod


一言引用

通过 一言 API 获取游戏类句子(c=c),显示在分隔线和版权信息之间。新建 layouts/_partials/footer/hitokoto.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<section class="hitokoto">
    <span id="hitokoto-text">加载中...</span>
    <span id="hitokoto-from"></span>
</section>

<script>
fetch('https://v1.hitokoto.cn/?c=c&encode=json&charset=utf-8')
  .then(res => res.json())
  .then(data => {
    document.getElementById('hitokoto-text').textContent = data.hitokoto;
    document.getElementById('hitokoto-from').textContent = ' —— ' + data.from;
  });
</script>

CSS 中使用 color: var(--body-text-color) 确保亮/暗色主题下文字均可见,出处文字 opacity: 0.6 形成层次。

网站运行时长

在 footer 底部显示自建站以来的运行天数、时、分、秒,每秒刷新。非静态渲染,纯前端 JS 计算。

站点访问统计

通过 Umami 自建实例 API 获取全站 浏览量/访客/访问次数,挂载在 <span id="visit-info"> 上。params.toml 中预设了占位文字:

1
customText = '<span id="visit-info">加载中...</span>'

JS 异步获取数据后替换为实际数值,请求失败时显示零值。


Git 提交时间线

以上功能按以下顺序逐步迭代:

提交内容
a9418ef初始化 Hugo + Stack 主题
bf80f7dGitHub Actions 自动部署
8e70b25社交媒体图标与链接
c06b5f5中文 i18n、分类标题翻译、section 样式
5778bd1卡片悬停动画、归档/友链网格布局
bef1714首页欢迎横幅、一言、浏览量、作者显示
ad37e9d运行时长、图片圆角统一、Waline 浏览量
cfab843Umami 全站统计
7220d06enableGitInfo、内容更新
ed3bfc5整卡可点击

后记

从新建文件夹到一键部署不过是几条命令的事,真正花时间的是那些"不够好"的细节——分类标题为什么是全大写、页面路由为什么是中文、卡片为什么只能点标题。每一次追问和修复,都是让博客更接近心中模样的过程。

如果你也在用 Hugo + Stack,希望这篇文章能帮你少走些弯路。

通往一言的大门正在打开···
本站浏览统计加载中...
使用 Hugo 构建
主题 StackJimmy 设计
本站已平稳运行:0天