Error Boundaries

Handle errors with error.tsx - catches errors in the child tree and shows fallback UI. Layouts stay visible.

Overview

Every layout and page is wrapped in an error boundary that resets on navigation. Add error.tsx in a folder for custom fallback UI. It is a special file - it does not create a URL. Nearest-wins applies.

error.tsx
export default function Error({ error, reset}) {
  return (
    <>
      An error occurred: {error.message}
      <button onClick={() => reset()}>Retry</button>
    </>
  );
}
Component hierarchy
<Layout>
  <ErrorBoundary fallback={<Error />}>
    <Page />
  </ErrorBoundary>
</Layout>
Error...
FileCreates URL?Purpose
app/error.tsxNo - special fileFallback for routes without a closer error file
app/dashboard/error.tsxNo - special fileDashboard segment only
app/blog/[slug]/error.tsxNo - special fileBlog post segment only
Layouts stay mounted. The error UI replaces the page (or segment) inside the boundary - not the whole app shell.

What are Error Boundaries?

Error boundaries catch JavaScript errors in the child tree, log them, and show fallback UI instead of a crashed tree. In Bini.js you use error.tsx.

They catch errors during rendering and in the tree below them. Boundaries also reset automatically when the pathname changes.

error.tsx does not create a URL - same idea as loading.tsx. Closest file to the error wins.

Creating an Error Boundary

Create error.tsx in any folder for that route and its children.

app
layout.tsx
page.tsx
dashboard
layout.tsx
page.tsx
error.tsx
/
/dashboard
app/dashboard/error.tsx
export default function DashboardError({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div className="mx-auto max-w-2xl p-6">
      <div className="rounded-lg border border-neutral-200 p-6 dark:border-neutral-800">
        <h2 className="mb-2 text-xl font-bold text-black dark:text-white">
          Something went wrong
        </h2>
        <p className="mb-4 text-neutral-600 dark:text-neutral-400">
          {error.message}
        </p>
        <button
          onClick={reset}
          className="rounded-lg bg-black px-4 py-2 font-medium text-white dark:bg-white dark:text-black"
        >
          Try again
        </button>
      </div>
    </div>
  )
}

Error Props

error.tsx receives two props.

PropTypeDescription
errorErrorThrown Error object with message and stack
reset() => voidClears error state and re-renders children
app/dashboard/error.tsx
export default function DashboardError({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  console.error('Dashboard error:', error)

  return (
    <div>
      <h2>Something went wrong!</h2>
      <details className="mt-4 rounded border border-neutral-200 bg-neutral-50 p-4 dark:border-neutral-800 dark:bg-neutral-900">
        <summary className="cursor-pointer">Error details</summary>
        <pre className="mt-2 whitespace-pre-wrap text-xs">{error.stack}</pre>
      </details>
      <button
        onClick={reset}
        className="mt-4 rounded bg-black px-4 py-2 text-white dark:bg-white dark:text-black"
      >
        Try again
      </button>
    </div>
  )
}

Nested Error Boundaries

Place error.tsx in subdirectories. Each only catches errors in its subtree.

app
error.tsx
layout.tsx
page.tsx
blog
error.tsx
page.tsx
[slug]
page.tsx
dashboard
error.tsx
page.tsx
settings
error.tsx
page.tsx
/
/blog
/blog/:slug
/dashboard
/dashboard/settings
RouteError Boundary Used
/blog/hello-worldapp/blog/error.tsx
/dashboardapp/dashboard/error.tsx
/dashboard/settingsapp/dashboard/settings/error.tsx
/aboutapp/error.tsx (global fallback)

Nearest Wins Resolution

The closest error.tsx to the route where the error occurred is used.

  1. Check the route's own folder for error.tsx
  2. If not found, walk up parent folders
  3. If still not found, use the built-in fallback

Error with Layout

Error UI is shown inside the layout hierarchy. Headers, sidebars, and nav stay visible when a child route errors.

app
layout.tsx
error.tsx
blog
layout.tsx
error.tsx
page.tsx
/blog
Error UI replaces only the page (or segment) - not the surrounding layout.

Built-in Fallback

If no error.tsx exists in scope, Bini.js uses a built-in fallback.

  • Development: Renders nothing so Vite / bini-overlay can show the error
  • Production: Generic "Something went wrong" UI with a retry button
  • Logging: Runtime errors dispatch a __bini_error__ CustomEvent on window for external overlays
Custom error UIs are recommended for production. Boundaries also reset when the pathname changes.

Complete Example

Special files do not create URLs. Pages do.

app
layout.tsx
error.tsx
page.tsx
blog
layout.tsx
error.tsx
page.tsx
dashboard
layout.tsx
error.tsx
page.tsx
/
/blog
/dashboard
FileCreates URL?Role
error.tsxNoSegment error boundary
layout.tsxNoWraps segment + children
page.tsxYesRoute content
loading.tsxNoSuspense fallback
Was this helpful?