gin默认404不走模板渲染,因其未匹配路由时直接输出纯文本“404 page not found”,不触发任何handler或中间件;noroute必须在所有路由注册后调用,显式设置状态码并终止响应流,方可返回html或json。

为什么 Gin 默认 404 不走模板渲染
Gin 的 DefaultWriter 在路由未匹配时直接输出纯文本 "404 page not found",根本没进入任何 handler,模板引擎自然不会执行。这不是配置漏了,而是框架设计如此:未命中路由 = 不触发任何中间件或 handler,更不调用你注册的 HTML 渲染逻辑。
NoRoute 必须放在所有路由注册之后
NoRoute() 是 Gin 提供的专用兜底钩子,但它不是“默认 fallback”,而是一个显式注册的处理器——必须在 r.GET()、r.POST()、r.Group() 全部注册完之后调用,否则会被后续路由覆盖或完全不触发。
- 错误写法:
r.NoRoute(...)写在r.GET("/user", ...)前 → 永远不会执行 - 正确顺序:业务路由 → 分组路由 →
r.NoRoute(...) - 如果用了
r.Group("/api"),也要确保整个分组注册完毕再挂NoRoute
返回 HTML 还是 JSON?参数和状态码都要手动设
无论返回 HTML 还是 JSON,NoRoute 里都必须显式设置状态码并终止响应流,否则可能被后续中间件(比如日志或 recover)重复写 header 或 body。
- 返回 HTML:
c.HTML(404, "404.html", gin.H{"path": c.Request.URL.Path}),前提是已调用r.LoadHTMLFiles()或r.LoadHTMLGlob()加载模板 - 返回 JSON:
c.AbortWithStatusJSON(404, gin.H{"error": "not found", "path": c.Request.URL.Path})——AbortWithStatusJSON自动调用Abort(),比c.JSON() + c.Abort()更安全 - 模板中不能访问
c.Request或c.Param(),所有数据必须提前塞进gin.H传入,例如{{.path}}可用,{{.Request.URL.Path}}会报错
区分 API 和页面路径时怎么判断
很多项目同时服务 REST 接口(如 /api/v1/users)和前端页面(如 /、/app/*),需要不同响应策略。靠 c.Request.URL.Path 做前缀判断即可,但要注意大小写和斜杠边界。
- 推荐写法:
strings.HasPrefix(path, "/api/"),而不是strings.Contains(path, "api") - 静态资源路径(如
/css/、/js/)未命中时,通常应返回 404 而非重定向,避免掩盖真实缺失文件问题 - SPA 场景下,可对根路径以外的页面路径做重定向:
if path != "/" && !strings.HasPrefix(path, "/api/") { c.Redirect(http.StatusFound, "/") }
LoadHTMLGlob 加载,但模板里引用的 /css/app.css 这类路径,必须额外配 r.Static("/css", "./static/css") 才能访问到——NoRoute 处理不了这类请求,它只管“路由未匹配”,不管“文件不存在”。











