PaperMod 主题自带的 404 页面只有一句纯文字,很简陋;右侧侧边栏的「系列」卡片此前也只放了系列名链接,不显示系列里的其他文章。这次给博客补上自定义 404 页面,并把系列内导航做进右侧系列卡片里。本文记录实现过程。

需求

  • 自定义 404 页面:访客访问不存在的 URL 时,展示友好的页面(返回首页、搜索入口、推荐最近文章),而不是主题默认的一行字;
  • 系列内导航:在右侧「系列」卡片里列出该系列全部文章,当前篇高亮、标注位置(第 x 篇 / 共 y 篇),方便读者顺着系列读下去。

一、自定义 404 页面

PaperMod 的默认 404 就是一个居中的文字页。要自定义,直接在站点根目录新建 layouts/404.html 覆盖主题模板即可(主题用的是 layouts/404.html 这个路径)。

我的实现:

  • 大号艺术字 404,用 CSS 变量 var(--primary) / var(--secondary) 配色,自动适配明暗主题;
  • 两个按钮:返回首页、搜索文章;
  • 底部列最近 5 篇文章,访客不至于直接流失。

模板里「最近文章」用 Hugo 内置的 site.RegularPages 拿:

1
2
3
{{- range first 5 (where site.RegularPages "Section" "posts") }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
{{- end }}

踩坑:光有 404.html 不够,还要服务器配合

Hugo 构建会生成静态的 public/404.html,但服务器不会自动把它当作错误页返回。

  • Nginx 需要在站点配置里加 error_page 404 /404.html;;
  • Caddy 需要在 Caddyfile 里加:
1
2
3
4
handle_errors {
    rewrite * /404.html
    file_server
}

Caddy 和 Nginx 不一样,它不会自动找 404.html,必须显式用 handle_errors 处理。改完 systemctl reload caddy,再用 curl -sI https://你的域名/不存在的路径 验证是否返回 404 状态码。

二、系列内导航(右侧系列卡片)

「系列」卡片之前只放了系列名链接。现在把它升级成完整的系列导航:列出该系列全部文章,当前篇蓝色高亮、标"当前",并显示位置(第 x 篇 / 共 y 篇)。

模板逻辑放在 layouts/single.html 的系列卡片里(卡片在侧边栏最底部,位于分类、标签之后):

 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
31
32
{{- if .Params.series }}
<div class="tags-aside-box series-aside-box">
  <div class="tags-aside-title">系列</div>
  {{- $current := . }}
  {{- range $term := ($.GetTerms $series) }}
  {{- $posts := $term.Pages.ByDate }}
  {{- $total := len $posts }}
  {{- $curIdx := 0 }}
  {{- range $i, $p := $posts }}
    {{- if eq $p.Permalink $current.Permalink }}
      {{- $curIdx = add $i 1 }}
    {{- end }}
  {{- end }}
  <div class="series-nav">
    <div class="series-nav-head">
      <a class="series-nav-title" href="{{ $term.Permalink }}">{{ $term.LinkTitle }}</a>
      <span class="series-nav-count">{{ $curIdx }}/{{ $total }}</span>
    </div>
    <ol class="series-nav-list">
      {{- range $i, $p := $posts }}
      <li{{ if eq $p.Permalink $current.Permalink }} class="active"{{ end }}>
        <a href="{{ $p.Permalink }}" title="{{ $p.Title }}">
          <span class="series-nav-idx">{{ add $i 1 }}</span>{{ $p.Title }}
          {{- if eq $p.Permalink $current.Permalink }}<span class="series-nav-cur">当前</span>{{- end }}
        </a>
      </li>
      {{- end }}
    </ol>
  </div>
  {{- end }}
</div>
{{- end }}

几个要点:

  • .Pages.ByDate 按发布时间升序拿系列文章,保证列表顺序和系列阅读顺序一致;
  • 当前位置:遍历比对 Permalink,算出当前篇是第几篇(curIdx)和总数(total),显示成 3/5;
  • 一篇文章属于多个系列时,range $term 会为每个系列渲染一个导航块,块之间用虚线分隔;
  • 当前篇 <li> 加 class="active",用 CSS 高亮;
  • 最初还加了"上一篇 / 下一篇"快捷跳转,后来发现列表本身就能跳转、用不上,删掉了。

样式放在 assets/css/extended/series.css,PaperMod 会自动加载 assets/css/extended/ 下的所有 CSS。核心几点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 系列卡片:高度按视口比例(45vh),头部固定、列表内部滚动 */
.series-aside-box {
    height: 45vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.series-nav-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px dashed var(--border);
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

.series-nav-list li.active a {
    color: var(--accent-blue);
    font-weight: 600;
}

用 45vh(视口高度的 45%)定死卡片高度:矮屏自动收缩、高屏拉伸,卡片大小不再跟随文章数量跳动,侧边栏整体稳定。头部(系列名 + 位置)始终固定,列表超出部分内部滚动。

三、顺带:高亮统一成蓝色

这次一起把两处高亮改成了同一套蓝色,并定义了 CSS 变量,方便以后统一换色:

1
2
3
4
5
6
:root {
    --accent-blue: #1a73e8;      /* 亮色模式 */
}
[data-theme="dark"] {
    --accent-blue: #5b9df5;      /* 暗色模式,暗底上更清晰 */
}
  • 目录(TOC)当前章节高亮:.toc .active { color: var(--accent-blue); },保留原有放大加粗效果;
  • 系列导航当前篇:.series-nav-list li.active a { color: var(--accent-blue); };
  • 系列导航非当前篇:文字用主题主色 --primary(黑字)。主题默认的 --secondary 灰字在侧边栏里对比度偏低,看着费劲。

配色原则最后收敛成一句话:黑色正文 + 蓝色强调。全站四处强调色统一走 --accent-blue:目录当前章节、系列当前篇、胶囊悬停、链接悬停。

四、侧边栏卡片布局微调

系列导航落地后,对右侧三张卡片(分类 / 标签 / 系列)做了一轮统一:

  • 顺序:分类 → 标签 → 系列(系列放最底部);
  • 分类/标签:维持胶囊外观,但文字从主题默认的灰字改成黑字,悬停从"背景加深"改为"文字变蓝",和系列导航的 hover 反馈一致(期间试过把胶囊改成纯文字列表,和系列完全同构,但视觉效果不佳,改回了胶囊);
  • 系列卡片:height: 45vh 定死高度 + 列表内部滚动(见上文 CSS),头部固定、系列名和位置始终可见;
  • 删除:系列卡片最初做的"上一篇 / 下一篇"快捷跳转删掉了——列表本身就能跳转,这个功能冗余。

小结

改动都不复杂,思路都是「覆盖主题模板 / 追加扩展样式」:

  • 404 页面 = 新建 layouts/404.html 覆盖主题默认,配好服务器错误页配置;
  • 系列导航 = 纯模板逻辑 + assets/css/extended/ 下的扩展样式,不动主题源文件;
  • 颜色统一用 CSS 变量管理,明暗主题各配一个值;
  • 布局上,侧边栏卡片顺序、高度、滚动行为统一管理,避免各卡片风格割裂。

记录两个容易漏的点:一是 Caddy 不像 Nginx 会自动识别 404.html,必须加 handle_errors;二是系列列表用 45vh 定死卡片高度 + 内部滚动,避免长系列撑爆 sticky 侧边栏。