最简单可靠的自定义模板方案是用r.loadhtmlglob("templates/*/")加载模板、c.html(200,"name.html",data)渲染,但需确保路径匹配、文件名准确(仅basename)、数据字段首字母大写且键名严格一致,三者缺一将导致白屏或“template is undefined”错误。

直接用 r.LoadHTMLGlob("templates/**/*") 加载模板,再用 c.HTML(200, "name.html", data) 渲染,是最简单可靠的自定义模板方案——但前提是路径、命名、字段导出三者全对,缺一就会白屏或报 "template is undefined"。
模板文件路径和加载方式必须匹配
Gin 不会自动递归查找子目录,LoadHTMLGlob 的通配符行为很关键:
-
"templates/*.html"只加载templates/根目录下的 .html 文件,templates/user/index.html不会被识别 -
"templates/**/*"才能匹配任意层级,比如templates/admin/dashboard.html或templates/shared/_header.html - 如果用
LoadHTMLFiles,必须写全路径:r.LoadHTMLFiles("./templates/user/profile.html", "./templates/base.html"),相对路径以二进制运行位置为准 - 编译后执行
./myapp时,Gin 只认当前工作目录下的templates/,不是源码目录;go run main.go则取决于 shell 当前路径,容易不一致
c.HTML() 第二个参数是文件名,不是路径
模板注册名只取文件 basename,和磁盘路径无关:
- 若文件是
templates/user/profile.html,调用时必须写c.HTML(200, "profile.html", data) - 写成
"user/profile.html"或"profile"都会失败,报"template: profile not found"或类似错误 - 多个同名文件(如
templates/a/index.html和templates/b/index.html)会导致覆盖,Gin 只保留最后一个
数据结构字段必须首字母大写且键名严格一致
Go 的 html/template 无法访问小写字段,模板里大小写也必须和传入 key 完全一致:
- 结构体字段
Title string✅ 可在模板中用{{.Title}};title string❌ 模板取不到值,也不报错,只渲染为空 - 用
gin.H{"title": "首页"}传参时,模板里必须写{{.title}},不能写{{.Title}}—— 键名区分大小写 - 嵌套结构体字段也要可导出:
type PageData struct { User UserInfo }要求UserInfo是导出类型,且其字段如Name也是大写开头 - 时间、切片、map 等类型可直接传,但格式化要用 Go 模板语法:
{{.Now.Format "2006-01-02"}}
自定义函数和分隔符需提前设置
模板函数和分隔符必须在加载模板前注册,否则无效:
- 自定义函数要先调用
r.SetFuncMap(),再调用r.LoadHTMLGlob(),顺序反了函数不会生效 - 修改分隔符如
r.Delims("{[{", "}]}")同样必须在LoadHTMLGlob前,否则已加载的模板仍按默认 {{}} 解析 - 函数签名必须是
func(...interface{}) interface{}类型,返回值不能是未导出类型,否则模板执行时报 panic - 静态资源(CSS/JS)不走模板系统,要用
r.Static("/static", "./static")单独挂载,路径别和模板目录混淆
最容易被忽略的是:模板加载时机必须在 gin.Default() 之后、任何路由注册之前;而所有自定义配置(FuncMap、Delims)又必须在模板加载之前。这三步顺序错了,哪怕代码一字不差,也会静默失效。











