Error & 404 pages
Every app generated by jorvel generate host|remote ships three files that handle runtime failure and unmatched routes. They are part of your app — not framework dependencies — so editing them is the supported customization path.
Why ship these by default?
Scaffolded files
apps/<name>/
└── src/
├── error-boundary.tsx # Top-level React error boundary
└── pages/
├── _error.tsx # Crash screen — dev stack vs prod message
├── _404.tsx # 404 page rendered for unknown URLs
└── README.md # How to overrideFor --lang js apps the extensions are .jsx instead of .tsx; the rest is identical.
The error boundary
src/error-boundary.tsx is a small class component that catches synchronous render errors anywhere in the tree below it and renders the local ErrorPage from pages/_error.tsx. The generated bootstrap.tsx wraps the entire app in this boundary.
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>
);What the boundary does not catch
- Promise rejections (async
fetchfailures, dynamic imports). - Errors thrown inside event handlers.
- Errors during server-side rendering (caught by the SSR adapter instead).
For unhandled promise rejections, attach a global listener:
window.addEventListener('unhandledrejection', (event) => {
// Forward to your observability sink, e.g. @jorvel/observability.
console.error(event.reason);
});The default error page
pages/_error.tsx renders one of two layouts based on process.env.NODE_ENV:
- Development — full
error.messageas the headline, formattederror.stackin a monospace block, dark/red theme to match standard dev-tools UX. A Try again button calls thereset()prop supplied by the boundary; Go home navigates back to/. - Production — generic, brand-safe message ("We hit an unexpected error"), no stack, no internals. Still keeps the reset + go-home affordances.
Inline stack only in dev
NODE_ENV === 'production'. If you build a custom error page, preserve that branch.The default 404 page
pages/_404.tsx exports NotFoundPage, which takes an optional path prop and renders a centered layout with the offending URL, a "go home" button, and a link back to the docs.
The generated bootstrap.tsx includes a helper matchesAnyHostRoute(pathname, routes) that walks jorvel.routes.host.json and renders NotFoundPage when no route would handle the current URL:
function App() {
const pathname = usePathname();
if (pathname === '/' || pathname === '') {
return <Welcome defaultProjectName="shell" />;
}
if (!matchesAnyHostRoute(pathname, HOST_ROUTES)) {
return <NotFoundPage path={pathname} />;
}
return <RemoteOutlet routes={HOST_ROUTES} remotes={REMOTES} />;
}Override patterns
Edit the file directly
Both files are plain React components with zero JORVEL imports. Open src/pages/_error.tsx or src/pages/_404.tsx and rewrite the JSX. Nothing else in the framework needs to change.
Swap the component via boundary prop
Keep _error.tsx for the dev surface and supply a different component for production:
import { ErrorBoundary } from './error-boundary';
import { BrandedCrashScreen } from './crash-screen';
<ErrorBoundary
fallback={process.env.NODE_ENV === 'production' ? BrandedCrashScreen : undefined}
>
<App />
</ErrorBoundary>Disable the boundary
Remove the <ErrorBoundary> wrapper in bootstrap.tsx to fall back to React's default behavior (white screen + console error in production). Recommended only when you have a higher-level boundary (e.g. supplied by the SSR adapter) handling the same scope.
Custom 404 logic
The default matchesAnyHostRoute uses prefix matching with /* suffix support. To handle more complex matching (regex, parameter capture beyond /*), replace the helper inline or build your own and render <NotFoundPage> from there.
Testing the pages
Throw inside any rendered component to trigger the boundary:
function BombButton() {
return (
<button onClick={() => { throw new Error('Manual test'); }}>
Break the app
</button>
);
}Visit a URL that no host route handles to trigger the 404:
http://localhost:3000/this-path-does-not-existSee also
- Observability — wire
onError+ RUM beacon so caught errors land in Sentry/OTel/console. - Routing — host vs remote sub-paths.
- SSR — server-side error handling for
renderRouteToString/renderRouteToStream.
