Notes /react/10-routing-and-navigation/03-next-app-router-segments-layout-loading-error-not-found-prefetch
Next.js App Router:Segments / Layouts / Loading-Error-NotFound / Prefetch(落地篇)
一句话结论:在 App Router 体系下,路由不是“匹配 URL → 渲染组件”这么简单,而是用 segment tree(分段树)组织 UI 与数据边界:
layout/page决定可复用的壳,loading/error/not-found决定“等/错/无”的兜底策略;当你把这些边界与 Next 的 prefetch + fetch 缓存/重验证 一起设计,才能避免“切页白屏、局部闪烁、线上不更新”等生产事故。
0. 先把 App Router 当成“树”来看
App Router 的关键不是某个 API,而是它的组织方式:
- URL path 会映射到
app/目录下的一棵 segment tree - 每个 segment 可以定义:
layout.tsx:这一段及其子树共享的 UI 壳(导航栏、侧边栏、权限壳)page.tsx:这一段的页面入口loading.tsx:这段树在加载时的 fallback(通常是 skeleton)error.tsx:这段树的错误边界(Client Component)not-found.tsx:这段树的“资源不存在”边界
你可以把它理解为:路由 = UI 边界树 + 数据边界树。
1. Segments:用“分段”表达信息架构
1.1 什么是 segment?
app/dashboard/settings/page.tsx中的dashboard、settings都是 segment- 动态段:
[id]、[...slug]、[[...slug]]
它承载两个含义:
- URL 的语义结构(信息架构)
- UI 与边界的复用层级(哪些页面共享同一层 layout / loading / error)
1.2 结构建议(架构师视角)
- 先用业务域拆 segment:
/orders/*、/users/*、/products/* - 把“壳”放在 layout,把“内容”放在 page
- 把鉴权/错误/加载边界尽量下沉到合适的业务域 segment(避免全局一刀切)
2. Layout:稳定 Shell 是体验与可维护性的核心
2.1 layout 的价值
- 复用页面壳:导航栏、侧边栏、面包屑
- 让导航时“壳不动、内容换”,减少切页闪屏
- 给错误/加载边界提供更好的分层(局部失败不拖垮整站)
2.2 layout vs template(了解即可)
layout.tsx:跨导航复用(状态可保留)template.tsx:每次导航都会重新创建(更像“每次都重置”)
大多数业务用 layout 足够;只有当你明确需要“每次进来都重置状态”才用 template。
3. Loading:把“等”变成可控的骨架分层
3.1 为什么 loading.tsx 很关键?
App Router 下,很多数据获取发生在 Server Components(RSC)里。只要某个 segment 树在等待数据,它就需要一个“等”的 UI。
loading.tsx 的价值在于:
- 它把等待变成显式边界,而不是组件里到处
if (isLoading) - 它天然适合做页面壳 skeleton 或 局部区块 skeleton
3.2 工程建议:优先保证稳定壳,再做局部骨架
- 全局/高层 layout:尽量稳定(不要在 loading 时整页换壳)
- 列表/详情等区域:在更低层 segment 做 skeleton
经验:Skeleton 的层级越合理,用户感知越“快”。
4. Error:错误边界要“能兜底 + 能上报 + 能恢复”
4.1 error.tsx 是什么?
- 它是 segment 级错误边界
- 它必须是 Client Component(通常要加
"use client")
4.2 设计要点
- 给用户一个可理解的错误信息(不是白屏)
- 提供重试(调用
reset()) - 上报(Sentry/自研)要带:
- route 信息(segment/path/searchParams)
- release/version
- traceId(如果你有全链路)
这里的上报策略会在“生产可用性”章节展开。
5. Not Found:把“无数据/无权限/真 404”区分开
5.1 not-found.tsx 的定位
- 表达“资源不存在”或“无法访问该资源(以 404 语义呈现)”
- 通常用于详情页:
/posts/[id]找不到
5.2 常见误区
- 把所有错误都丢到 not-found:会导致监控缺失(500 被当成 404)
- 把鉴权失败当成 not-found:要看业务(安全考虑可隐藏资源存在性,但要把错误上报到监控)
6. Prefetch:Next 的预取默认很强,但要理解边界
6.1 Link 预取
Next 的 <Link /> 默认可能会预取:
- 目标路由的 RSC payload
- 相关静态资源
这会带来:
- 导航更快
- 但也可能带来:带宽压力、数据提前拉取(与权限/个性化有关)
6.2 预取策略的工程化建议
- 对“命中率高”的导航(主流程)保持默认预取
- 对“命中率低/代价高”的路由:
- 降低预取(按 Next 版本能力调整
prefetch) - 或把数据获取延后到进入页面后(避免无效请求)
- 降低预取(按 Next 版本能力调整
注意:预取不是越多越好,它是预算问题。
7. 与数据获取/缓存的关系(先给边界,细节放到第 12 章)
在 App Router 下,你会遇到两套“像缓存”的东西:
- Next 的
fetch缓存/重验证(框架级,影响线上是否更新) - 客户端的缓存(例如 TanStack Query,偏交互级)
架构建议(先立边界):
- 首屏/页面级数据:更倾向于 Server Components + Next 缓存/重验证
- 强交互数据(筛选、无限滚动、局部刷新):更倾向于 Client Components + TanStack Query
不要让同一数据域同时被两套缓存系统“各自为政”。
8. Checklist
- segment tree 是否能表达清晰的信息架构(业务域分层)?
- 是否存在稳定 shell(layout)来避免切页闪屏?
- loading/error/not-found 是否分层(局部失败不拖垮整站)?
- 是否理解 prefetch 的收益与成本,并有预算策略?
- 数据获取边界是否明确(RSC vs Client Query),避免双重缓存真相?
关联阅读
- 路由与 URL state 心智模型:
./01-routing-and-url-state-mental-model.md - TanStack Router 落地(对照):
./02-tanstack-router-route-tree-search-loader-prefetch.md - TanStack Query 与缓存一致性:
../11-data-layer/02-tanstack-query-caching-and-invalidation.md - 渲染与交付(Hydration/Next 缓存事故):
../12-rendering-and-delivery/README.md