-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Which project does this relate to?
Start
Describe the bug
When using shellComponent on the root route, if the shell function renders <Outlet /> directly (instead of {props.children}), the root's errorComponent never catches errors re-thrown from a child route's errorComponent.
This happens because shellComponent receives the CatchBoundary (which wraps errorComponent) as part of its children prop. When the shell ignores children and renders <Outlet /> directly — which is the natural and common pattern — the CatchBoundary is never placed in the render tree. Re-thrown errors from child errorComponent have no parent boundary to propagate to.
The same route structure works correctly in @tanstack/react-router because React uses the component pattern where CatchBoundary wraps the component (which contains <Outlet />), keeping it in the render tree.
Root route (__root.tsx) — Solid:
export const Route = createRootRoute({
shellComponent: RootShell,
errorComponent: ({ error }) => (
<div data-testid="root-error">Root caught: {error.message}</div>
),
})
function RootShell() {
return (
<html>
<head>...</head>
<body>
<Outlet /> {/* ← renders Outlet directly, bypasses CatchBoundary in children */}
<Scripts />
</body>
</html>
)
}Child route (test-error.tsx) — identical for both Solid and React:
export const Route = createFileRoute('/test-error')({
beforeLoad: () => { throw new Error('CHILD_THREW') },
component: () => <div>never renders</div>,
errorComponent: ({ error }) => {
if (error.message === 'HANDLE_THIS') return <div>handled</div>
throw error // ← re-throw to parent
},
})Your Example Website or App
https://github.com/ljho01/tanstack-solid-router-error-mre
Steps to Reproduce the Bug or Issue
cd solid && pnpm install && pnpm dev- Open
http://localhost:3001/ - Click "Go to /test-error (client nav)"
- Page goes blank — root
errorComponentis never rendered
Compare with React:
cd react && pnpm install && pnpm dev- Open
http://localhost:3002/ - Click "Go to /test-error (client nav)"
- Root
errorComponentrenders correctly with "Root errorComponent caught: CHILD_THREW"
E2E tests (optional):
# Solid — 2 of 3 FAIL
cd solid && pnpm install && npx playwright install chromium && pnpm test:e2e
# React — 3 of 3 PASS
cd react && pnpm install && npx playwright install chromium && pnpm test:e2eExpected behavior
The root route's errorComponent should catch errors re-thrown from a child route's errorComponent, regardless of whether the shell renders <Outlet /> directly or uses {props.children}. This is consistent with how @tanstack/react-router behaves.
Screenshots or Videos
No response
Platform
- Router / Start Version: @tanstack/solid-router 1.166.2, @tanstack/solid-start 1.166.2
- OS: macOS 15.4
- Browser: Chrome 135
- Bundler: Vite 7.3.1
Additional context
Root cause in source (@tanstack/solid-router/dist/esm/Match.js):
In the Outlet component, when routeId() === rootRouteId, the child <Match> is rendered inside a <Suspense> but not inside a CatchBoundary. The root's CatchBoundary only exists as a child of ShellComponent (passed via props.children), so if the shell doesn't render its children, the boundary is absent.
A possible fix is to wrap the child <Match> inside the Outlet with the root's CatchBoundary when shellComponent is in use:
// In Outlet, when routeId() === rootRouteId:
const rootRoute = () => router.routesById[rootRouteId];
const needsRootCatchBoundary = () =>
routeId() === rootRouteId && !!rootRoute().options.shellComponent;
// Wrap child match with CatchBoundary if shellComponent bypasses children
if (needsRootCatchBoundary() && rootErrorComponent()) {
return createComponent(CatchBoundary, {
getResetKey: () => resetKey(),
errorComponent: rootErrorComponent(),
onCatch: (error) => { /* ... */ },
children: childMatch(),
});
}The repo includes a working patch in the linked reproduction.