Error Boundaries

Learn how to handle errors gracefully with error boundaries in Bini.js.

What are Error Boundaries?

Error boundaries are React components that catch JavaScript errors in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. In Bini.js, you can create error boundaries using the error.tsx file.

Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Creating an Error Boundary

Create an error.tsx file in any folder to define an error boundary for that route and its children.

src/app/
├── layout.tsx
├── page.tsx
└── dashboard/
    ├── layout.tsx
    ├── page.tsx
    └── error.tsx           ← Error boundary for /dashboard/*
app/dashboard/error.tsx
// src/app/dashboard/error.tsx
export default function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div className="p-6 max-w-2xl mx-auto">
      <div className="bg-red-500/10 border border-red-500/30 rounded-lg p-6">
        <h2 className="text-xl font-bold text-white mb-2">Something went wrong</h2>
        <p className="text-red-400 mb-4">{error.message}</p>
        <button 
          onClick={reset}
          className="px-4 py-2 bg-cyan-500 text-black font-medium rounded-lg hover:bg-cyan-400 transition-colors"
        >
          Try again
        </button>
      </div>
    </div>
  )
}

Error Props

The error.tsx component receives two props:

PropTypeDescription
errorErrorThe thrown Error object with message and stack trace
reset() => voidA function that clears the error state and re-renders children
app/dashboard/error.tsx
// src/app/dashboard/error.tsx
export default function DashboardError({ 
  error, 
  reset 
}: { 
  error: Error; 
  reset: () => void 
}) {
  // Log the error to your error reporting service
  console.error('Dashboard error:', error)
  
  return (
    <div>
      <h2>Something went wrong!</h2>
      <details className="mt-4 p-4 bg-slate-800 rounded">
        <summary className="cursor-pointer text-slate-300">Error details</summary>
        <pre className="mt-2 text-xs text-red-400 whitespace-pre-wrap">
          {error.stack}
        </pre>
      </details>
      <button 
        onClick={reset}
        className="mt-4 px-4 py-2 bg-cyan-500 text-black rounded"
      >
        Try again
      </button>
    </div>
  )
}

Nested Error Boundaries

You can create nested error boundaries by placing error.tsx in subdirectories. Each error boundary only catches errors in its subtree.

src/app/
├── error.tsx                 ← Global error boundary (fallback)
├── layout.tsx
├── page.tsx
├── blog/
│   ├── error.tsx             ← Blog error boundary
│   ├── page.tsx
│   └── [slug]/
│       └── page.tsx
└── dashboard/
    ├── error.tsx             ← Dashboard error boundary
    ├── page.tsx
    └── settings/
        ├── error.tsx         ← Settings error boundary
        └── page.tsx
RouteError Boundary Used
/blog/hello-worldapp/blog/error.tsx
/dashboardapp/dashboard/error.tsx
/dashboard/settingsapp/dashboard/settings/error.tsx
/aboutapp/error.tsx (global)

Nearest Wins Resolution

Error boundaries use "nearest wins" resolution. The closest error.tsx to the route where the error occurred is used.

src/app/
├── error.tsx                 ← Fallback for any error not caught below
├── layout.tsx
├── page.tsx
├── blog/
│   ├── error.tsx             ← Catches errors in /blog/*
│   ├── page.tsx
│   └── [slug]/
│       ├── error.tsx         ← Catches errors in /blog/:slug
│       └── page.tsx
└── dashboard/
    ├── error.tsx             ← Catches errors in /dashboard/*
    └── page.tsx

When an error occurs:

  1. Check the route's own folder for error.tsx
  2. If not found, check each parent folder (going up)
  3. If still not found, use the built-in fallback

Error with Layout

Error boundaries are rendered inside the layout hierarchy. Layouts remain visible when an error occurs in a child route.

src/app/
├── layout.tsx                 ← Root layout (always visible)
├── error.tsx                  ← Global error (shown inside root layout)
└── blog/
    ├── layout.tsx             ← Blog layout (always visible)
    ├── error.tsx              ← Blog error (shown inside blog layout)
    └── page.tsx

This allows you to keep navigation, headers, and sidebars visible even when an error occurs in the main content area.

Built-in Fallback

If no error.tsx exists in the hierarchy, Bini.js uses a built-in fallback:

  • Development: Renders null so bini-overlay takes over with an animated error badge and full error panel
  • Production: Shows a generic "Something went wrong" UI with a "Try again" button
  • Error logging: Errors are dispatched as a __bini_error__ CustomEvent on window for external dev overlays

Creating custom error boundaries is recommended for production applications to provide a better user experience.