模板不渲染且直接 panic,通常因 loadhtmlglob 或 loadhtmlfiles 未在 r.run() 前调用,或路径错误、后缀不匹配;gin 不支持运行时热加载,r.run() 后调用加载方法无效。

模板不渲染、直接 panic,八成是 LoadHTMLGlob 或 LoadHTMLFiles 没在 r.Run() 前调用,或者路径写错、文件名后缀没匹配上。
模板加载必须在 r.Run() 之前执行
Gin 不会运行时扫描或热加载模板。一旦你调用 r.Run(),所有模板加载窗口就关闭了——之后再补 LoadHTMLGlob 完全无效,c.HTML 会直接 panic。
-
r.LoadHTMLGlob("templates/**/*.tmpl")是最常用方式,支持子目录嵌套,但注意双星号**在某些旧版 Go(templates/*/*.tmpl 或分层加载 -
r.LoadHTMLFiles("templates/base.tmpl", "templates/home.tmpl")适合小项目,顺序敏感:如果home.tmpl里{{template "base" .}},那base.tmpl必须先列在参数里 - 路径是相对
go run所在目录的,不是相对于main.go;比如你在cmd/app下执行go run main.go,那"templates/*.tmpl"就得从cmd/app/templates/找
{{template}} 嵌入子模板时作用域不会自动继承
写 {{template "header"}} 和 {{template "header" .}} 效果完全不同:前者子模板收不到任何数据,后者才把当前上下文完整传进去。
- 局部模板名必须严格一致:
{{define "header"}}和{{template "header" .}}中的字符串要逐字匹配,不能有空格或大小写差异 - 如果只想传部分字段,别硬塞整个
.,用{{template "card" (dict "Name" .User.Name "ID" .User.ID)}},但前提是已通过r.SetFuncMap注册过dict函数 - 避免在子模板里访问未传入的字段,比如主模板传了
"user": User{...},子模板写{{.Profile.Avatar}}却没传Profile字段,结果就是空字符串,不会报错,极难排查
静态资源 404 不是模板问题,是路由没配
模板里写的 <link href="/static/css/app.css"> 这类路径,Gin 根本不解析,也不会提示“你忘了配静态路由”。浏览器发请求时,Gin 看到 /static/xxx 路径没匹配任何 GET 路由,就返回 404。
- 必须显式加一行
r.Static("/static", "./static"),且前缀/static要和模板中写的 URL 完全一致(区分大小写) - 本地目录路径
"./static"是相对执行目录的,不是代码目录;若用go build && ./app方式部署,确保打包时把./static目录一并带上 - 别试图用
r.StaticFS或中间件伪造静态服务——r.Static是 Gin 内置优化过的,支持缓存头、范围请求(Range)、gzip 自动协商,自己实现容易漏掉关键逻辑
struct 字段必须首字母大写才能在模板里访问
Go 的 html/template 只导出首字母大写的字段和方法。传 User{Name: "Alice", age: 25} 进去,{{.Name}} 能取到,{{.age}} 永远是空。
- 别依赖指针解引用:传
*User进去,模板里{{.Name}}依然有效,但{{.Method()}}会静默失败(除非Method是导出方法) -
gin.H是map[string]interface{},方便快速构造,但它无法调用方法、不支持嵌套结构体字段链式访问(比如{{.user.profile.avatar}}需要user是 struct,且每级字段都导出) - 如果字段名含下划线(如
user_name),Go 结构体得写成UserName并加 tag:UserName string `json:"user_name"`,否则模板里只能写{{.UserName}}
最容易被忽略的是:模板语法错误(比如 {{if .X}}...{{end 少了个 }})和路径错误都会导致 LoadHTML* 直接 panic,而不是延迟到 c.HTML 时才报——这意味着你根本收不到 HTTP 请求,连日志都看不到,只能盯紧启动日志里的 panic 输出。











