必须用NoRoute注册兜底路由并手动调用c.HTML(404, "404.html", data)或c.AbortWithStatusJSON(404, ...),因Gin默认DefaultWriter直接输出纯文本绕过模板;NoRoute须置于所有业务路由之后,且需提前加载模板文件,否则panic。

在 Gin 框架中实现自定义 404 页面,必须绕过默认的纯文本响应机制,因为 Gin 的 DefaultWriter 在路由未匹配时直接写入“404 page not found”,完全跳过模板引擎;若只注册 HTML 模板但不显式调用 NoRoute,页面永远不会渲染。
注册 NoRoute 兜底路由
第一步:确保所有业务路由(GET/POST/PUT 等)已全部注册完毕,再调用 NoRoute。
第二步:调用 r.NoRoute() 并传入一个接收 *gin.Context 的函数,该函数内必须主动调用 c.HTML(404, "404.html", data) 或 c.AbortWithStatusJSON(404, ...),否则请求会继续向下执行,最终返回默认纯文本。
第三步:确认模板文件已被加载——使用 r.LoadHTMLFiles("templates/404.html") 或 r.LoadHTMLGlob("templates/**/*"),否则运行时 panic。
【NoRoute 必须放在所有 r.GET/r.POST/r.Group 之后,否则后续路由永不触发】
区分 404 与 405 错误类型
方法一:仅处理路径未匹配(404)
直接使用 NoRoute 即可,它只捕获「路径 + 方法」双重不匹配的情况,例如对 /api/user 发起 GET 请求但该路径未注册任何 handler。
方法二:同时捕获方法不支持(405)
Gin 提供专用钩子 NoMethod,用于处理路径存在但 HTTP 方法不被允许的情形,比如对已注册的 POST /login 发起 GET 请求。
必须单独注册:r.NoMethod(func(c *gin.Context) { c.HTML(405, "405.html", gin.H{"title": "Method Not Allowed"}) }),且同样需提前加载对应模板文件。
注意:NoMethod 不会自动继承 NoRoute 的逻辑,两者互不干扰,必须分别注册。
返回 JSON 格式的 404 响应
如果项目是纯 API 服务,不需要 HTML 页面,就用 c.AbortWithStatusJSON() 终止流程并返回结构化数据:
复制以下代码到 NoRoute 函数体中:
c.AbortWithStatusJSON(404, gin.H{
"code": "NOT_FOUND",
"message": "The requested endpoint does not exist",
"path": c.Request.URL.Path,
"timestamp": time.Now().Unix(),
})
这一步必须包含 c.AbortWithStatusJSON 或等效组合(如 c.JSON() + c.Abort()),【只写 c.JSON(404, ...) 而不 Abort 会导致中间件重复执行、Header 冲突】。
按路径前缀分流处理
当项目同时暴露 API 接口(如 /api/v1/xxx)和前端静态资源(如 /、/app/*)时,可依据请求路径动态响应:
① 提取原始路径:path := c.Request.URL.Path
② 判断是否为 API 请求:if strings.HasPrefix(path, "/api/")
③ 是则返回 JSON 错误,否则重定向至 SPA 入口或渲染 HTML 页面:
if strings.HasPrefix(path, "/api/") {
c.AbortWithStatusJSON(404, gin.H{"error": "API endpoint not found"})
} else {
c.Redirect(http.StatusFound, "/index.html")
}
注意:字符串比较区分大小写,/API/ 和 /api/ 视为不同路径。


















