模板嵌套必须用 define + template,不能靠文件路径拼接;所有块需同一 loadhtmlglob 加载且共存于一个 template 实例中,define 名区分大小写、不含斜杠,. 始终指向顶层传入数据。

模板嵌套必须用 define + template,不是文件路径拼接
很多人误以为把 header.html 和 footer.html 放在子目录里,再用 c.HTML(200, "user/index.html", data) 就能自动包含它们——其实不会。Gin 的默认渲染器不解析子模板路径,只认当前文件内容。嵌套依赖 Go 原生 html/template 的语义:define 定义命名块,template 引用块,且所有块必须在同一个 template.Parse 上下文中注册。
常见错误现象:executing "user/index.html" at <.>: error calling template: template "header" not defined</.>,说明被引用的块没被加载或名字不匹配。
-
define名字区分大小写,且不能含斜杠(如"layout/header"是非法的) - 所有参与嵌套的 HTML 文件必须通过同一方式加载(比如全用
LoadHTMLGlob("templates/**/*")),否则块之间不可见 - 基础布局(如
base.html)里用{{ template "content" . }},子模板里用{{ define "content" }}...{{ end }},顺序无关,但必须共存于同一加载批次
LoadHTMLGlob 通配符必须覆盖全部嵌套文件,否则块缺失
如果你的目录结构是 templates/layout/base.html、templates/pages/home.html,而只写 r.LoadHTMLGlob("templates/pages/*.html"),那 base.html 根本不会被加载,home.html 里 {{ template "base" . }} 就会报错“template is undefined”。
正确做法是让 glob 匹配所有层级:
- 推荐写法:
r.LoadHTMLGlob("templates/**/*")—— 递归加载templates/下所有文件,不管几层深 - 避免写
"templates/*.html":它只扫第一层,layout/base.html被忽略 - 不要写
"templates/**/*.html":Go 的filepath.Glob不支持双星号嵌套(**在 Go 1.16+ 才被path/filepath有限支持,但html/template加载逻辑不保证兼容),稳妥起见统一用"templates/**/*"
使用 gin-contrib/multitemplate 时,AddFromDir 的第一个参数是模板名,不是路径
这个包本质是用多个独立 template.Template 实例模拟“多模板”,每个实例可单独定义块。但它不改变 Go 模板引擎的块作用域规则:一个模板实例里的 define 只对它自己生效,跨实例的 template 调用无效。
所以你不能指望 renderer.AddFromDir("base", "templates/base.html") 后,在另一个用 AddFromDir("home", "templates/home.html") 加载的模板里直接 {{ template "base" . }} —— 这会失败。
- 真正有效的做法:把所有需要互相引用的块(比如
header、footer、content)都定义在同一个 HTML 文件里(例如templates/base.html),然后只调用AddFromDir("base", ...)一次 -
AddFromDir第一个参数(如"base")是注册进 Gin 渲染器的模板键名,c.Render(200, "base")才能命中;它和文件路径无关 - 如果真要拆分文件,得改用
template.ParseFiles手动合并,或坚持用原生LoadHTMLGlob+ 单一模板实例
数据传入嵌套模板时,. 始终是顶层传入对象,不因嵌套改变
无论你在 base.html 里写 {{ .Title }},还是在 {{ define "content" }} 块里写 {{ .User.Name }},. 都指向 c.HTML() 第三个参数传入的那个 struct 或 gin.H。嵌套不产生新作用域,也不自动解包字段。
容易踩的坑:
- 字段名大小写敏感:
User字段导出为User,模板里必须写.User.Name,写成.user.name就取不到 - map 类型字段需确保 key 存在,
gin.H{"data": map[string]interface{}{"name": "Alice"}},模板中用{{ .data.name }}(小写name)才对 - 循环内
.指向当前项:{{ range .Items }}{{ .ID }}{{ end }},不是{{ .Items.ID }}
复杂点在于嵌套层级深了之后,字段路径容易写错,而且错误只在运行时暴露——没有编译检查。建议用结构体而非 gin.H,让 IDE 能提示字段名,也方便加 JSON tag 对齐前后端。











