必须用loadhtmlglob("templates/*/")才能递归加载子目录模板,否则{{template "header" .}}会报"undefined"错误;嵌套依赖命名模板(define/template名称严格一致),继承需用{{block}}而非{{template}},且所有模板须属同一template实例。

模板嵌套必须用 LoadHTMLGlob("templates/**/*"),不能只写 templates/*
很多新手一上来就写 router.LoadHTMLGlob("templates/*"),结果子目录里的 header.html 或 base.html 根本加载不进内存,后续 {{template "header" .}} 直接报 template: "header" is undefined 错误。
原因很简单:* 只匹配一级目录(比如 templates/base.html),但不进 templates/layout/ 或 templates/components/ 这类子目录;而 **/* 才能递归穿透所有层级。
- ✅ 正确写法:
router.LoadHTMLGlob("templates/**/*") - ❌ 错误写法:
router.LoadHTMLGlob("templates/*")、router.LoadHTMLGlob("templates/*.html") - ⚠️ 注意:路径是相对于当前工作目录(
os.Getwd()),不是go.mod所在目录
{{define}} 和 {{template}} 必须成对出现在不同文件中,且名字严格一致
嵌套依赖的是 Go 模板的命名模板机制,不是文件名。比如你在 templates/layout/base.html 里写了 {{define "sidebar"}},那在 templates/blog/list.html 里就必须用 {{template "sidebar" .}} ——引号里的字符串必须一字不差,大小写、空格、下划线都不能错。
常见错误现象:template: "sidebar" not defined,但你明明写了 define "Sidebar",只是首字母大写了。
- 定义处:
{{define "header"}}<h1>{{.Title}}</h1>{{end}} - 调用处:
{{template "header" .}}(不能是"Header"或'header') - 作用域限制:被
{{template}}引入的内容,仍共享父模板传入的.数据,无需额外传参
继承模板要用 {{block}} + {{end}},而不是单纯 {{template}}
真正实现“继承”语义(即子模板覆盖父模板某区块)靠的是 {{block "name" .}}...{{end}}。如果只用 {{template}},那就是静态包含,子模板无法重写内容。
比如 base.html 定义了可被覆盖的主体区域:
{{block "content" .}}<p>默认内容</p>{{end}}
而 list.html 可以这样覆盖它:
{{define "content"}}
- {{range .Items}}
- {{.Name}} {{end}}
-
{{block}}的第一个参数是模板名,第二个是数据上下文(通常传.) - 子模板里
{{define "content"}}不会自动生效,必须确保它被LoadHTMLGlob加载且与base.html在同一模板集合中 - 如果子模板没定义对应
block名,就会回退到base.html里写的默认内容
自定义函数在嵌套模板里直接可用,但需在 LoadHTMLGlob 后注册
你写了一个 formatDate 函数,希望在 base.html 和所有子模板里都能用 {{.CreatedAt | formatDate}},那就不能在每个 ParseFiles 时单独注册 —— Gin 的模板引擎是全局共享的,必须在 LoadHTMLGlob 之后、路由注册之前,用 router.SetHTMLTemplate 注入。
否则会出现:主模板能用函数,但 {{template "header" .}} 里调用就报 function "formatDate" not defined。
- 正确顺序:
LoadHTMLGlob→template.New().Funcs(...).ParseGlob(...)→router.SetHTMLTemplate(...) - 别漏掉
ParseGlob:仅LoadHTMLGlob不会触发函数绑定 - 函数签名必须返回
(string, error)或(interface{}, error),否则运行时报 panic
template.Template 实例解析,Gin 默认的 LoadHTMLGlob 走的是内部缓存逻辑,一旦你中途用 template.ParseFiles 单独加载某个文件,它就脱离了 Gin 的模板树,{{template}} 就找不到它。











