noroute 是 gin 中处理 404 的唯一正确方式,须在所有路由注册后调用,接收 *gin.context 的 handler;500 需自定义 recovery 中间件捕获 panic;业务错误应返回标准 json 而非 html 页面。

用 NoRoute 拦截 404,不是改状态码或重定向
404 页面必须在路由匹配失败时触发,r.NoRoute() 是唯一正确入口。很多人误以为在某个 handler 里 c.Status(404) 或 c.Redirect() 就能显示自定义 404 页面,这是错的——那只是返回一个 404 状态码,页面内容还是你写的 HTML 或 JSON,和真正的“未匹配路径”无关。
关键点:
-
NoRoute必须在所有GET/POST路由注册之后调用,否则会被忽略 - 它接收一个
gin.HandlerFunc,参数是*gin.Context,不是中间件函数 - 不能在里面调用
c.Next(),它不参与中间件链
模板路径要和 LoadHTMLGlob 匹配,否则 c.HTML 报错
如果你用 r.LoadHTMLGlob("templates/**/*.html"),那模板名就得写成 "errors/404.html";如果只加载单个文件如 r.LoadHTMLFiles("templates/404.html"),那就要写 "404.html"。路径不一致会导致 c.HTML() 静默失败或 panic,日志里只有一行 template: "errors/404.html" is undefined。
建议做法:
- 统一用
LoadHTMLGlob("templates/**/*"),把所有模板放templates/下,子目录结构自由 - 模板里用
{{define "errors/404.html"}}显式声明 name,和c.HTML第二个参数严格一致 - 避免在模板中用相对路径引用 CSS/JS,优先内联或用绝对路径
/static/xxx.css
500 页面不能靠 NoRoute,得用 Recovery 中间件捕获 panic
NoRoute 只管 404,500 是运行时 panic 导致的,Gin 默认 recovery 中间件只打印堆栈、返回空响应。要显示自定义 500 页面,必须自己写 recovery 中间件,并在 recover 后调用 c.HTML(500, ...)。
常见陷阱:
- 没手动替换默认
Recovery:用gin.New()时不加任何中间件,就完全没 recovery;用gin.Default()时默认 recovery 已存在,必须用r.Use(YourRecovery())覆盖它 - recover 后没重置 response writer,导致
c.HTML()写失败,浏览器卡住或返回空白 - panic 值不是
error类型,直接断言err.(error)会二次 panic
业务错误(比如参数校验失败)不该走 404/500 页面,该走统一 JSON 响应
404 和 500 是 HTTP 协议层错误,对应“找不到资源”和“服务崩溃”。而 binding error、user not found、permission denied 是业务逻辑错误,应该返回标准 JSON 结构(如 {"code": 400, "msg": "xxx"}),前端才能区分处理。
混淆的后果:
- 前端无法判断是网络问题还是业务问题,统一跳转到错误页,体验断裂
- 监控系统把业务错误当 500 统计,掩盖真实稳定性问题
- SEO 友好性受损:搜索引擎看到大量 500 响应,可能降权
真正需要自定义页面的,只有两类:用户输错 URL(404)、服务意外崩溃(500)。其他都该走 API 标准响应,别往 HTML 页面里塞。











