Gin模板加载必须在路由注册前完成,否则c.HTML会报“template is undefined”;LoadHTMLGlob路径需用"templates/*/"才能递归匹配子目录;静态资源路径须与r.Static挂载前缀严格一致;自定义函数必须在LoadHTMLGlob前通过Funcs注册。

模板加载时机必须在路由注册前
模板没渲染出来,八成是 r.LoadHTMLGlob 放错了位置。Gin 的模板加载不是“按需读取”,而是一次性解析并缓存全部匹配文件;如果写在 r.GET 之后,后续所有 c.HTML 调用都会报 template: "xxx" is undefined。这个错误不抛 panic,只返回空白页或 500,非常难定位。
常见错误写法:
r := gin.Default()
r.GET("/home", handler)
r.LoadHTMLGlob("templates/*") // ❌ 错!此时模板引擎已初始化完毕,加载无效
正确顺序必须是:
-
r.LoadHTMLGlob或r.LoadHTMLFiles紧跟在gin.Default()或gin.New()之后 - 再注册中间件(如日志、CORS)
- 最后才是路由定义
使用 LoadHTMLGlob 时路径通配符要带双星号才能递归匹配子目录
很多人以为 "templates/*" 能加载 templates/admin/index.html,其实不能——它只匹配一级子目录下的文件,admin/index.html 这种路径会被忽略。真正生效的是 "templates/**/*"。
实际影响:
- 子目录模板(如
templates/user/profile.html)无法被识别,调用c.HTML(200, "user/profile.html", data)直接 panic -
LoadHTMLFiles虽然精确,但维护成本高,新增模板就得改代码 - 生产环境若用
embed.FS静态打包,路径模式也要同步改成**/*.html才能覆盖嵌套结构
模板中引用静态资源必须用绝对路径,且 StaticFS 挂载点要和 HTML 中的 href/src 前缀一致
CSS 加载 404、JS 报错找不到,90% 是因为路径没对齐。Gin 的 r.Static 或 r.StaticFS 注册的是“URL 前缀 → 本地目录”的映射,而模板里写的路径是浏览器发起请求时用的 URL,两者必须严格对应。
例如:
r.Static("/static", "./assets") // 表示 /static/xxx → ./assets/xxx
那模板里就必须写:
<link rel="stylesheet" href="/static/css/app.css"> <script src="/static/js/main.js"></script>
容易踩的坑:
- 写成
./static/css/app.css或static/css/app.css:浏览器会拼到当前 URL 后面,比如从/admin页面发出请求,就变成/admin/static/css/app.css,404 - 挂载点用了
/assets,但模板里还写/static:路径不匹配,资源永远 404 - 开发时用
file://直接打开 HTML,静态路径看似正常,但一跑 Gin 就崩——这是环境差异,不是代码问题
自定义模板函数必须在 LoadHTMLGlob 之前注册,且函数名不能含点号
想在模板里用 {{ formatDate .CreatedAt }} 却提示 function "formatDate" not defined?不是函数写错了,而是注册时机或命名违规。
Gin 的模板引擎底层是 Go 标准库 html/template,它要求所有自定义函数必须在模板加载前注入到全局函数集里。一旦 LoadHTMLGlob 执行,函数表就冻结了。
正确做法:
- 用
template.Funcs注册,不是直接往gin.Engine上挂方法 - 函数名只能是字母、数字、下划线,不能有
.、-、/等符号(否则模板解析阶段就报错) - 如果用了
embed.FS+LoadHTMLFS,函数注册逻辑也要放在 FS 加载之前
典型错误示例:
r := gin.Default()
r.LoadHTMLGlob("templates/*")
r.SetFuncMap(template.FuncMap{"format.date": formatDate}) // ❌ 错!此时模板已加载,函数无效


















