diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx
index 90be8b8db29..1fd85918efd 100644
--- a/packages/react-router/tests/router.test.tsx
+++ b/packages/react-router/tests/router.test.tsx
@@ -8,7 +8,7 @@ import {
waitFor,
} from '@testing-library/react'
import { z } from 'zod'
-import { composeRewrites } from '@tanstack/router-core'
+import { composeRewrites, notFound } from '@tanstack/router-core'
import {
Link,
Outlet,
@@ -1929,13 +1929,11 @@ describe('does not strip search params if search validation fails', () => {
})
})
-describe('statusCode reset on navigation', () => {
+describe('statusCode', () => {
it('should reset statusCode to 200 when navigating from 404 to valid route', async () => {
const history = createMemoryHistory({ initialEntries: ['/'] })
- const rootRoute = createRootRoute({
- component: () => ,
- })
+ const rootRoute = createRootRoute()
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
@@ -1968,6 +1966,174 @@ describe('statusCode reset on navigation', () => {
await act(() => router.navigate({ to: '/another-non-existing' }))
expect(router.state.statusCode).toBe(404)
})
+
+ describe.each([true, false])(
+ 'status code is set when loader/beforeLoad throws (isAsync=%s)',
+ async (isAsync) => {
+ const throwingFun = isAsync
+ ? (toThrow: () => void) => async () => {
+ await new Promise((resolve) => setTimeout(resolve, 10))
+ toThrow()
+ }
+ : (toThrow: () => void) => toThrow
+
+ const throwNotFound = throwingFun(() => {
+ throw notFound()
+ })
+ const throwError = throwingFun(() => {
+ throw new Error('test-error')
+ })
+ it('should set statusCode to 404 when a route loader throws a notFound()', async () => {
+ const history = createMemoryHistory({ initialEntries: ['/'] })
+
+ const rootRoute = createRootRoute()
+
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () =>
Home
,
+ })
+
+ const loaderThrowsRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/loader-throws-not-found',
+ loader: throwNotFound,
+ component: () => (
+ loader will throw
+ ),
+ notFoundComponent: () => (
+ Not Found
+ ),
+ })
+
+ const routeTree = rootRoute.addChildren([indexRoute, loaderThrowsRoute])
+ const router = createRouter({ routeTree, history })
+
+ render()
+
+ expect(router.state.statusCode).toBe(200)
+
+ await act(() => router.navigate({ to: '/loader-throws-not-found' }))
+ expect(router.state.statusCode).toBe(404)
+ expect(
+ await screen.findByTestId('not-found-component'),
+ ).toBeInTheDocument()
+ expect(screen.queryByTestId('route-component')).not.toBeInTheDocument()
+ })
+
+ it('should set statusCode to 404 when a route beforeLoad throws a notFound()', async () => {
+ const history = createMemoryHistory({ initialEntries: ['/'] })
+
+ const rootRoute = createRootRoute()
+
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => Home
,
+ })
+
+ const beforeLoadThrowsRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/beforeload-throws-not-found',
+ beforeLoad: throwNotFound,
+ component: () => (
+ beforeLoad will throw
+ ),
+ notFoundComponent: () => (
+ Not Found
+ ),
+ })
+
+ const routeTree = rootRoute.addChildren([
+ indexRoute,
+ beforeLoadThrowsRoute,
+ ])
+ const router = createRouter({ routeTree, history })
+
+ render()
+
+ expect(router.state.statusCode).toBe(200)
+
+ await act(() => router.navigate({ to: '/beforeload-throws-not-found' }))
+ expect(router.state.statusCode).toBe(404)
+ expect(
+ await screen.findByTestId('not-found-component'),
+ ).toBeInTheDocument()
+ expect(screen.queryByTestId('route-component')).not.toBeInTheDocument()
+ })
+
+ it('should set statusCode to 500 when a route loader throws an Error', async () => {
+ const history = createMemoryHistory({ initialEntries: ['/'] })
+
+ const rootRoute = createRootRoute()
+
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => Home
,
+ })
+
+ const loaderThrowsRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/loader-throws-error',
+ loader: throwError,
+ component: () => (
+ loader will throw
+ ),
+ errorComponent: () => Error
,
+ })
+
+ const routeTree = rootRoute.addChildren([indexRoute, loaderThrowsRoute])
+ const router = createRouter({ routeTree, history })
+
+ render()
+
+ expect(router.state.statusCode).toBe(200)
+
+ await act(() => router.navigate({ to: '/loader-throws-error' }))
+ expect(router.state.statusCode).toBe(500)
+ expect(await screen.findByTestId('error-component')).toBeInTheDocument()
+ expect(screen.queryByTestId('route-component')).not.toBeInTheDocument()
+ })
+
+ it('should set statusCode to 500 when a route beforeLoad throws an Error', async () => {
+ const history = createMemoryHistory({ initialEntries: ['/'] })
+
+ const rootRoute = createRootRoute()
+
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => Home
,
+ })
+
+ const beforeLoadThrowsRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/beforeload-throws-error',
+ beforeLoad: throwError,
+ component: () => (
+ beforeLoad will throw
+ ),
+ errorComponent: () => Error
,
+ })
+
+ const routeTree = rootRoute.addChildren([
+ indexRoute,
+ beforeLoadThrowsRoute,
+ ])
+ const router = createRouter({ routeTree, history })
+
+ render()
+
+ expect(router.state.statusCode).toBe(200)
+
+ await act(() => router.navigate({ to: '/beforeload-throws-error' }))
+ expect(router.state.statusCode).toBe(500)
+ expect(await screen.findByTestId('error-component')).toBeInTheDocument()
+ expect(screen.queryByTestId('route-component')).not.toBeInTheDocument()
+ })
+ },
+ )
})
describe('Router rewrite functionality', () => {
diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts
index eb9f67113b6..e4f35570e40 100644
--- a/packages/router-core/src/router.ts
+++ b/packages/router-core/src/router.ts
@@ -2135,10 +2135,16 @@ export class RouterCore<
await this.latestLoadPromise
}
+ let newStatusCode: number | undefined = undefined
if (this.hasNotFoundMatch()) {
+ newStatusCode = 404
+ } else if (this.__store.state.matches.some((d) => d.status === 'error')) {
+ newStatusCode = 500
+ }
+ if (newStatusCode !== undefined) {
this.__store.setState((s) => ({
...s,
- statusCode: 404,
+ statusCode: newStatusCode,
}))
}
}