错误与未找到
error.tsx 接渲染失败,not-found.tsx 接 notFound(),unauthorized/forbidden 接 401/403 认证中断;global-error.tsx 是根布局崩溃时换掉整个文档的最后一道
按下去,看本段 error.tsx
抛错发生在渲染阶段。按钮的 onClick 本身不会被 Error Boundary 接住,所以用一次 state 更新把异常留到下一次渲染
触发后,这个专题页会被app/router/errors/error.tsx换掉。顶栏还在,因为错误边界包的是这一段,不是根布局。global-error.tsx才会换掉根布局 —— 本站现在有真实的app/global-error.tsx了(见下方专节),但不拿线上壳层做触发实验。
打开一条不存在的地址
notFound() 不是异常。error.tsx 接不住它
打开 /router/errors/missing
那个路由的 page 直接调用 notFound(),由同段的 not-found.tsx 渲染。 预期中的空(用户被删了、id 不存在)走这条;渲染崩溃走 error.tsx。
五份文件各管什么
先问这是失败,还是本来就没有;认证中断与 404 一样是「中断」,不是异常
| 文件 | 什么时候出现 | 必须是 Client? |
|---|---|---|
| error.tsx | 本段渲染抛错 | 是。它要提供 reset() |
| not-found.tsx | 调用了 notFound(),或没有匹配的页面 | 否 |
| unauthorized.tsx | 调用了 unauthorized()(HTTP 401,未认证) | 否 |
| forbidden.tsx | 调用了 forbidden()(HTTP 403,已认证但没权限) | 否 |
| global-error.tsx | 根布局自己坏了 | 是,并且要自己写 html 和 body |
按下去,看 401 与 403
unauthorized() / forbidden() 与 notFound() 同族:抛出即中断渲染,交给最近的同名约定文件,不进 error.tsx
- 语义分工:401 回答「你是谁」(未登录、会话过期, 登录入口该出现在 unauthorized.tsx 里);403 回答「我知道你是谁, 但你没权限」(角色不足,引导换账号/申请权限)
- 两个演示页的闸门都先
await connection()再抛 —— 权限是请求时决策,不能出现在预渲染阶段 (cacheComponents 约束),所以闸门包在 Suspense 里 - 真实项目里闸门的位置是「读 session → 分支」;本站没有登录体系, 固定演示两个失败分支。与notFound()一样,它们抛出的不是 Error,error.tsx 接不住也不该接
global-error.tsx:根布局崩溃时的最后一道
本站真的有这份文件了 —— app/global-error.tsx,这里直接引用它的源码事实
- 边界:error.tsx 只能接「本段 page 及子树」的错误, 接不住根 layout 自己的崩溃;根布局坏了时,
app/global-error.tsx替换的是整个文档,顶栏侧边栏全军覆没 - 必须自带 <html>/<body>:根布局被换掉了, 文档骨架没人提供 —— 这是全站唯一一个自己渲染 html/body 的非根布局文件
- 必须是 Client Component:与 error.tsx 同理, reset() 依赖客户端 Error Boundary;本站的实现第一行就是
"use client" - 全内联样式,零外部依赖:根布局崩溃意味着 globals.css、 字体变量都可能不可用,本站的 global-error 只用内联样式 + 品牌底色 + signal 磷光,连 Tailwind 类都不写
- 为什么不做活演示:触发它得让根布局真的抛错, 那会把全站(包括导航回专题页的能力)一起打挂;读
app/global-error.tsx源码就是本站选择的演示方式