必须调用c.AbortWithStatusJSON或c.Abort()显式终止,否则后续中间件会覆盖响应;NoRoute须置于所有路由注册之后,仅在整棵路由树无匹配时触发;API与页面路径需按前缀区分处理。

直接写 c.JSON(404, ...) 或 c.HTML(404, ...) 后页面空白或返回默认文本,是因为没调用 c.Abort() 或等效终止方法。
为什么 NoRoute 必须放在所有路由注册之后
因为 NoRoute 是 Gin 的兜底钩子,只在整棵路由树完全无匹配时触发。它不按代码顺序“逐条判断”,而是依赖内部路由树构建完成后的最终状态。
- 如果提前写
r.NoRoute(...),后面所有r.GET()、r.POST()都不会被路由引擎收录——请求根本到不了那些 handler - 哪怕漏掉一个
r.PUT("/x", ...),只要路径和方法组合未定义,就会落到NoRoute里 - 用了
r.Group("/api"),也要等整个 group 内所有子路由注册完,再在 router 根级调用NoRoute,不能挂在 group 里面
如何让 NoRoute 返回 JSON 而不是默认纯文本
Gin 默认的未匹配响应是硬编码的 "404 page not found",绕过所有中间件和模板逻辑。要返回 JSON,必须显式终止执行链。
- 推荐用
c.AbortWithStatusJSON(404, ...):它内部已包含c.Status(404)+c.JSON(...)+c.Abort() - 避免只写
c.JSON(404, ...):状态码设了,但 body 可能被后续 recover / logger 中间件覆盖或冲掉 - 若需复用统一错误结构体(如
ErrorResponse{Code: 404, Message: ...}),就在NoRoute里直接构造并传入
如何区分 API 和前端页面路径做不同 404 响应
单页应用(SPA)通常把非 API 请求 fallback 到 /index.html,而 API 请求未命中必须返回 JSON 错误。靠路径前缀判断最简单可靠。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 用
strings.HasPrefix(c.Request.URL.Path, "/api/")判断是否为 API 请求 - 注意大小写:/API/ 和 /api/ 是不同路径;业务允许的话可先转小写再比对
- 静态资源路径(如
/static/、/assets/)建议单独分支处理,避免误重定向 - 别用正则做前缀判断——字符串操作更快,也更安全,且 Gin 的
NoRoute本身不支持正则匹配
HTML 模板 404 页面为何渲染为空白或 404 状态码不对
两个关键点:模板必须提前加载,且 c.HTML() 调用时要显式传 404 状态码。
-
r.LoadHTMLGlob("templates/errors/*.html")或r.LoadHTMLFiles("templates/errors/404.html")必须在r.NoRoute()之前调用,否则c.HTML()会 panic 或静默失败(返回空 body,但状态码仍是 200) -
c.HTML(404, "404.html", gin.H{"path": c.Request.URL.Path}):第一个参数必须是404,否则浏览器看到的是 200 OK - 模板中无法访问
c.Request或c.Param(),所有变量必须手动提取并塞进gin.H{},比如"referer": c.GetHeader("Referer")
最容易被忽略的是:NoRoute 不是“自动生效”的中间件,它是一次性注册的兜底函数;而 c.Abort() 的缺失会导致响应被后续中间件悄悄覆盖——这种问题在线上往往表现为偶发空白页或状态码错乱,排查时容易绕远路。










