loadhtmlglob匹配的是当前工作目录下的templates/目录,仅匹配单层文件,*才递归子目录;c.html()的name参数是模板名而非路径,需配合{{define}}声明;推荐用filepath.walkdir+loadhtmlfiles显式加载以避免冲突。

LoadHTMLGlob 通配符匹配实际生效路径
你写 r.LoadHTMLGlob("templates/**/*"),Gin 并不关心你源码里 templates 在哪,它只认**当前进程工作目录下的 templates/ 目录**。编译后执行 ./myapp,就去二进制同级找 templates/;用 go run main.go,则取决于终端当前所在路径——常因 pwd 不对导致模板“加载成功但渲染报错 template is undefined”。
通配符行为必须明确:* 只匹配单层文件(如 templates/*.html);** 才递归子目录(如 templates/**/index.html 匹配 templates/user/index.html 和 templates/admin/index.html);**/* 是最常用组合,能覆盖所有层级的 HTML/TMPL 文件。
- 错误写法:
"templates/*"→ 子目录里的文件全被忽略 - 危险写法:
"templates/**"→ 会把目录本身也当模板加载,触发 panic - 安全写法:
"templates/**/*.html"或"templates/**/*"
c.HTML() 中的 name 参数不是路径而是文件名
调用 c.HTML(200, "admin/dashboard.html", data) 是错的——Gin 不解析路径,只认文件名。如果磁盘上是 templates/admin/dashboard.html,那 name 必须填 "dashboard.html";若多个目录下存在同名文件(比如 templates/web/index.html 和 templates/api/index.html),就必须在模板内用 {{ define "web/index.html" }} 显式声明唯一模板名,否则后加载的会覆盖前一个。
- 模板内必须用
{{ define "xxx.html" }}才能支持带斜杠的 name -
gin.H{}传进去的字段名大小写敏感,且必须是导出字段(首字母大写) - 结构体字段加
json:"title"tag 对 HTML 渲染无影响,只作用于 JSON 输出
多级目录下避免模板冲突的两种方案
当项目有 templates/web/、templates/admin/、templates/email/ 多个目录时,直接靠 LoadHTMLGlob 容易撞名或漏加载。更稳的做法是:要么统一用 {{ define }} 命名,要么改用 filepath.WalkDir + LoadHTMLFiles 显式控制。
推荐代码片段:
vartplFiles []string
filepath.WalkDir("templates", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() && strings.HasSuffix(d.Name(), ".html") {
tplFiles = append(tplFiles, path)
}
return nil
})
r.LoadHTMLFiles(tplFiles...)
- 这样加载后,
c.HTML(200, "web/index.html", data)就能直接用路径名了(前提是模板里{{ define "web/index.html" }}) - 比
LoadHTMLGlob更可控,尤其适合 CI/CD 环境中模板路径不确定的场景 - 注意
tplFiles...的三个点不能漏,否则类型不匹配
模板继承时 base.html 的加载位置很关键
如果你用 {{ template "content" . }} 做布局继承,base.html 必须和子模板一起被 LoadHTMLGlob 或 LoadHTMLFiles 加载进来——它不会自动“发现”并加载未注册的父模板。
- 常见错误:只加载
templates/pages/home.html,但没加载templates/layouts/base.html,渲染时报template: "content" is not defined - 解决方案:确保
base.html在 glob 模式范围内,或显式加入LoadHTMLFiles列表 - 不建议把
base.html放在templates/外层,Gin 不支持跨目录引用











