我的博客主题是如何定制的

主题用的 hugo-cactus-dark, 一个暗色主题, 干净, 没什么花哨的东西. 完全够用.

但原版主题只覆盖了基础功能. 我需要双语切换, Giscus 评论, 页面统计, 结构化数据, 这些原版都没有. 所以直接 fork 了一份, 在 layouts/ 里覆盖掉需要改的模板.

覆盖了哪些

layouts/ 目录下的模板优先级高于主题里的同名文件. 我只覆盖需要改的部分, 不需要动的就留给主题.

这个东西开发的时候有个烦人的点: Hugo 对模板有缓存. 你改了 layouts/ 下的文件, 有时候浏览器还是用旧版本. 不是 Hugo 的锅, 是浏览器缓存了 HTML. 清一下或者开无痕模式就好.

head.html

改动最大的一个. 原版只输出了基本的 meta 和样式引用. 这里加了一堆东西:

<!-- 搜索引擎验证 -->
<meta name="360-site-verification" content="xxx" />
<meta name="sogou_site_verification" content="xxx" />

<!-- 双语 hreflang -->
{{ if .IsTranslated }}
{{ range .Translations }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}

<!-- Open Graph -->
<meta property="og:locale" content="{{ if eq .Lang "zh" }}zh_CN{{ else }}en_US{{ end }}">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:image" content="{{ .Params.image | absURL }}">
<meta property="og:description" content="{{ .Summary | truncate 200 }}">

<!-- Canonical -->
<link rel="canonical" href="{{ .Permalink }}">

<!-- 结构化数据 -->
{{ if .IsPage }}
<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "{{ .Title }}",
    "datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
    "dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
    "author": { "@type": "Person", "name": "{{ .Site.Params.author }}" }
}
</script>
{{ end }}

为什么要加 Open Graph? 因为文章分享到社交媒体时, 预览卡片需要这些 meta. 没有的话就只显示一个光秃秃的链接.

结构化数据是为了 Google 搜索的 rich result. 文章在搜索结果里会显示发布时间, 作者等信息.

导航栏加了语言切换器. 逻辑是这样的: 如果当前文章有翻译版本, 直接跳转到翻译版本. 如果没有, 就跳到另一种语言的首页.

<li class="lang-switcher has-dropdown">
  <a href="#">{{ .Site.Language.LanguageName }}</a>
  <ul class="dropdown">
    {{ range $.Site.Home.AllTranslations }}
    <li>
      <a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
    </li>
    {{ end }}
    {{ if .IsTranslated }}
    {{ range .Translations }}
    <li>
      <a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
    </li>
    {{ end }}
    {{ end }}
  </ul>
</li>

语言切换器显示当前语言, 下拉菜单列出所有可切换的语言. 如果有翻译版本就直接跳文章, 没有就跳首页.

comments.html

原版主题支持 Disqus 和 Valine. 我都换掉了, 用的 Giscus.

Giscus 基于 GitHub Discussions. 访客用 GitHub 账号登录后可以评论. 不需要第三方服务, 数据存在仓库的 Discussions 里.

第一次配的时候有个坑: data-repo-iddata-category-id 不是自己猜的, 要去 Giscus 官网 (https://giscus.app) 输入仓库名, 它会自动生成. 我一开始以为 repo-id 就是仓库名, 配了半天没反应.

具体怎么配置, 流程怎么跑的, 单独写了一篇, 看这里: 评论系统怎么接入博客的.

single.html

文章页模板. 加了文章链接输出, 方便别人引用. 标签改用 relLangURL 生成链接, 确保指向当前语言版本的标签页.

<div class="content" itemprop="articleBody">
  {{ .Content }}
  <h2>{{ i18n "articleLink" }}</h2>
  <a href='{{ .RelPermalink }}'>{{ .RelPermalink }}</a>
</div>

js.html

页面底部的脚本加载. 加了这些:

  • highlight.js: 代码高亮, 只在文章页面加载
  • Busuanzi: 页面浏览量统计, 显示在文章底部
  • Service Worker 注册: 只在生产环境 (time-friend.com) 下注册
  • Google Analytics: 统计访问数据

Service Worker 注册的代码:

if ('serviceWorker' in navigator && location.hostname === 'time-friend.com') {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js');
  });
}

限制 hostname 是为了防止开发环境也注册 SW, 避免缓存干扰.

其他覆盖

  • footer.html: 用 i18n 输出版权信息
  • post-list.html: 首页文章列表, 过滤掉 about 页面, 置顶最新文章
  • latest-posts.html: 文章底部的"最新文章"推荐, 排除当前文章
  • tagcloud.html: 标签云, 根据文章数量动态调整字体大小
  • terms.html: 标签/分类列表页, 带文章数量

自定义 CSS

static/css/custom.css 里加了一些样式:

/* 中文字体优先 */
body {
  font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
}

/* 语言切换下拉菜单 */
.has-dropdown:hover .dropdown {
  display: block;
}

主要就搞了中文字体栈和下拉菜单样式. 没改太多, 原版主题的样式本身就够用.

性能考虑

覆盖模板对性能的影响是零. Hugo 在构建时就把模板编译成静态 HTML 了, 覆盖不覆盖, 运行时没有区别.

唯一需要留意的是脚本加载位置. 所有 JS 都放在 js.html 里, 在页面底部加载, 不阻塞渲染. highlight.js 只在文章页面加载, 首页和其他列表页不加载.

文章链接:

/zh/archive/blog-architecture-theme-customization/

# 相关文章推荐