我的博客主题是如何定制的
主题用的 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. 文章在搜索结果里会显示发布时间, 作者等信息.
nav.html
导航栏加了语言切换器. 逻辑是这样的: 如果当前文章有翻译版本, 直接跳转到翻译版本. 如果没有, 就跳到另一种语言的首页.
<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-id 和 data-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 只在文章页面加载, 首页和其他列表页不加载.