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.
export default function Error({ error, reset}) {
return (
<>
An error occurred: {error.message}
<button onClick={() => reset()}>Retry</button>
</>
);
}<Layout>
<ErrorBoundary fallback={<Error />}>
<Page />
</ErrorBoundary>
</Layout>| File | Creates URL? | Purpose |
|---|---|---|
| app/error.tsx | No - special file | Fallback for routes without a closer error file |
| app/dashboard/error.tsx | No - special file | Dashboard segment only |
| app/blog/[slug]/error.tsx | No - special file | Blog post segment only |
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.
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.
| Prop | Type | Description |
|---|---|---|
| error | Error | Thrown Error object with message and stack |
| reset | () => void | Clears error state and re-renders children |
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.
| Route | Error Boundary Used |
|---|---|
| /blog/hello-world | app/blog/error.tsx |
| /dashboard | app/dashboard/error.tsx |
| /dashboard/settings | app/dashboard/settings/error.tsx |
| /about | app/error.tsx (global fallback) |
Nearest Wins Resolution
The closest error.tsx to the route where the error occurred is used.
- Check the route's own folder for
error.tsx - If not found, walk up parent folders
- 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.
Built-in Fallback
If no error.tsx exists in scope, Bini.js uses a built-in fallback.
- Development: Renders nothing so Vite /
bini-overlaycan show the error - Production: Generic "Something went wrong" UI with a retry button
- Logging: Runtime errors dispatch a
__bini_error__CustomEvent onwindowfor external overlays
Complete Example
Special files do not create URLs. Pages do.
| File | Creates URL? | Role |
|---|---|---|
| error.tsx | No | Segment error boundary |
| layout.tsx | No | Wraps segment + children |
| page.tsx | Yes | Route content |
| loading.tsx | No | Suspense fallback |