Skip to content
Ilha
Esc
↑↓navigate↵open⌘Jpreview
On this page

Error boundaries

Catch render failures with +error.tsx boundaries, errorBoundary(), and wrapError().

A render failure in a page or layout does not take down the app. The nearest error boundary catches it and renders a fallback view.

Throw from a page

Throw RouteError with error() (or redirect() for navigation):

import { const error: (status: number, message: string) => nevererror } from "@ilha/router";

export default async function 
function User({ params }: {
    params: {
        id: string;
    };
}): Promise<View>
User
({
params: {
    id: string;
}
params
}: {
params: {
    id: string;
}
params
: { id: stringid: string } }) {
const
const user: {
    name: string;
} | null
user
= await
function findUser(_id: string): Promise<{
    name: string;
} | null>
findUser
(
params: {
    id: string;
}
params
.id: stringid);
if (!
const user: {
    name: string;
} | null
user
) function error(status: number, message: string): nevererror(404, "No such user");
return <JSX.IntrinsicElements.h1: HTMLAttributes<HTMLHeadingElement>h1>{
const user: {
    name: string;
}
user
.name: stringname}</JSX.IntrinsicElements.h1: HTMLAttributes<HTMLHeadingElement>h1>;
} async function
function findUser(_id: string): Promise<{
    name: string;
} | null>
findUser
(_id: string_id: string) {
return null as null | { name: stringname: string }; }

error(status, message) throws a RouteError; redirect(to, status?) throws a Redirect that the router converts into a client navigation.

+error.tsx files

Drop a +error.tsx next to a page (or in a parent folder) to catch failures for everything under that folder. The nearest boundary wins:

src/pages/
  +error.tsx          ← catches all routes
  user/
    +error.tsx        ← catches /user/*
    [id].server.tsx
// src/pages/user/+error.tsx
import type { AppError } from "@ilha/router";
import type { type View = VNode | UnsafeHtml | JsonText | AtomHandle<unknown> | Stream<View, unknown, unknown> | Component | View[] | Iterable<View>View } from "ilha";

export default function 
function ErrorPage({ error, children, }: {
    error: AppError;
    children?: View;
}): View
ErrorPage
({
error: AppErrorerror, children: Viewchildren, }: { error: AppErrorerror: AppError; children?: Viewchildren?: type View = VNode | UnsafeHtml | JsonText | AtomHandle<unknown> | Stream<View, unknown, unknown> | Component | View[] | Iterable<View>View; }) { return ( <JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div class?: Attrclass="card bg-base-100 shadow"> <JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div class?: Attrclass="card-body"> <JSX.IntrinsicElements.h2: HTMLAttributes<HTMLHeadingElement>h2 class?: Attrclass="card-title">{error: AppErrorerror.AppError.status?: number | undefinedstatus ?? 500}</JSX.IntrinsicElements.h2: HTMLAttributes<HTMLHeadingElement>h2> <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{error: AppErrorerror.AppError.message: stringmessage}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p> {children: Viewchildren} </JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> </JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> ); }

Boundary components receive the error and the failed route as children.

errorBoundary() on the builder

With a hand-built router, register a boundary per pattern:

function router(options?: RouterOptions): RouterBuilderrouter()
  .
RouterBuilder.route: (pattern: string, page: Page, opts?: {
    layouts?: LayoutHandler[];
}) => RouterBuilder
route
("/user/:id", const UserPage: () => Promise<never>UserPage)
.RouterBuilder.errorBoundary: (pattern: string, handler: ErrorHandler) => RouterBuildererrorBoundary("/user/:id", (err: AppErrorerr) => function h(type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null | undefined, ...rest: View[]): VNodeh("p", null, `Oops: ${err: AppErrorerr.AppError.message: stringmessage}`));

The handler receives an AppError (message, status?) and a route snapshot, and returns a view or a component.

wrapError()

Wrap a single page when you want the fallback inline instead of a boundary file:

export default function wrapError(handler: ErrorHandler, page: Page): PagewrapError(({ message: stringmessage }) => function h(type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null | undefined, ...rest: View[]): VNodeh("p", null, message: stringmessage), const UserPage: () => Promise<never>UserPage);

wrapError catches everything the page throws — RouteError or any exception — and calls your handler with { message, status? }.

Topic Guide
Throwing redirects Routes and navigation
Server pages and frames Server islands
Hardening and status codes Middleware and security

Was this page helpful?