Domphy

Error Handling & Not Found

Route error component

Each route can define its own errorComponent — rendered when the route's loader throws:

import { createRoute } from "@domphy/router"

const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/posts/$id",
  loader: async ({ params }) => {
    const post = await fetchPost(params.id)
    if (!post) throw new Error("Post not found")
    return post
  },
  component: () => PostPage,
  errorComponent: ({ error }) => ({
    div: [
      { h2: "Something went wrong" },
      { p: (error as Error).message },
      { button: "Go back", onClick: () => history.back() },
    ],
  }),
})

errorComponent receives { error: unknown; reset: () => void }.

Global error boundary

Set a global defaultErrorComponent on the router to catch all unhandled errors:

const router = createRouter({
  routeTree,
  defaultErrorComponent: ({ error, reset }) => ({
    div: [
      { h1: "Unexpected error" },
      { pre: (error as Error).message, style: { fontSize: "0.875rem" } },
      { button: "Try again", onClick: reset },
    ],
    style: { padding: "2rem", textAlign: "center" },
  }),
})

reset re-runs the failed route transition — useful after fixing a transient error.

Not-found errors

When a resource doesn't exist (404-equivalent), throw notFound() from a loader instead of returning null:

import { createRoute, notFound } from "@domphy/router"

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/users/$id",
  loader: async ({ params }) => {
    const user = await fetchUser(params.id)
    if (!user) throw notFound()   // ← renders notFoundComponent, not errorComponent
    return user
  },
  component: () => UserProfile,
  notFoundComponent: () => ({
    div: [
      { h1: "User not found" },
      { a: "Browse users", href: "/users" },
    ],
  }),
})

Global not-found route

Catch all unmatched paths:

const rootRoute = createRootRoute({
  notFoundComponent: () => ({
    div: [
      { h1: "404 — Page not found" },
      { p: "The page you're looking for doesn't exist." },
      { a: "Back to home", href: "/" },
    ],
    style: { padding: "4rem", textAlign: "center" },
  }),
})

Or use createRoute with path *:

const catchAll = createRoute({
  getParentRoute: () => rootRoute,
  path: "*",
  component: () => NotFoundPage,
})

Error vs. not-found — when to use which

ScenarioUse
Server error (5xx), network failurethrow new Error(...)errorComponent
Resource doesn't exist (404)throw notFound()notFoundComponent
User not authorized (401/403)throw redirect(...) → redirects to login
Missing required search paramthrow new Error(...) or throw notFound() depending on UX

Retrying after error

The reset function in errorComponent re-runs the route:

const errorBoundary = ({ error, reset }) => ({
  div: [
    { p: `Error: ${(error as Error).message}` },
    {
      button: "Retry",
      onClick: async () => {
        // Optional: clear the cache before retrying
        await client.invalidateQueries()
        reset()
      },
    },
  ],
})

Handling loader errors globally

Log all route errors centrally:

const router = createRouter({
  routeTree,
  onError: (error) => {
    console.error("Route error:", error)
    errorReporter.capture(error)
  },
})

TypeScript: typed errors

When your loaders throw typed errors, narrow in errorComponent:

class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message)
  }
}

const route = createRoute({
  loader: async () => {
    const res = await fetch("/api/data")
    if (!res.ok) throw new ApiError(res.status, await res.text())
    return res.json()
  },
  errorComponent: ({ error }) => {
    if (error instanceof ApiError) {
      return { div: `API Error ${error.status}: ${error.message}` }
    }
    return { div: "Unknown error" }
  },
})

Pending component during error recovery

While a retry is in flight, show a pending state:

const route = createRoute({
  pendingComponent: () => ({ div: "Retrying…" }),
  pendingMinMs: 200,
  errorComponent: ({ reset }) => ({
    div: [
      { p: "Failed to load. " },
      { button: "Retry", onClick: reset },
    ],
  }),
})