Skip to content
Ilha
Esc
navigateopen⌘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 { error } from "@ilha/router";

export default async function User({
  params,
}: {
  params: { id: string };
}) {
  const user = await findUser(params.id);
  if (!user) error(404, "No such user");
  return <h1>{user.name}</h1>;
}

async function findUser(_id: string) {
  return null as null | { name: 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 { View } from "ilha";

export default function ErrorPage({
  error,
  children,
}: {
  error: AppError;
  children?: View;
}) {
  return (
    <div class="card bg-base-100 shadow">
      <div class="card-body">
        <h2 class="card-title">{error.status ?? 500}</h2>
        <p>{error.message}</p>
        {children}
      </div>
    </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:

import { router } from "@ilha/router";
import { h } from "ilha";

const UserPage = async () => {
  throw new Error("boom");
};

// ---cut---
router()
  .route("/user/:id", UserPage)
  .errorBoundary("/user/:id", (err) =>
    h("p", null, `Oops: ${err.message}`),
  );

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:

import { error, wrapError } from "@ilha/router";
import { h } from "ilha";

const UserPage = async () => {
  error(418, "short and stout");
};

// ---cut---
export default wrapError(
  ({ message }) => h("p", null, message),
  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?