Capítulo 50 de 57

Chapter 50: Not Found Errors

Core Idea

Not-found errors cover two cases (non-matching pathnames and missing resources), both handled through the same notFound() function and notFoundComponent route option; which route renders the not-found UI depends on the router's notFoundMode ('fuzzy' by default, or 'root') and, for manually thrown errors, an optional target routeId.

Key Concepts

  • Two triggers for not-found: (1) a pathname that doesn't match any route or has extra trailing segments (thrown automatically by the router), and (2) a missing resource (post/data not found), which the developer must throw manually via notFound() in beforeLoad/loader.
  • notFoundMode: 'fuzzy' (default): The router renders the notFoundComponent of the nearest matching route in the hierarchy that has one configured, preserving as much parent layout as possible.
  • notFoundMode: 'root': All not-found errors bubble up to the root route's notFoundComponent, regardless of how deep the match was.
  • notFoundComponent: A per-route option rendered when a not-found error is thrown; unlike the deprecated NotFoundRoute, it works with layouts and requires strict path matching.
  • defaultNotFoundComponent: A router-wide fallback (passed to createRouter) used for any route with children that lacks its own notFoundComponent; leaf routes never render an Outlet so they can't use this fallback.
  • notFound() function: Thrown (like redirect()) inside a loader to signal a missing resource: throw notFound(). Accepts a routeId to target a specific ancestor route's boundary, or rootRouteId to target the root explicitly.
  • notFound({ data }): Forwards partial data to notFoundComponent since useLoaderData isn't guaranteed available there; Route.useParams/useSearch/useRouteContext remain accessible.
  • CatchNotFound: Component for catching not-found errors thrown from within components (loader-thrown is still preferred to avoid flicker and preserve typed loader data).
  • beforeLoad-thrown not-found: Still runs required parent loaders so the chosen not-found boundary has the loader data it needs to render.

Code Examples

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post) throw notFound()
    return { post }
  },
  notFoundComponent: () => <p>Post not found!</p>,
})
  • What it demonstrates: The standard pattern of throwing notFound() from a loader when a resource is missing.
// _pathlessLayout/route-a.tsx
export const Route = createFileRoute('/_pathless/route-a')({
  loader: async () => {
    throw notFound({ routeId: '/_pathlessLayout' })
  },
})
  • What it demonstrates: Targeting a specific ancestor route's notFoundComponent by routeId, bypassing normal fuzzy propagation.

Key Takeaways

  1. Always configure at least one notFoundComponent (root route or defaultNotFoundComponent), the ultimate fallback is a bare <p>Not Found</p> which is intentionally undesirable.
  2. Prefer throwing notFound() in loader over components, this keeps loader data correctly typed and avoids UI flicker.
  3. Use notFoundMode: 'root' when you want a single consistent not-found page; keep the default 'fuzzy' when preserving parent layout context helps users navigate back.
  4. The legacy NotFoundRoute class is deprecated, requires an <Outlet> on the parent, doesn't support layouts, and is incompatible with notFound()/notFoundComponent; migrate to notFoundComponent.

Connects To

  • Ch 44: Creating a Router, where notFoundComponent/defaultNotFoundComponent are initially configured.
  • Ch 51: Authenticated Routes, beforeLoad is the same hook used for both auth redirects and not-found checks.
  • Concept: redirect(), the sibling throwable-error mechanism with parallel API design (isRedirect/isNotFound).