☰
Hugo 全局函数(page 与 site)完全指南:在任何模板上下文中访问页面与站点数据
2026/10/10 18:33:48 网站建设 项目流程
  • 开发工具
  • 前端
  • CLI

【免费下载链接】hugo

The world’s fastest framework for building websites.

项目地址:https://gitcode.com/gh_mirrors/hu/hugo
点击查看免费下载

Hugo 模板系统除了把Page、Site对象作为数据上下文(.)传入模板外,还提供了一组全局函数(Global functions),让你在任意上下文——包括嵌套的 partial、render hook 中——都能稳定地拿到“当前页面”与“当前站点”。本篇指南以 Hugo 文档 functions/global 这一章节为核心,结合仓库源码(tpl/page/init.go、tpl/site/init.go、resources/page/site.go等)讲透page与site两个全局函数的用法、等价写法、底层实现,以及两个最容易踩的坑(顶层上下文与模板缓存),读完即可在真实站点模板中安全、正确地使用它们。

一、什么是全局函数:page与site

在 Hugo 文档体系中,functions/global是一个独立的函数分类目录,其章节描述只有一句话:"Use these global functions to access page and site data."(使用这些全局函数访问页面与站点数据)。它下属两个实际函数:

函数返回类型作用文档正文
pagepage.Page返回当前页面的Page对象,可从任何上下文访问page 文档
sitepage.siteWrapper返回当前站点的Site对象,可从任何上下文访问site 文档

从源码角度看,两者都是通过 Hugo 的“模板函数命名空间”机制注册的全局函数:tpl/page/init.go中const name = "page",tpl/site/init.go中const name = "site",均通过internal.AddTemplateFuncsNamespace(f)注册。因此你在模板的任何位置都可以直接书写{{ page.XXX }}或{{ site.XXX }},无需像普通函数那样带包名前缀(如partials.IncludeCached)。

二、page全局函数:随时拿到当前页面

1. 基本用法:三种等价写法

当模板顶层上下文本身就是Page对象时,以下三种写法完全等价(见 page 文档 的 Usage 一节):

{{ .Params.foo }} {{ .Page.Params.foo }} {{ page.Params.foo }}

Hugo 在渲染顶层模板(如baseof.html、single.html、home.html)时几乎总会把Page作为数据上下文传入,因此.通常可以直接访问当前页面。唯一的例外是多主机(multihost)sitemap 模板——它没有 Page 作为上下文。

当你在 partial、render hook 等深层嵌套中无法(或不方便)通过.访问Page时,page函数就派上用场:

{{ page.Params.foo }}

这正是全局函数的价值:从任意上下文、任意模板中获取当前页面对象。

2. 底层实现:从渲染上下文(context)中取 Page

page函数并不是凭空猜测“当前页面”,而是从 Gocontext.Context中显式取出。看 tpl/page/init.go 的实现:

f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func(ctx context.Context, args ...any) (any, error) { v := tpl.Context.Page.Get(ctx) if v == nil { // The multilingual sitemap does not have a page as its context. return nil, nil } return v.(page.Page), nil }, } return ns }

这条调用链是:

  1. 渲染页面前,Hugo 把当前Page注入渲染上下文。核心代码在 tpl/tplimpl/templatestore.go 的PrepareTopLevelRenderCtx:
func (t *TemplateStore) PrepareTopLevelRenderCtx(ctx context.Context, p page.Page) context.Context { if p != nil { ctx = tpl.Context.Page.Set(ctx, p) } ... }
  1. 真正渲染页面时调用它:在 hugolib/site.go 的renderAndWritePage中:
ctx := s.TemplateStore.PrepareTopLevelRenderCtx(context.Background(), p)

(别名页面的渲染同样走此流程,见 hugolib/alias.go。)

  1. 上下文键定义在 tpl/template.go:contextKeyPage对应Context.Page这个ContextDispatcher。而page全局函数正是通过tpl.Context.Page.Get(ctx)读取它。

从源码结构看,这套机制保证了:只要当前模板渲染确实发生在某个页面渲染流程内,page就一定能取到该页面的Page对象;若上下文里没有注入 Page(如多语言 sitemap 场景),函数返回nil, nil而不是报错。

3. 常见坑一:注意顶层上下文

page函数访问的是传入顶层模板的那个 Page 对象,而不是你在range中迭代到的页面。文档中给出了非常典型的一例。

内容结构如下:

content/ ├── posts/ │ ├── post-1.md │ ├── post-2.md │ └── post-3.md └── _index.md <-- title is "My Home Page"

首页模板(home)中这样写:

{{ range site.Sections }} {{ range .Pages }} {{ page.Title }} {{ end }} {{ end }}

渲染结果是:

My Home Page My Home Page My Home Page

原因就在上面的实现细节:page读取的是渲染上下文里注入的那个 Page(这里是 home 模板的_index.md,标题 "My Home Page"),而range只是改变了点(.)指向的迭代值,并不会改变 context 中的 Page。在循环体内想访问“当前迭代页面”,应使用.(或.Title、.LinkTitle),而不是page。

4. 常见坑二:注意模板缓存

文档明确警告(见 page 文档 的 Note 与 Examples 部分):不要在以下场景使用page全局函数:

  • Shortcodes(短代码)
  • 被 shortcode 调用的 partial 模板
  • 被partialCached缓存起来的 partial 模板

原因:Hugo 会缓存渲染过的 shortcode。如果一个页面的内容在两个或更多模板中被渲染,而 shortcode 内部使用了page函数,那么缓存下来的 shortcode 输出可能是错误的。

文档给出了具体场景:section 模板中调用.Summary(详见 page.Summary 方法文档):

{{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ .Summary }} {{ end }}

调用Summary时,Hugo 会渲染页面内容(包括其中的 shortcode)。此时若 shortcode 内用了page,它取到的是section 页面的 Page 对象,而非内容页面的。更麻烦的是:由于 Hugo 并发渲染、渲染顺序不可控,如果 section 页面先于内容页面渲染,缓存下来的 shortcode 结果就会是错误的。这一点同样可以从前文源码验证——page的值完全取决于渲染上下文中当前注入的是哪个 Page,而缓存机制会把第一次渲染的结果复用给后续所有调用。

[!NOTE] 在这三种场景中,请改用显式传入的上下文(.或$)来访问页面对象,避免依赖page全局函数。

三、site全局函数:随时拿到当前站点

1. 基本用法:与.Site、$.Site的对比

site函数返回Site对象,与上下文无关(见 site 文档):

{{ site.Params.foo }}

当Site对象已在上下文中时,你同样可以用以下两种传统写法:

<!-- current context --> {{ .Site.Params.foo }} <!-- template context --> {{ $.Site.Params.foo }}

文档给出的建议很直接:无论Site对象是否在上下文中,都请统一使用全局site函数,这样能简化模板——你不必再关心当前点(.)指向的是 Page、Site 还是某个迭代值,也不必在 partial 里纠结用.Site还是$.Site。

2. 底层实现:包装后的 Site 接口

看 tpl/site/init.go:

f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { s := page.WrapSite(d.Site) ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func(cctx context.Context, args ...any) (any, error) { return s, nil }, } // We just add the Site as the namespace here. No method mappings. return ns }

site函数直接返回page.WrapSite(d.Site)的包装对象,类型即为page.siteWrapper。包装器定义在 resources/page/site.go:它持有一个内部Site并逐方法委托(delegate),同时通过Key()返回语言标识,并实现了额外的identity相关内部接口。

Site接口(resources/page/site.go)暴露了模板中最常用的一批方法,例如:

  • 站点基础信息:Title()、BaseURL()、Copyright()、Language()、Languages()、Lastmod()
  • 页面集合:Pages()、RegularPages()、AllPages()、Sections()、Home()、GetPage(ref...)
  • 配置与数据:Params()、Param(key)、Config()、Data()、Menus()、Taxonomies()、MainSections()
  • 多站点支持:Sites()、Current()、IsDefault()、LanguagePrefix()、ServerPort()
  • 构建信息:Hugo()(返回HugoInfo)

因此模板中可以放心书写诸如{{ site.Title }}、{{ site.BaseURL }}、{{ site.Menus.main }}、{{ site.GetPage "/about" }}等表达式。

3. 与page的差异:无缓存陷阱

与page不同,site函数的取值与渲染上下文、渲染顺序无关——它在函数命名空间初始化时一次性包装了d.Site,之后每次调用都返回同一个包装对象。所以site没有“顶层上下文”和“缓存污染”这两类问题,可以在 shortcode、partial(含partialCached)、render hook 中放心使用。这也是为什么官方文档建议模板中统一采用site。

四、实战选型:page、site 与.、$该怎么选

综合两个函数的语义与源码实现,给出如下选型建议:

场景推荐写法理由
顶层模板(上下文是 Page),访问当前页面{{ .Params.foo }}直接、直观
顶层模板,访问站点信息{{ site.Params.foo }}与上下文解耦,官方推荐
partial / render hook,访问当前页面{{ page.Params.foo }}上下文不可靠时兜底
partial / render hook,访问站点信息{{ site.Title }}任何位置都可用
shortcode 内部访问“调用它的页面”传入参数(如{{ .Inner }}所在页面经GetPage/参数传递)page在此场景可能命中缓存错误
range循环内访问“迭代到的页面”{{ .Title }}page始终指向顶层页面

核心原则一句话:site全局函数可无条件使用;page全局函数只应在“确认不会被缓存复用”的模板层级使用,且它总是代表顶层模板的当前页面,而不是循环中的迭代对象。

五、可验证的源码与文档路径

  • 全局函数章节入口:docs/content/en/functions/global/_index.md
  • page函数文档与示例:docs/content/en/functions/global/page.md
  • site函数文档:docs/content/en/functions/global/site.md
  • page函数实现(从 context 取 Page):tpl/page/init.go
  • site函数实现(WrapSite 包装):tpl/site/init.go
  • Site接口与siteWrapper委托实现:resources/page/site.go
  • 渲染上下文与Context.Page调度器:tpl/template.go
  • 渲染前注入 Page 的PrepareTopLevelRenderCtx:tpl/tplimpl/templatestore.go
  • 页面渲染调用链(renderAndWritePage):hugolib/site.go
  • partialCached函数说明:docs/content/en/functions/partials/IncludeCached.md
  • Summary方法说明:docs/content/en/methods/page/Summary.md

结语

page与site是 Hugo 模板体系中最常用的两个全局函数,前者让你在深层嵌套中依然能定位“当前页面”,后者让你在任何位置拿到“当前站点”。理解它们的关键在于认清实现本质:page读取的是渲染上下文中注入的 Page(因此有顶层上下文与缓存两大陷阱),而site返回的是初始化时包装好的站点对象(因此无条件安全)。记住本文的选型表格与两条官方警告,你就能写出既简洁又稳健的 Hugo 模板。

  • 开发工具
  • 前端
  • CLI

【免费下载链接】hugo

The world’s fastest framework for building websites.

项目地址:https://gitcode.com/gh_mirrors/hu/hugo
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询