news 2026/9/14 22:38:51

TanStack Router 404 与资源缺失处理全指南:`notFound`、`notFoundComponent` 与 `notFoundMode` 深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Router 404 与资源缺失处理全指南:`notFound`、`notFoundComponent` 与 `notFoundMode` 深度解析

TanStack Router 404 与资源缺失处理全指南:notFoundnotFoundComponentnotFoundMode深度解析

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

本文基于当前仓库(TanStack Router 及配套的 React / Solid / Vue 框架实现)中 docs/router/guide/not-found-errors.md 展开,围绕路由匹配失败(404)与资源缺失两类场景,系统讲解notFound函数、notFoundComponent路由选项、notFoundMode全局策略以及从旧版NotFoundRoute的迁移路径,并辅以 packages/router-core/src/not-found.ts、packages/router-core/src/router.ts 等源码证据,帮助读者在实战中正确配置、抛出并捕获 not-found 错误。

概述:not-found 错误的两大来源

在 TanStack Router 中,not-found 错误(404)有且仅有两种用途,二者在底层共用同一套notFound函数与notFoundComponentAPI:

  1. 路径不匹配(Non-matching route paths):当路径无法匹配任何已知路由模式,或者仅部分匹配某路由但带有额外路径段时,路由(Router)会自动抛出not-found 错误。

    • 若路由的notFoundModefuzzy(默认值),则由最近的可匹配路由中配置了notFoundComponent的那一个负责处理;
    • notFoundModeroot,则统一由根路由负责处理。
    • 典型示例:
      • 访问/users,但路由树中根本不存在/users路由;
      • 访问/posts/1/edit,但路由树只声明了/posts/$postId(多出的/edit段无法匹配)。
  2. 资源缺失(Missing resources):当某个资源找不到,例如指定 ID 的文章不存在、异步数据不可用等场景。开发者需要自行beforeLoadloader中调用notFound工具函数抛出 not-found 错误。

    • 当在loader中调用时,由最近的、配置了notFoundComponent的路由处理;否则回退到根路由处理。
    • 典型示例:
      • 访问/posts/1,但 ID 为 1 的文章不存在;
      • 访问/docs/path/to/document,但该文档不存在。

从路由自动抛错到开发者手动抛错,最终都收敛为同一种错误对象与同一套渲染边界,这正是新 API 相比旧NotFoundRoute更统一、更可控的原因。

notFoundMode:全局 404 处理策略

当 TanStack Router 遇到无法匹配任何已知路由模式的 pathname,或部分匹配但存在多余尾部 pathname 段时,会自动抛出一个 not-found 错误。此时如何处理取决于路由器选项notFoundMode

模式行为适用场景
fuzzy(默认)智能寻找最近的可匹配路由,渲染该路由的notFoundComponent尽量保留父级布局,让用户基于"原本想去的位置"就近获得导航上下文
root所有 not-found 错误一律由根路由的notFoundComponent处理希望全局统一 404 页面、不关心就近路由

notFoundMode: 'fuzzy'(默认)

默认情况下notFoundModefuzzy:若 pathname 未匹配任何已知路由,路由器会尝试使用最近匹配的、且配置了notFoundComponent的路由

为什么这是默认值?模糊匹配尽可能多地保留父级布局,用户能借助保留的上下文导航到有用位置,而不是直接跌落到一个与当前位置毫无关联的全新页面。

"最近合适路由"的查找标准为:该路由必须配置了notFoundComponent,或者路由器配置了defaultNotFoundComponent

例如,给定如下路由树:

  • __root__(配置了notFoundComponent
    • posts(配置了notFoundComponent
      • $postId(配置了notFoundComponent

若访问/posts/1/edit,将渲染如下组件结构:

<Root> <Posts> <Post> <Post.notFoundComponent>

因为$postId最近的、配置了notFoundComponent的匹配路由,所以渲染的是它的notFoundComponent

这一查找逻辑在源码中有精确对应:findGlobalNotFoundRouteId(见 packages/router-core/src/router.ts)从已匹配路由数组的末尾(最深层路由)向前遍历,一旦发现route.options.notFoundComponent就立即返回该路由 id;若没有找到任何配置了notFoundComponent的路由,则回退到具有子路由的最近分支,最终仍无果时才回落到rootRouteId

function findGlobalNotFoundRouteId( notFoundMode: 'root' | 'fuzzy' | undefined, routes: ReadonlyArray<AnyRoute>, ) { if (notFoundMode !== 'root') { let fallback for (let i = routes.length - 1; i >= 0; i--) { const route = routes[i]! if (route.options.notFoundComponent) { return route.id } fallback ||= route.children && route.id } if (fallback) { return fallback } } return rootRouteId }

notFoundMode: 'root'

notFoundMode设为root时,所有 not-found 错误都由根路由的notFoundComponent处理,而不是从最近模糊匹配的路由向上冒泡。以上述同一路由树为例,访问/posts/1/edit时将渲染:

<Root> <Root.notFoundComponent>

因为模式为root,所以渲染的是__root__路由的notFoundComponent。从源码可见,root模式直接跳过最近的模糊匹配遍历,无条件返回rootRouteIdnotFoundMode的默认值在 packages/router-core/src/router.ts 处确认:notFoundMode: options.notFoundMode ?? 'fuzzy'

配置路由的notFoundComponent

为同时处理上述两类 not-found 错误,可以给任意路由挂载notFoundComponent。该组件会在 not-found 错误被抛出时渲染。

例如,为/settings路由配置notFoundComponent,处理不存在的设置子页面:

export const Route = createFileRoute('/settings')({ component: () => { return ( <div> <p>Settings page</p> <Outlet /> </div> ) }, notFoundComponent: () => { return <p>This setting page doesn't exist!</p> }, })

或者为/posts/$postId路由配置notFoundComponent,处理不存在的文章:

export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound() return { post } }, component: ({ post }) => { return ( <div> <h1>{post.title}</h1> <p>{post.body}</p> </div> ) }, notFoundComponent: () => { return <p>Post not found!</p> }, })

注意这里的notFoundComponent不是路由组件,它在渲染时会被wrapInNonRouteComponentContext包裹(见 packages/react-router/src/renderRouteNotFound.tsx),因此不支持渲染<Outlet />,且与普通路由组件的数据访问方式存在差异(详见下文"notFoundComponent中的数据加载"一节)。

全局默认 not-found 处理:defaultNotFoundComponent

你可能会希望为应用中所有带有子路由的路由提供默认的 not-found 组件。

为什么只针对有子路由的路由?叶子路由(无子路由的路由)永远不会渲染<Outlet>,因此无法处理 not-found 错误。

为此,把defaultNotFoundComponent传给createRouter即可:

const router = createRouter({ defaultNotFoundComponent: () => { return ( <div> <p>Not found!</p> <Link to="/">Go home</Link> </div> ) }, })

该选项的声明位于 packages/react-router/src/router.ts。渲染优先级在 packages/react-router/src/renderRouteNotFound.tsx 中清晰可见:

  1. 优先使用路由自身的notFoundComponent
  2. 未配置时回退到路由器级defaultNotFoundComponent
  3. 两者都没有时,回退到 TanStack Router 内置的DefaultGlobalNotFound——即那个刻意保持"极其简陋"(且故意不美观)的默认组件,仅渲染<p>Not Found</p>(实现见 packages/react-router/src/not-found.tsx)。

源码中还会在开发环境打印一条警告,提示开发者为路由配置notFoundComponent或路由器级defaultNotFoundComponent,以避免落到过度通用的默认组件。强烈建议至少为根路由挂载一个notFoundComponent,或配置路由器级defaultNotFoundComponent

手动抛出notFound错误

除了路由自动抛错,你也可以在 loader 和组件中手动抛出 not-found 错误,用于标记"资源不存在"。notFound函数与redirect函数的工作方式类似——抛出notFound()即可触发 not-found 错误

export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { // 文章不存在时返回 null const post = await getPost(postId) if (!post) { throw notFound() // 或者让 notFound 函数自行抛出: // notFound({ throw: true }) } // 走到这里,post 一定有值(因为不存在时已抛出错误) return { post } }, })

从源码实现看(packages/router-core/src/not-found.ts),notFound函数的核心逻辑是给选项对象打上内部标记isNotFound = true,然后根据throw选项决定直接抛出还是返回错误对象;而isNotFound(obj)则用于运行时判别某个值是否是 TanStack Router 的 not-found 错误(obj?.isNotFound === true),这也是CatchNotFound等边界组件判断错误类型的依据:

export function notFound(options: NotFoundError = {}) { ;(options as any).isNotFound = true if (options.throw) throw options return options } export function isNotFound(obj: any): obj is NotFoundError { return obj?.isNotFound === true }

上述手动抛出的 not-found 错误,将由该路由自身或最近的、配置了notFoundComponent路由选项(或defaultNotFoundComponent路由器选项)的父级路由处理。

若既没有找到合适的路由,也没有合适的父级路由处理该错误,则根路由会使用 TanStack Router 内置的极简默认 not-found 组件(仅渲染<p>Not Found</p>)来处理。

beforeLoad中抛出时的行为:当你(或任何库代码)在beforeLoad中抛出notFound()时,TanStack Router 会像处理其他 not-found 错误一样解析它:

  • 若传入了routeId,由该路由(或最近的合法祖先边界)处理;
  • 若未传入routeId,由最近的、配置了notFoundComponent的路由/祖先处理(取决于路由器的模式和匹配规则);
  • 若找不到合适的边界,则回退到根路由/默认 not-found 行为。

对于beforeLoad中抛出的 not-found 错误,TanStack Router 仍会运行必需的父级 loader,以确保选中的 not-found 边界能带着它依赖的 loader 数据正常渲染。

指定哪些路由处理 not-found 错误

有时你希望在某个特定的父级路由上触发 not-found,并绕过常规的 not-found 组件传播逻辑。此时可在notFound函数的route选项中传入目标路由 id:

// _pathlessLayout.tsx export const Route = createFileRoute('/_pathlessLayout')({ // 这个会渲染 notFoundComponent: () => { return <p>Not found (in _pathlessLayout)</p> }, component: () => { return ( <div> <p>This is a pathless layout route!</p> <Outlet /> </div> ) }, }) // _pathlessLayout/route-a.tsx export const Route = createFileRoute('/_pathless/route-a')({ loader: async () => { // 让 LayoutRoute 处理这个 not-found 错误 throw notFound({ routeId: '/_pathlessLayout' }) // ^^^^^^^^^ 会从注册的 router 中自动补全 }, // 这个不会渲染 notFoundComponent: () => { return <p>Not found (in _pathlessLayout/route-a)</p> }, })

routeId的类型为RouteIds<RegisteredRouter['routeTree']>(见 packages/router-core/src/not-found.ts),在配置了注册路由器的项目中,该字段会获得完整的类型自动补全与校验。

手动指定根路由

你也可以通过向notFound函数的route属性传入导出的rootRouteId变量来指定根路由:

export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound({ routeId: rootRouteId }) return { post } }, })

rootRouteId由 packages/router-core/src/root.ts 导出,并经由 packages/router-core/src/index.ts 对外暴露。值得留意的是,NotFoundError类型中有一个@deprecatedglobal选项(见 packages/router-core/src/not-found.ts),其注释明确建议改用routeId: rootRouteId——说明旧式的"全局 404"概念已被显式路由定位取代。

在组件中抛出 not-found 错误

你也可以在组件中抛出 not-found 错误。不过,官方建议优先在 loader 中抛出,以便正确推导 loader 数据类型并避免闪烁(flickering)。

TanStack Router 提供了与CatchBoundary类似的CatchNotFound组件,用于在组件中捕获 not-found 错误并展示对应 UI。其实现位于 packages/react-router/src/not-found.tsx:内部复用CatchBoundary,在onCatch与错误渲染分支中通过isNotFound(error)判断错误类型——若是 not-found 错误则执行fallback/onCatch,否则原样重新抛出;同时基于pathname与路由状态生成resetKey,确保导航到新路径后边界能够正确重置。

import { CatchNotFound } from '@tanstack/react-router' function Posts() { return ( <CatchNotFound fallback={(error) => <p>Post not found.</p>}> <PostDetail /> </CatchNotFound> ) }

该组件已从 packages/react-router/src/index.tsx 对外导出(同时导出的还有DefaultGlobalNotFound)。

notFoundComponent中的数据加载

notFoundComponent在数据加载方面是一个特例:SomeRoute.useLoaderData可能未定义,具体取决于你正在访问哪个路由、以及 not-found 错误是在哪里抛出的。而Route.useParamsRoute.useSearchRoute.useRouteContext等 Hook 会返回确定的值。

若需要把不完整的 loader 数据传给notFoundComponent,可通过notFound函数中的data选项传递,并在notFoundComponent中自行校验:

export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound({ // 把部分数据转发给 notFoundComponent // data: someIncompleteLoaderData }) return { post } }, // 调用 notFound 时通过 data 选项传入的数据会以 { data } 形式到达组件 notFoundComponent: ({ data }) => { // ❌ 这里不能使用 useLoaderData:const { post } = Route.useLoaderData() // ✅ 这些 Hook 是安全的: const { postId } = Route.useParams() const search = Route.useSearch() const context = Route.useRouteContext() return <p>Post with id {postId} not found!</p> }, })

对应的data字段定义在 packages/router-core/src/not-found.ts,类型为anynotFoundComponent渲染时该数据会作为 props 传入(见 packages/react-router/src/renderRouteNotFound.tsx)。注意NotFoundError中还支持headers?: HeadersInit字段,可用于在 SSR 等场景下附带响应头信息。

与 SSR 的配合

not-found 错误处理在服务端渲染(SSR)场景下同样生效:路由匹配失败或 loader 中抛出的notFound()会在服务端被捕获,并将状态同步到客户端完成一致的 404 渲染。详细的服务端渲染配置与流程请参阅 SSR 指南。

从源码结构看,服务端的加载流程同样参与了 not-found 判定:notFoundMode相关逻辑同时存在于 packages/router-core/src/load-client.ts、packages/router-core/src/load-server.ts 与 packages/router-core/src/router.ts 中,确保客户端与服务端对"由谁处理 404"的决策保持一致。

NotFoundRoute迁移

NotFoundRouteAPI 已废弃,未来版本将移除,请改用notFoundComponent

重要:使用NotFoundRoute时,notFound函数与notFoundComponent将不会生效——二者是互斥的两套机制。

两者主要差异如下:

对比维度NotFoundRoute(已废弃)notFoundComponent(推荐)
形态一个独立的路由可挂载到任意路由的组件选项
渲染前提父路由必须渲染<Outlet>无此要求,自动插入渲染
布局支持无法使用布局(layout)可与布局(layout)配合使用
路径匹配宽松:/post/1/2/3会匹配NotFoundRoute严格:声明了/post/$postId时访问/post/1/2/3会抛 not-found 错误
Outlet 渲染支持不支持

迁移只需几步修改。以src/router.tsx为例:

import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen.' - import { notFoundRoute } from './notFoundRoute' // [!code --] export const router = createRouter({ routeTree, - notFoundRoute // [!code --] }) // routes/__root.tsx import { createRootRoute } from '@tanstack/react-router' export const Route = createRootRoute({ // ... + notFoundComponent: () => { // [!code ++] + return <p>Not found!</p> // [!code ++] + } // [!code ++] })

迁移中的关键变化:

  • 在根路由上添加notFoundComponent用于全局 not-found 处理;也可以在路由树中的任意其他路由上添加notFoundComponent,以处理该路由专属的 not-found 错误;
  • 移除传给createRouternotFoundRoute选项及其导入;
  • 牢记notFoundComponent不支持渲染<Outlet />,原有依赖NotFoundRoute+<Outlet>的布局结构需要调整为"在父路由的component中保留<Outlet />,同时把 404 展示逻辑迁入notFoundComponent"。

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 22:38:39

Anolis OS 23.4全面支持RISC-V架构的技术解析

1. 项目概述&#xff1a;Anolis OS 23.4的技术突破龙蜥社区最新发布的Anolis OS 23.4版本&#xff0c;标志着国产操作系统在RISC-V生态支持上的重大进展。这个版本最引人注目的特性是完整支持RVA23 RISC-V架构规范&#xff0c;这意味着开发者现在可以在Anolis OS上构建和运行符…

作者头像 李华
网站建设 2026/9/14 22:38:04

知识图谱增强RAG-从Cypher到企业问答

知识图谱 RAG&#xff1a;让大模型读懂实体关系&#xff0c;而非只搜关键词摘要&#xff1a;本文基于 DeepLearning.AI《RAG 的知识图谱》课程实践&#xff0c;系统讲解如何用 Neo4j 知识图谱增强 RAG&#xff1a;从图建模基础&#xff08;节点/关系/属性/标签&#xff09;、S…

作者头像 李华
网站建设 2026/9/14 22:36:49

CD3ε抗体在T细胞研究与免疫治疗中的关键作用

1. CD3ε抗体在T细胞研究中的核心地位CD3ε抗体作为免疫学研究的重要工具&#xff0c;其价值在于能够特异性识别T细胞表面的CD3ε分子。CD3ε是T细胞受体&#xff08;TCR&#xff09;复合物的关键组成部分&#xff0c;与CD3γ、CD3δ和CD3ζ共同构成TCR-CD3复合物。这个复合物在…

作者头像 李华
网站建设 2026/9/14 22:35:48

ArcGIS脚本工具开发全流程与实战技巧

1. ArcGIS脚本工具入门指南作为一名GIS工程师&#xff0c;我使用ArcGIS脚本工具已有8年时间。脚本工具是ArcGIS平台中最高效的自动化解决方案&#xff0c;它允许我们将Python脚本封装成标准的GP工具&#xff0c;实现批量化、流程化的地理数据处理。不同于直接运行Python脚本&am…

作者头像 李华
网站建设 2026/9/14 22:35:37

光热电站N-k安全约束优化调度MATLAB实现

1. 项目背景与核心挑战在可再生能源占比不断提升的现代电力系统中&#xff0c;光热电站&#xff08;Concentrated Solar Power, CSP&#xff09;因其独特的储热能力和灵活调节特性&#xff0c;正成为解决风电、光伏波动性问题的关键技术手段。然而&#xff0c;当我们将光热电站…

作者头像 李华
网站建设 2026/9/14 22:35:25

分数阶极值寻优控制的光伏MPPT策略与Simulink仿真实现

1. 项目概述与设计思路1.1 为什么选择极值寻优控制解决MPPT问题最大功率点跟踪&#xff08;MPPT&#xff09;在光伏发电、风电、燃料电池这些领域是老生常谈的话题了。传统方案里&#xff0c;扰动观察法&#xff08;P&O&#xff09;和电导增量法&#xff08;INC&#xff09…

作者头像 李华