首页
/ Hugo Page.Next 与 Page.Prev 完全解析:Next/Prev 判定排序层级、底层实现与导航方向配置

Hugo Page.Next 与 Page.Prev 完全解析:Next/Prev 判定排序层级、底层实现与导航方向配置

2026-09-04 10:08:11作者:丁柯新Fawn

本文以 Hugo 官方文档中 Page 对象的 Next/Prev 方法说明为主线,系统讲清 Hugo 判定“上一篇/下一篇”的四级排序层级(weight、date、linkTitle、path),并结合仓库源码剖析惰性计算的实现机制、DefaultPageSort 的真实排序逻辑,以及通过 nextPrevSortOrder 等配置项反转导航方向的方法。读完本文,你能够在模板中安全地编写上下篇导航,并准确解释排序列表与 Next/Prev 顺序“看似不一致”的原因。

Hugo 如何判定 Next 和 Prev

Hugo 判定 _next__previous_ 页的方式是:将站点的全部常规页面RegularPages)按如下优先级排序,再根据当前页面在排序结果中的位置取前后邻居:

字段 优先级 排序方向
weight 1 descending
date 2 descending
linkTitle 3 descending
path 4 descending

需要注意一个文档中强调、但实践中极易踩坑的特性:用于判定 Next/Prev 的排序页面集合,独立于站点中其他页面集合。也就是说,你在列表模板里看到的排序(例如按 ByWeight 渲染的目录)与每个页面上 Next/Prev 的实际指向,可能并不一致。

官方文档给出的例子非常典型。假设内容结构如下:

content/
├── pages/
│   ├── _index.md
│   ├── page-1.md   <-- front matter: weight = 10
│   ├── page-2.md   <-- front matter: weight = 20
│   └── page-3.md   <-- front matter: weight = 30
└── _index.md

以及这两个模板:

{{ range .Pages.ByWeight }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}
{{ with .Prev }}
  <a href="{{ .RelPermalink }}">Previous</a>
{{ end }}

{{ with .Next }}
  <a href="{{ .RelPermalink }}">Next</a>
{{ end }}

访问 page-2 时,结果是:

  • Prev 方法指向 page-3(weight = 30)
  • Next 方法指向 page-1(weight = 10)

换言之,默认配置下 Next 指向排序层级中“更靠前”的页面,Prev 指向“更靠后”的页面,方向与大多数人直觉中“序号递增 = Next”恰好相反。要反转 nextprevious 的含义,有两种手段:

  1. 在项目配置中修改排序方向(nextPrevSortOrder / nextPrevInSectionSortOrder),详见后文;
  2. 使用 Pages 对象上的 NextPrev 方法,获得更灵活的导航控制。

Next/Prev 与其兄弟方法 NextInSection/PrevInSection 的区别在于页面集合范围:后者只在“当前 section 的常规页面”内计算邻居,而 Next/Prev 面向全站常规页面。

源码剖析:Next/Prev 的惰性计算链路

从源码结构看,Next/Prev 并不是在页面构建时立即算好的,而是一条惰性求值(lazy init)链路,分三步完成装配。

1. 站点级惰性任务:遍历排序后的常规页面

核心实现在 site.goprepareInits 中,Next/Prev 的计算被封装为一个可重置的一次性任务:

s.init.prevNext = hsync.OnceMoreFunc(func(ctx context.Context) error {
    regularPages := s.RegularPages()
    if s.conf.Page.NextPrevSortOrder == "asc" {
        regularPages = regularPages.Reverse()
    }
    for i, p := range regularPages {
        np, ok := p.(nextPrevProvider)
        if !ok {
            continue
        }
        pos := np.getNextPrev()
        if pos == nil {
            continue
        }
        pos.nextPage = nil
        pos.prevPage = nil

        if i > 0 {
            pos.nextPage = regularPages[i-1]
        }

        if i < len(regularPages)-1 {
            pos.prevPage = regularPages[i+1]
        }
    }
    return nil
})

这段代码精确印证了文档的语义:

  • 集合来自 s.RegularPages(),即全站常规页面,与任何菜单、分类、ByWeight 列表无关;
  • 当配置 [page] nextPrevSortOrder = 'asc' 时,先整体 Reverse() 排序结果;
  • 邻居映射是 next = pages[i-1]prev = pages[i+1]——这正是上一节例子中“page-2 的 Next 指向 weight 更小的 page-1”的底层原因。

NextInSection/PrevInSection 的对应任务在紧随其后的 site.go 中:它先枚举所有 section(含首页)节点,再对每个 section 的 RegularPages() 应用同样的邻居映射,并支持独立的 [page] nextPrevInSectionSortOrder 配置。

值得注意的工程细节:两个任务都实现了 Reset()(见 site.gositeInit.Reset)。这意味着在 hugo server 的重新构建流程中,旧邻居关系会被清空并按最新内容重新惰性计算,保证了 dev server 下 Next/Prev 的正确性。

2. 页面级的装配

每个常规页面在初始化公共提供者时,把自己的 nextPrev 结构挂接到上述站点级任务上,见 page.go

ps.posNextPrev = &nextPrev{init: ps.s.init.prevNext}
ps.posNextPrevSection = &nextPrev{init: ps.s.init.prevNextInSection}
ps.InSectionPositioner = newPagePositionInSection(ps.posNextPrevSection)
ps.Positioner = newPagePosition(ps.posNextPrev)

3. 模板调用时的求值点

模板中调用 {{ .Next }} 最终落到 page__position.go 的包装器上:

func (n *nextPrev) next() page.Page {
    n.init.Do(context.Background())
    return n.nextPage
}

func (p pagePosition) Next() page.Page {
    return p.next()
}

只有模板真正调用了 Next/Prev(或 InSection 变体),整站范围的邻居计算才会执行一次;若整站没有任何页面用到这些方法,这段计算就不会发生。pagePositionpagePositionInSection 是同一 nextPrev 内核的两个外观,分别服务于全站与 section 两种范围。

4. 排序本身:DefaultPageSort 的真实层级

文档表格给出的是 weight/date/linkTitle/path 四级层级,而实际执行排序的函数在 pages_sort.goDefaultPageSort,可以推断其完整判定顺序比文档表格更细致:

// DefaultPageSort is the default sort func for pages in Hugo:
// Order by Ordinal, Weight, Date, LinkTitle and then full file path.
DefaultPageSort = func(p1, p2 Page) bool {
    o1, o2 := getOrdinals(p1, p2)
    if o1 != o2 && o1 != -1 && o2 != -1 {
        return o1 < o2
    }
    // Weight0, as by the weight of the taxonomy entrie in the front matter.
    w01, w02 := getWeight0s(p1, p2)
    ...
    if p1.Weight() == p2.Weight() {
        if p1.Date().Unix() == p2.Date().Unix() {
            c := collatorStringCompare(func(p Page) string { return p.LinkTitle() }, p1, p2)
            if c == 0 {
                return compare.LessStrings(p1.PathInfo().Path(), p2.PathInfo().Path())
            }
            return c < 0
        }
        return p1.Date().Unix() > p2.Date().Unix()
    }
    if p2.Weight() == 0 {
        return true
    }
    if p1.Weight() == 0 {
        return false
    }
    return p1.Weight() < p2.Weight()
}

对照文档表格可以读出几点实现细节:

  • 文档中的四级(weight → date → linkTitle → path)确实是主干层级;代码中在这之前还有 Ordinal(菜单序号,来自 collections.Order)与 Weight0(分类条目前的 weight0)两个前置判定,它们只在页面显式设置了相应元数据时才生效,一般内容页不会触发;
  • weight 有一个特殊的零值规则:weight 为 0 的页面排在所有非零 weight 页面之后,非零 weight 之间按数值从小到大排列;
  • date 相同时按 linkTitle 做 locale 感知的字符串比较(collatorStringCompare),再相同时以归一化完整路径(p1.PathInfo().Path())兜底,保证排序结果的稳定性(Sort 使用 sort.Stable,见 pages_sort.go)。

理解了这条排序链,就能解释各种“意外”:例如某页未设 weight 而另一页设了 weight,前者会整体靠后;两页 weight 相同则退化为比 datedate 又相同则比 linkTitle

反转 Next/Prev 方向:nextPrevSortOrder 配置

文档指出反转导航方向可在“项目配置”中修改排序方向。对应的配置项与说明见 configuration/page.md,位于配置的 [page] 段:

配置项 类型 说明
nextPrevSortOrder string 调用 Page 对象的 Next/Prev 时判定上下篇的排序方向。取值 asc(升序)或 desc(降序),默认 desc
nextPrevInSectionSortOrder string 调用 NextInSection/PrevInSection 时判定“同 section 内”上下篇的排序方向。取值 asc/desc,默认 desc

配置示例(TOML):

[page]
  nextPrevInSectionSortOrder = 'asc'
  nextPrevSortOrder = 'asc'

该配置项在源码中的消费点就是上文 site.go 中的:

if s.conf.Page.NextPrevSortOrder == "asc" {
    regularPages = regularPages.Reverse()
}

即实现上并非更换比较函数,而是对默认(desc)排序结果做整体反转。官方文档同时明确了一条边界(见 configuration/page.md 的 NOTE):这两个设置不适用于 Pages 对象上的 Next/Prev 方法——它们只对 Page 对象的方法生效。

实战建议:防御式模板与替代方案

始终做存在性检查

Next/Prev 在集合端点(第一页/最后一页)返回空值,模板中应像文档示例那样用 {{ with }} 防御式地检查页面存在,避免渲染出指向 undefined 的死链:

{{ with .PrevInSection }}
  <a href="{{ .RelPermalink }}">Previous</a>
{{ end }}

{{ with .NextInSection }}
  <a href="{{ .RelPermalink }}">Next</a>
{{ end }}

让导航顺序与列表顺序一致

如果你希望“页面上看到的 Next/Prev”与“目录列表的视觉顺序”一致,有两种可控路径:

  1. 统一排序依据:给页面显式设置 weight(并保证非零),让 DefaultPageSort 的主键层级(weight 优先)主导顺序;
  2. 改用 Pages 方法:直接对目标集合调用 Pages.Next/Pages.Prev(其实现与测试见 pages_prev_next_test.go),因为该方法作用于“你显式传入的集合及其排序”,天然不受 Next/Prev 独立集合机制的影响,也支持先 ReverseByDate 等再取邻居,灵活度高于配置项翻转。

小结

  • 判定依据Next/Prev 基于全站 RegularPages 按 weight → date → linkTitle → path 的层级排序独立计算(NextInSection/PrevInSection 则限定在当前 section);
  • 默认方向Next 指向排序更靠前的页面(默认 weight 非零值更小者、更新者更靠前),Prev 指向更靠后者;
  • 实现机制:站点级 OnceMoreFunc 惰性任务 + 页面级 pagePosition 包装器,构建(含 dev server 重建)时可 Reset
  • 方向控制[page] nextPrevSortOrder / nextPrevInSectionSortOrderasc/desc,默认 desc),且不影响 Pages 对象方法;
  • 进阶控制:对 Pages 集合使用 Next/Prev 方法获得与具体列表完全一致的导航。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384