Route with server function as loader that throws `notFound` crashes route on HMR
Which project does this relate to?
Start
Describe the bug
I have a route that declares a server function as loader and a notFoundComponent.
The server function throws notFound().
import { createFileRoute, notFound } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
const loaderServerFn = createServerFn().handler(() => {
throw notFound();
});
export const Route = createFileRoute("/")({
loader: async () => await loaderServerFn(),
component: RouteComponent,
notFoundComponent: NotFoundComponent,
});
function NotFoundComponent() {
return <span>NotFoundComponent</span>;
}
function RouteComponent() {
return <span>RouteComponent</span>;
}
It shows NotFoundComponent as expected when initially navigating to it. When saving the file again in VSCode and therefore triggering an hot module replacement event in the browser, the route crashes with following error:
{"isNotFound":true}
Your Example Website or App
https://github.com/ulrichstark/tanstack-repro-isNotFound-on-hmr
Steps to Reproduce the Bug or Issue
git clone https://github.com/ulrichstark/tanstack-repro-isNotFound-on-hmr
cd tanstack-repro-isNotFound-on-hmr
npm i
npx vite
Then go to /src/routes/index.tsx and hit save to trigger hmr.
Expected behavior
NotFoundComponent should still be shown after hmr.
Screenshots or Videos
No response
Platform
- Router / Start Version: 1.134.12
Additional context
Maybe similar to #5322
A summary of the changes CodeRabbit can apply:
Update packages/router-core/src/router.ts to treat 'notFound' like 'error' in invalidate (change condition to d.status === 'error' || d.status === 'notFound') and add a new test in packages/react-router/tests/router.test.tsx verifying routes throwing notFound() are correctly reset and display notFoundComponent after HMR invalidation.
Add a new test in packages/react-router/tests/router.test.tsx that verifies a route returning a 404 notFound (via loader) continues to render its notFoundComponent after router.invalidate(), and update packages/router-core/src/router.ts to treat routes with status 'notFound' as invalidatable (include 'notFound' in the condition that resets a route to pending on invalidate).
- [ ] ✅ Create PR with these edits
- [ ] 📋 Get copyable edits
@ulrichstark , there will be a release out in a minute that resolves this. Let us know if it works as expected.
@ulrichstark , there will be a release out in a minute that resolves this. Let us know if it works as expected.
Hey thanks for letting me know. My issue isn't fixed by the most recent TanStack Start version. I just upgraded my repro project to version 1.137.0 of @tanstack/react-start. It's stil showing me {"isNotFound":true} after hitting save in index.tsx
Also the same issue is happening when clicking a <Link> that navigates to a route with a server function throwing notFound().
I added a test route to my repro project. If you are on http://localhost:3000/link and click the link, you will see the same error like you would when saving the index.tsx route.
This might be related to my issue with SSR + nested routes
I'm experiencing what could be related to this issue. In my case, I have a nested route structure where throwing notFound() in a child route's loader doesn't properly render the parent layout's notFoundComponent.
My Setup
I have a parent layout route with a notFoundComponent defined, and a child route that throws notFound() in its loader:
Parent route (/backoffice/clients/$clientId/layout.tsx):
export const Route = createFileRoute('/backoffice/clients/$clientId')({
component: ClientLayout,
notFoundComponent: ClientNotFound, // ← Defined here
// ... other config
})
function ClientLayout() {
return (
<div>
<DetailsNav>
<Outlet />
</DetailsNav>
</div>
)
}
Child route (/backoffice/clients/$clientId/personal-info.tsx):
export const Route = createFileRoute(
'/backoffice/clients/$clientId/personal-info',
)({
loader: async ({ context: { queryClient }, params: { clientId } }) => {
// Client doesn't exist, throw notFound
throw notFound()
},
component: PersonalInfo,
})
The Problem
Client-side navigation (renders wrong component ⚠️):
- Start at
/backoffice/clients - Click a link to navigate to
/backoffice/clients/00000000-0000-0000-0000-000000000001/personal-info - Result: Renders the root route's
defaultNotFoundComponentinstead of the layout'sClientNotFoundcomponent - The parent layout's
notFoundComponentis completely ignored
Direct navigation / SSR (complete failure ❌):
- Paste URL directly:
http://localhost:3000/backoffice/clients/00000000-0000-0000-0000-000000000001/personal-info - Result: Server responds with
Cannot GET /backoffice/clients/00000000-0000-0000-0000-000000000001/personal-info - No component is rendered at all - just a blank error page
Environment
@tanstack/react-router:^1.139.3@tanstack/react-start:^1.139.3- Using Nitro (TanStack Start default server)
I can confirm this issue with TanStack Start version 1.139.3.
Setup:
notFoundComponentconfigured in__root.tsxand_authed.tsx- Using
throw notFound()in loader
Symptoms:
- ✅ Client-side navigation →
notFoundComponentrenders correctly - ❌ Direct URL access → Shows "Cannot GET /path"
- ❌ Page refresh → Shows "Cannot GET /path"
Example:
export const Route = createFileRoute("/_authed/agency")({
loader: async ({ context }) => {
const userAccess = await getUserAccess();
if (!isAgencyMember(userAccess)) {
throw notFound();
}
return { user: context.user, userAccess };
},
component: AgencyLayout,
});
Accessing /agency directly shows "Cannot GET /agency" instead of the configured notFoundComponent. Same happens for any non-existent route like /test-route.
I was able to "solve" the issue @Andresuito and I am seeing by removing the nitro() plugin from vite.config.ts. I don't nearly understand enough of what's going here to call this a solution, but hopefully this bit of information can be helpful.
I was able to "solve" the issue @Andresuito and I am seeing by removing the
nitro()plugin fromvite.config.ts. I don't nearly understand enough of what's going here to call this a solution, but hopefully this bit of information can be helpful.
isnt nitro required to deploy on vercel?