# Error Handling

Rendering errors can be caught by the nearest
[React Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)
but a boundary's fallback only renders in the browser. To server-render the error UI itself, Rebask
provides `<NotFound>` and `<ServerError>`, which render inline and set the response status when
headers haven't been sent yet.

## Two kinds of failures

| Failure          | Handled by                                                        | Default  |
| ---------------- | ----------------------------------------------------------------- | -------- |
| Not Found        | `<NotFound>` in a page or layout, or a catch-all not found page   | 404 page |
| Unexpected error | `try/catch` in the page, or an `<ErrorBoundary>` client component | 500 page |

## `<NotFound>`

Return `<NotFound>` from a server component when its data is missing. The response gets a 404
status when headers haven't been sent yet:

```tsx
import { NotFound } from "@rebask/core/components/errors"

const product = await getProduct(id)

if (!product) {
	return (
		<NotFound>
			<p>The product is not available.</p>
		</NotFound>
	)
}
```

A page that renders `<NotFound>` can still respond with a 200 if streaming already started, but it's
cached so subsequent visits will see a 404. It also emits a noindex meta tag, and the response
includes `x-robots-tag: noindex` when headers haven't been sent.

> `<NotFound>` renders its `children`, its job is to mark the response as a 404 so it adds no
> wrapper element of its own.

## Unmatched URLs

To render a custom 404 for URLs that match no page, add a
[catch-all page](./routing/introduction.md#dynamic-segments) and mark it as a not-found route:

```tsx
// app/[...slug]/page.tsx
export const notFound = true

export default function NotFoundPage() {
	return <p>Page not found</p>
}
```

`export const notFound = true` renders the page exactly like any other page. The only difference is
caching: Rebask never writes a pathname entry for it, serving one
[shared shell](./caching/page-caching.md#page-cache-layers-shared-shell) for every URL plus one intermediate shell per
[`prerenderParams`](./routing/introduction.md#prerenderparams) combination.

The response always carries a 404 and `x-robots-tag: noindex`, the page can read matched params with
[`routeParams()`](./server-runtime-apis.md#routeparams), and placing the catch-all deeper (like
`blog/[...slug]`) limits it to that subtree.

`<NotFound>` and the `notFound` catch-all page solve different problems. `<NotFound>` is returned
from a page that _did_ match, when its data turns out to be missing, the page still renders and
caches like normal. The catch-all page is the fallback for URLs that match _no_ other page.

## `<ServerError>`

Return `<ServerError>` from a server component when something fails and you want to render your own
error UI. The response gets an error status (when headers haven't been sent yet), and the page stays
out of the cache:

```tsx
import { ServerError } from "@rebask/core/components/errors"

export default async function Page() {
	try {
		const product = await getProduct(id)
		return <Content product={product} />
	} catch (error) {
		console.error(error)
		return (
			<ServerError status={500}>
				<p>An error happened</p>
			</ServerError>
		)
	}
}
```

It receives `status` as a prop, which defaults to `500`. Use another error status such as `503` when
appropriate. It also emits a noindex meta tag.

> `<ServerError>` renders its `children`, its job is to mark the response as an error so it adds no
> wrapper element of its own.

## `<ErrorBoundary>`

`<ErrorBoundary>` handles rendering errors in the browser:

```tsx
// app/layout.tsx
import { ErrorBoundary } from "@rebask/core/components/error-boundary"

<ErrorBoundary fallback={<p>Something went wrong</p>}>{children}</ErrorBoundary>
```

It does not catch errors from event handlers or code that runs outside rendering, such as timers or
unawaited promises. Browser errors throw directly while server errors are serialized and sent to the
browser so the nearest boundary catches both.

Users can recover from an error by navigating away, and revisiting a failed page retries it. Back
and forward navigations restore history as it was.
