最常见原因是未显式调用c.status(404)或c.status(500),fiber默认返回200;且notfound仅在无匹配路由时触发,需通过fiber.config配置,而非普通路由注册。

为什么自定义错误响应总返回 200 状态码
最常见原因是:你在 NotFound 或 ErrorHandler 里只写了 HTML 渲染或字符串输出,却忘了调用 c.Status(404) 或 c.Status(500)。Fiber 不会自动根据内容推断状态码,c.SendString("not found") 默认发的是 200。
另一个隐蔽坑是:用了 app.Get("/404", ...) 这类路由,误以为访问不存在路径会跳过去——实际完全不触发,因为 404 是“无匹配路由”时才走的兜底逻辑,不是普通路由。
- 必须在
fiber.New(fiber.Config{...})初始化时传入NotFound和ErrorHandler,后期调用app.Use(...)无法覆盖 -
NotFound只处理“没注册任何路由”的情况;ErrorHandler才捕获 panic、显式fiber.NewError(500, "")等异常 - 浏览器缓存可能导致你改了代码但页面还是旧的,测试时用无痕模式或加时间戳参数(如
/test?_t=123)
如何正确配置 NotFound 和 ErrorHandler
NotFound 是纯函数,接收 *fiber.Ctx,推荐直接返回静态内容;ErrorHandler 多一个 error 参数,需手动判断错误类型再设状态码。
-
NotFound示例:NotFound: func(c *fiber.Ctx) error { c.Status(404) return c.SendString("<h1>⛔ 页面未找到</h1>") } -
ErrorHandler示例(区分 404 和 500):ErrorHandler: func(c *fiber.Ctx, err error) error { if e, ok := err.(*fiber.Error); ok { c.Status(e.Code) } else { c.Status(500) } return c.SendString("<h1>" + c.Response().StatusCode() + " 错误</h1>") } - 二者可共存:比如用户访问
/api/user/999,路由存在但 DB 查不到数据,应由业务 handler 主动返回fiber.NewError(404, "user not found"),这样会进ErrorHandler而非NotFound
用模板引擎渲染错误页时怎么避免 panic
如果你已配好 html.New() 模板引擎,不能直接在 NotFound 里写 c.Render("404", nil)——因为 Render 是异步方法,而 NotFound 是同步 handler,出错时没法 recover。
- 必须手动捕获
Render错误,并 fallback:NotFound: func(c *fiber.Ctx) error { c.Status(404) if err := c.Render("404", nil); err != nil { return c.SendString("404 page not found") } return nil } - 确保模板文件路径正确,比如
views/404.html,且html.New("views", ".html")的根目录和扩展名与实际一致 - 别在
Render前调用c.SendString或c.JSON,否则会报body already written
移动端和桌面端错误页要不要分开
一般不用。错误页本质是服务端问题或路径错误,终端类型不影响语义,强行分会导致缓存混乱、CDN 配置复杂、日志难以归因。
真有需求(比如嵌入小程序的错误页要隐藏调试信息),靠 c.Get("User-Agent") 判断即可,但注意:
- 别用
c.Accepts("html")判断——它只看Accept请求头,和终端无关 - 正则建议用
/(?i)mobile|android|iphone|ipad|ios/,比依赖第三方 UA 库更轻量可靠 - 如果用了
Sec-CH-UA-Mobile(Chromium 新标准),记得同时检查该 header 是否为?1,并 fallback 到 UA
真正容易被忽略的是:错误页里的 JS/CSS 资源路径是否带版本号或哈希,否则缓存旧资源会让新错误页样式错乱。











