Not Found and Error Boundary
Problem
Not-found pages and data errors need different treatment: one is expected (wrong URL), one is unexpected (thrown exception). Both need centralized handling without special router hooks.
Solution
Use the notFound option in createRouter for unmatched URLs. Wrap await next() in a try/catch middleware for error boundaries.
ts
import { createRouter } from '@vielzeug/wayfinder';
const errorBoundary = async (ctx, next) => {
try {
await next();
} catch (error) {
// Report and redirect on any unhandled error from data().
reportError(error, { path: ctx.pathname });
await ctx.navigate({ path: '/error' }, { replace: true });
}
};
const router = createRouter({
middleware: [errorBoundary],
routes: {
home: {
path: '/',
component: HomePage,
},
error: {
path: '/error',
component: ErrorPage,
},
},
notFound: {
component: NotFoundPage,
},
});For per-route data errors that should render a degraded state instead of redirecting, use onError on the route definition:
ts
const router = createRouter({
routes: {
userDetail: {
path: '/users/:id',
data: async ({ params }) => fetchUser(params.id),
onError: (error) => ({ error, user: null }),
},
},
});When onError returns a value, it becomes match.data and the route renders normally (status: 'idle'). The global error boundary is not triggered.
Pitfalls
- An error thrown inside a middleware function before calling
next()is not caught by the boundary wrapping that same middleware. It propagates toonErrorinstead. - Navigating to
/errorinside the error boundary must not itself throw, or you risk an infinite loop. - If
onErroritself throws, the router falls through tostatus: 'error'as usual and the global boundary fires.