- 开发工具
- 前端
- CLI
【免费下载链接】hugo
The world’s fastest framework for building websites.
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."(使用这些全局函数访问页面与站点数据)。它下属两个实际函数:
| 函数 | 返回类型 | 作用 | 文档正文 |
|---|---|---|---|
page | page.Page | 返回当前页面的Page对象,可从任何上下文访问 | page 文档 |
site | page.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 }这条调用链是:
- 渲染页面前,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) } ... }- 真正渲染页面时调用它:在 hugolib/site.go 的
renderAndWritePage中:
ctx := s.TemplateStore.PrepareTopLevelRenderCtx(context.Background(), p)(别名页面的渲染同样走此流程,见 hugolib/alias.go。)
- 上下文键定义在 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.mdsite函数文档:docs/content/en/functions/global/site.mdpage函数实现(从 context 取 Page):tpl/page/init.gosite函数实现(WrapSite 包装):tpl/site/init.goSite接口与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.mdSummary方法说明:docs/content/en/methods/page/Summary.md
结语
page与site是 Hugo 模板体系中最常用的两个全局函数,前者让你在深层嵌套中依然能定位“当前页面”,后者让你在任何位置拿到“当前站点”。理解它们的关键在于认清实现本质:page读取的是渲染上下文中注入的 Page(因此有顶层上下文与缓存两大陷阱),而site返回的是初始化时包装好的站点对象(因此无条件安全)。记住本文的选型表格与两条官方警告,你就能写出既简洁又稳健的 Hugo 模板。
- 开发工具
- 前端
- CLI
【免费下载链接】hugo
The world’s fastest framework for building websites.
相关推荐
43秒无缝长视频:ComfyUI-WanVideoWrapper单卡跑通Context Window长视频生成
43秒无缝长视频:ComfyUI WanVideoWrapper单卡跑通Context Window长视频生成 单卡显存不到5GB,一口气生成1025帧连贯视频
人工智能大模型媒体生成如何让 AI 操作真实已登录浏览器:BrowserSkill 10 分钟上手指南
如何让 AI 操作真实已登录浏览器:BrowserSkill 10 分钟上手指南 BrowserSkill 是一款让 AI 智能体直接操作你真实、已登录的 Ch
人工智能AI 应用AI 技能浏览器控制dsh-pluginHugo 模板上下文(Context)完全指南:理解点号 `.` 与模板数据流
Hugo 模板上下文(Context)完全指南:理解点号 . 与模板数据流 Hugo 的模板系统建立在 Go 标准库 text/template 与 html/
开发工具前端CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考