Error Pages
Davaux lets you replace the default error responses with custom JSX pages. Create a _error.tsx file in any route directory to handle errors for that scope.
Creating an error page
Export a handler via defineError. It receives an ErrorPageProps object and must return JSX:
// src/routes/_error.tsx
import { defineError } from 'davaux'
import type { ErrorPageProps } from 'davaux'
export default defineError(({ status, message, ctx }: ErrorPageProps) => {
ctx.head.title = `${status} — Error`
return (
<main class="error-page">
<h1>{status}</h1>
<p>{message}</p>
<a href="/">Go home</a>
</main>
)
})
ErrorPageProps
| Property | Type | Description |
|---|---|---|
status | number | HTTP status code (404, 500, etc.) |
message | string | Human-readable error description |
ctx | RequestContext | Full request context (for cookies, head, etc.) |
Scope and inheritance
_error.tsx applies to all routes at the same directory level and below. A root-level _error.tsx catches everything:
src/routes/
├── _error.tsx ← catches all unhandled errors
├── _layout.tsx
├── index.page.tsx
└── admin/
├── _error.tsx ← overrides for /admin/* only
└── dashboard.page.tsx
If the route that threw is inside admin/ and that directory has its own _error.tsx, the admin error page is used. Otherwise the root one applies.
Error types
404 Not Found
Triggered when no route matches the incoming URL. The error page receives status: 404 and a generic not-found message.
export default defineError(({ status, message, ctx }) => {
if (status === 404) {
ctx.head.title = 'Page Not Found'
return (
<main>
<h1>Page not found</h1>
<p>The page you were looking for does not exist.</p>
<a href="/">Return to home</a>
</main>
)
}
// Fallback for 500 and other errors
ctx.head.title = 'Something went wrong'
return (
<main>
<h1>Something went wrong</h1>
<p>An unexpected error occurred. Please try again.</p>
</main>
)
})
500 Internal Server Error
Triggered when a route handler or middleware throws an unhandled exception. In development, Davaux includes the error stack trace in the message prop. In production it is a generic message to avoid leaking internals.
Custom status codes
Handlers can throw errors with specific status codes:
import { defineError as defineHandlerError } from 'davaux'
// In any handler or middleware:
throw Object.assign(new Error('Forbidden'), { status: 403 })
The error page receives status: 403 and the error message.
Layouts wrap error pages
Error pages go through the same layout chain as normal pages. This means your site navigation, header, and footer appear on error pages automatically — no need to duplicate the HTML shell.
The layout receives the error page's JSX as children, just as it does for normal pages.
Fallback behavior
If the error page itself throws (a bug in your _error.tsx), Davaux falls back to a plain-text response rather than entering an infinite error loop:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Internal Server Error
This ensures the server always responds, even in degenerate cases.
Custom 404 with search
A more complete example that suggests similar pages:
// src/routes/_error.tsx
import { defineError } from 'davaux'
import type { ErrorPageProps } from 'davaux'
export default defineError(async ({ status, ctx }: ErrorPageProps) => {
ctx.head.title = status === 404 ? 'Not Found' : 'Error'
if (status !== 404) {
return (
<main class="error-page">
<h1>Something went wrong</h1>
<p>We had trouble processing your request. Please try again later.</p>
<a href="/">Home</a>
</main>
)
}
const path = ctx.url.pathname
return (
<main class="error-page not-found">
<div class="error-code">404</div>
<h1>Page not found</h1>
<p>
No page exists at <code>{path}</code>. It may have moved or been deleted.
</p>
<nav class="error-nav">
<a href="/">Home</a>
<a href="/getting-started">Documentation</a>
</nav>
</main>
)
})