diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
index 590f7df911..912fb05874 100644
--- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts
+++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
@@ -29,10 +29,13 @@ import { Route as groupLazyinsideImport } from './routes/(group)/lazyinside'
import { Route as groupInsideImport } from './routes/(group)/inside'
import { Route as groupLayoutImport } from './routes/(group)/_layout'
import { Route as anotherGroupOnlyrouteinsideImport } from './routes/(another-group)/onlyrouteinside'
+import { Route as RedirectComponentIndexImport } from './routes/redirect/component/index'
import { Route as RedirectTargetIndexImport } from './routes/redirect/$target/index'
import { Route as RedirectPreloadThirdImport } from './routes/redirect/preload/third'
import { Route as RedirectPreloadSecondImport } from './routes/redirect/preload/second'
import { Route as RedirectPreloadFirstImport } from './routes/redirect/preload/first'
+import { Route as RedirectComponentSecondImport } from './routes/redirect/component/second'
+import { Route as RedirectComponentFirstImport } from './routes/redirect/component/first'
import { Route as RedirectTargetViaLoaderImport } from './routes/redirect/$target/via-loader'
import { Route as RedirectTargetViaBeforeLoadImport } from './routes/redirect/$target/via-beforeLoad'
import { Route as PostsPostIdEditImport } from './routes/posts_.$postId.edit'
@@ -148,6 +151,12 @@ const anotherGroupOnlyrouteinsideRoute =
getParentRoute: () => rootRoute,
} as any)
+const RedirectComponentIndexRoute = RedirectComponentIndexImport.update({
+ id: '/redirect/component/',
+ path: '/redirect/component/',
+ getParentRoute: () => rootRoute,
+} as any)
+
const RedirectTargetIndexRoute = RedirectTargetIndexImport.update({
id: '/',
path: '/',
@@ -172,6 +181,18 @@ const RedirectPreloadFirstRoute = RedirectPreloadFirstImport.update({
getParentRoute: () => rootRoute,
} as any)
+const RedirectComponentSecondRoute = RedirectComponentSecondImport.update({
+ id: '/redirect/component/second',
+ path: '/redirect/component/second',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const RedirectComponentFirstRoute = RedirectComponentFirstImport.update({
+ id: '/redirect/component/first',
+ path: '/redirect/component/first',
+ getParentRoute: () => rootRoute,
+} as any)
+
const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderImport.update({
id: '/via-loader',
path: '/via-loader',
@@ -387,6 +408,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof RedirectTargetViaLoaderImport
parentRoute: typeof RedirectTargetImport
}
+ '/redirect/component/first': {
+ id: '/redirect/component/first'
+ path: '/redirect/component/first'
+ fullPath: '/redirect/component/first'
+ preLoaderRoute: typeof RedirectComponentFirstImport
+ parentRoute: typeof rootRoute
+ }
+ '/redirect/component/second': {
+ id: '/redirect/component/second'
+ path: '/redirect/component/second'
+ fullPath: '/redirect/component/second'
+ preLoaderRoute: typeof RedirectComponentSecondImport
+ parentRoute: typeof rootRoute
+ }
'/redirect/preload/first': {
id: '/redirect/preload/first'
path: '/redirect/preload/first'
@@ -415,6 +450,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof RedirectTargetIndexImport
parentRoute: typeof RedirectTargetImport
}
+ '/redirect/component/': {
+ id: '/redirect/component/'
+ path: '/redirect/component'
+ fullPath: '/redirect/component'
+ preLoaderRoute: typeof RedirectComponentIndexImport
+ parentRoute: typeof rootRoute
+ }
}
}
@@ -523,10 +565,13 @@ export interface FileRoutesByFullPath {
'/posts/$postId/edit': typeof PostsPostIdEditRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/preload/first': typeof RedirectPreloadFirstRoute
'/redirect/preload/second': typeof RedirectPreloadSecondRoute
'/redirect/preload/third': typeof RedirectPreloadThirdRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/redirect/component': typeof RedirectComponentIndexRoute
}
export interface FileRoutesByTo {
@@ -549,10 +594,13 @@ export interface FileRoutesByTo {
'/posts/$postId/edit': typeof PostsPostIdEditRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/preload/first': typeof RedirectPreloadFirstRoute
'/redirect/preload/second': typeof RedirectPreloadSecondRoute
'/redirect/preload/third': typeof RedirectPreloadThirdRoute
'/redirect/$target': typeof RedirectTargetIndexRoute
+ '/redirect/component': typeof RedirectComponentIndexRoute
}
export interface FileRoutesById {
@@ -581,10 +629,13 @@ export interface FileRoutesById {
'/posts_/$postId/edit': typeof PostsPostIdEditRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/preload/first': typeof RedirectPreloadFirstRoute
'/redirect/preload/second': typeof RedirectPreloadSecondRoute
'/redirect/preload/third': typeof RedirectPreloadThirdRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
+ '/redirect/component/': typeof RedirectComponentIndexRoute
}
export interface FileRouteTypes {
@@ -611,10 +662,13 @@ export interface FileRouteTypes {
| '/posts/$postId/edit'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/preload/first'
| '/redirect/preload/second'
| '/redirect/preload/third'
| '/redirect/$target/'
+ | '/redirect/component'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -636,10 +690,13 @@ export interface FileRouteTypes {
| '/posts/$postId/edit'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/preload/first'
| '/redirect/preload/second'
| '/redirect/preload/third'
| '/redirect/$target'
+ | '/redirect/component'
id:
| '__root__'
| '/'
@@ -666,10 +723,13 @@ export interface FileRouteTypes {
| '/posts_/$postId/edit'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/preload/first'
| '/redirect/preload/second'
| '/redirect/preload/third'
| '/redirect/$target/'
+ | '/redirect/component/'
fileRoutesById: FileRoutesById
}
@@ -686,9 +746,12 @@ export interface RootRouteChildren {
StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute
RedirectIndexRoute: typeof RedirectIndexRoute
PostsPostIdEditRoute: typeof PostsPostIdEditRoute
+ RedirectComponentFirstRoute: typeof RedirectComponentFirstRoute
+ RedirectComponentSecondRoute: typeof RedirectComponentSecondRoute
RedirectPreloadFirstRoute: typeof RedirectPreloadFirstRoute
RedirectPreloadSecondRoute: typeof RedirectPreloadSecondRoute
RedirectPreloadThirdRoute: typeof RedirectPreloadThirdRoute
+ RedirectComponentIndexRoute: typeof RedirectComponentIndexRoute
}
const rootRouteChildren: RootRouteChildren = {
@@ -704,9 +767,12 @@ const rootRouteChildren: RootRouteChildren = {
StructuralSharingEnabledRoute: StructuralSharingEnabledRoute,
RedirectIndexRoute: RedirectIndexRoute,
PostsPostIdEditRoute: PostsPostIdEditRoute,
+ RedirectComponentFirstRoute: RedirectComponentFirstRoute,
+ RedirectComponentSecondRoute: RedirectComponentSecondRoute,
RedirectPreloadFirstRoute: RedirectPreloadFirstRoute,
RedirectPreloadSecondRoute: RedirectPreloadSecondRoute,
RedirectPreloadThirdRoute: RedirectPreloadThirdRoute,
+ RedirectComponentIndexRoute: RedirectComponentIndexRoute,
}
export const routeTree = rootRoute
@@ -731,9 +797,12 @@ export const routeTree = rootRoute
"/structural-sharing/$enabled",
"/redirect/",
"/posts_/$postId/edit",
+ "/redirect/component/first",
+ "/redirect/component/second",
"/redirect/preload/first",
"/redirect/preload/second",
- "/redirect/preload/third"
+ "/redirect/preload/third",
+ "/redirect/component/"
]
},
"/": {
@@ -845,6 +914,12 @@ export const routeTree = rootRoute
"filePath": "redirect/$target/via-loader.tsx",
"parent": "/redirect/$target"
},
+ "/redirect/component/first": {
+ "filePath": "redirect/component/first.tsx"
+ },
+ "/redirect/component/second": {
+ "filePath": "redirect/component/second.tsx"
+ },
"/redirect/preload/first": {
"filePath": "redirect/preload/first.tsx"
},
@@ -857,6 +932,9 @@ export const routeTree = rootRoute
"/redirect/$target/": {
"filePath": "redirect/$target/index.tsx",
"parent": "/redirect/$target"
+ },
+ "/redirect/component/": {
+ "filePath": "redirect/component/index.tsx"
}
}
}
diff --git a/e2e/react-router/basic-file-based/src/routes/redirect/component/first.tsx b/e2e/react-router/basic-file-based/src/routes/redirect/component/first.tsx
new file mode 100644
index 0000000000..c84304d5d1
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/redirect/component/first.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+import React from 'react'
+
+export const Route = createFileRoute('/redirect/component/first')({
+ component: RouteComponent,
+})
+
+function useThrowRedirect() {
+ throw redirect({ to: '/redirect/component/second' })
+}
+
+function RouteComponent() {
+ useThrowRedirect()
+ return (
+
+
Redirecting...
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based/src/routes/redirect/component/index.tsx b/e2e/react-router/basic-file-based/src/routes/redirect/component/index.tsx
new file mode 100644
index 0000000000..0f94685e63
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/redirect/component/index.tsx
@@ -0,0 +1,23 @@
+import { Link, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/redirect/component/')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return (
+
+
+ go to second
+
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based/src/routes/redirect/component/second.tsx b/e2e/react-router/basic-file-based/src/routes/redirect/component/second.tsx
new file mode 100644
index 0000000000..f66dd3f33f
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/redirect/component/second.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/redirect/component/second')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return Hello "/redirect/component/second"!
+}
diff --git a/e2e/react-router/basic-file-based/tests/redirect.spec.ts b/e2e/react-router/basic-file-based/tests/redirect.spec.ts
index 6166d1cf50..637519160e 100644
--- a/e2e/react-router/basic-file-based/tests/redirect.spec.ts
+++ b/e2e/react-router/basic-file-based/tests/redirect.spec.ts
@@ -123,4 +123,20 @@ test.describe('redirects', () => {
await page.waitForURL('/redirect/preload/third')
await expect(page.getByTestId(`third`)).toBeInViewport()
})
+
+ test.describe('throw redirect in route component', () => {
+ test('initial load', async ({ page }) => {
+ await page.goto(`/redirect/component/first`)
+ await page.waitForURL('/redirect/component/second')
+ await expect(page.getByTestId('second')).toBeInViewport()
+ })
+ test('when navigating', async ({ page }) => {
+ await page.goto(`/redirect/component/`)
+ const link = page.getByTestId(`link`)
+ await link.focus()
+ await link.click()
+ await page.waitForURL('/redirect/component/second')
+ await expect(page.getByTestId('second')).toBeInViewport()
+ })
+ })
})
diff --git a/e2e/start/basic/app/routeTree.gen.ts b/e2e/start/basic/app/routeTree.gen.ts
index 3dc72a54ff..f8ef1287e3 100644
--- a/e2e/start/basic/app/routeTree.gen.ts
+++ b/e2e/start/basic/app/routeTree.gen.ts
@@ -31,6 +31,8 @@ import { Route as RedirectTargetImport } from './routes/redirect/$target'
import { Route as PostsPostIdImport } from './routes/posts.$postId'
import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2'
import { Route as RedirectTargetIndexImport } from './routes/redirect/$target/index'
+import { Route as RedirectComponentSecondImport } from './routes/redirect/component/second'
+import { Route as RedirectComponentFirstImport } from './routes/redirect/component/first'
import { Route as RedirectTargetViaLoaderImport } from './routes/redirect/$target/via-loader'
import { Route as RedirectTargetViaBeforeLoadImport } from './routes/redirect/$target/via-beforeLoad'
import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep'
@@ -161,6 +163,18 @@ const RedirectTargetIndexRoute = RedirectTargetIndexImport.update({
getParentRoute: () => RedirectTargetRoute,
} as any)
+const RedirectComponentSecondRoute = RedirectComponentSecondImport.update({
+ id: '/redirect/component/second',
+ path: '/redirect/component/second',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const RedirectComponentFirstRoute = RedirectComponentFirstImport.update({
+ id: '/redirect/component/first',
+ path: '/redirect/component/first',
+ getParentRoute: () => rootRoute,
+} as any)
+
const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderImport.update({
id: '/via-loader',
path: '/via-loader',
@@ -392,6 +406,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof RedirectTargetViaLoaderImport
parentRoute: typeof RedirectTargetImport
}
+ '/redirect/component/first': {
+ id: '/redirect/component/first'
+ path: '/redirect/component/first'
+ fullPath: '/redirect/component/first'
+ preLoaderRoute: typeof RedirectComponentFirstImport
+ parentRoute: typeof rootRoute
+ }
+ '/redirect/component/second': {
+ id: '/redirect/component/second'
+ path: '/redirect/component/second'
+ fullPath: '/redirect/component/second'
+ preLoaderRoute: typeof RedirectComponentSecondImport
+ parentRoute: typeof rootRoute
+ }
'/redirect/$target/': {
id: '/redirect/$target/'
path: '/'
@@ -531,6 +559,8 @@ export interface FileRoutesByFullPath {
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
'/redirect/$target/serverFn/via-beforeLoad': typeof RedirectTargetServerFnViaBeforeLoadRoute
'/redirect/$target/serverFn/via-loader': typeof RedirectTargetServerFnViaLoaderRoute
@@ -559,6 +589,8 @@ export interface FileRoutesByTo {
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/$target': typeof RedirectTargetIndexRoute
'/redirect/$target/serverFn/via-beforeLoad': typeof RedirectTargetServerFnViaBeforeLoadRoute
'/redirect/$target/serverFn/via-loader': typeof RedirectTargetServerFnViaLoaderRoute
@@ -592,6 +624,8 @@ export interface FileRoutesById {
'/posts_/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
'/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute
+ '/redirect/component/first': typeof RedirectComponentFirstRoute
+ '/redirect/component/second': typeof RedirectComponentSecondRoute
'/redirect/$target/': typeof RedirectTargetIndexRoute
'/redirect/$target/serverFn/via-beforeLoad': typeof RedirectTargetServerFnViaBeforeLoadRoute
'/redirect/$target/serverFn/via-loader': typeof RedirectTargetServerFnViaLoaderRoute
@@ -625,6 +659,8 @@ export interface FileRouteTypes {
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/$target/'
| '/redirect/$target/serverFn/via-beforeLoad'
| '/redirect/$target/serverFn/via-loader'
@@ -652,6 +688,8 @@ export interface FileRouteTypes {
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/$target'
| '/redirect/$target/serverFn/via-beforeLoad'
| '/redirect/$target/serverFn/via-loader'
@@ -683,6 +721,8 @@ export interface FileRouteTypes {
| '/posts_/$postId/deep'
| '/redirect/$target/via-beforeLoad'
| '/redirect/$target/via-loader'
+ | '/redirect/component/first'
+ | '/redirect/component/second'
| '/redirect/$target/'
| '/redirect/$target/serverFn/via-beforeLoad'
| '/redirect/$target/serverFn/via-loader'
@@ -707,6 +747,8 @@ export interface RootRouteChildren {
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
RedirectIndexRoute: typeof RedirectIndexRoute
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
+ RedirectComponentFirstRoute: typeof RedirectComponentFirstRoute
+ RedirectComponentSecondRoute: typeof RedirectComponentSecondRoute
}
const rootRouteChildren: RootRouteChildren = {
@@ -725,6 +767,8 @@ const rootRouteChildren: RootRouteChildren = {
RedirectTargetRoute: RedirectTargetRouteWithChildren,
RedirectIndexRoute: RedirectIndexRoute,
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
+ RedirectComponentFirstRoute: RedirectComponentFirstRoute,
+ RedirectComponentSecondRoute: RedirectComponentSecondRoute,
}
export const routeTree = rootRoute
@@ -751,7 +795,9 @@ export const routeTree = rootRoute
"/users",
"/redirect/$target",
"/redirect/",
- "/posts_/$postId/deep"
+ "/posts_/$postId/deep",
+ "/redirect/component/first",
+ "/redirect/component/second"
]
},
"/": {
@@ -859,6 +905,12 @@ export const routeTree = rootRoute
"filePath": "redirect/$target/via-loader.tsx",
"parent": "/redirect/$target"
},
+ "/redirect/component/first": {
+ "filePath": "redirect/component/first.tsx"
+ },
+ "/redirect/component/second": {
+ "filePath": "redirect/component/second.tsx"
+ },
"/redirect/$target/": {
"filePath": "redirect/$target/index.tsx",
"parent": "/redirect/$target"
diff --git a/e2e/start/basic/app/routes/redirect/component/first.tsx b/e2e/start/basic/app/routes/redirect/component/first.tsx
new file mode 100644
index 0000000000..c84304d5d1
--- /dev/null
+++ b/e2e/start/basic/app/routes/redirect/component/first.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+import React from 'react'
+
+export const Route = createFileRoute('/redirect/component/first')({
+ component: RouteComponent,
+})
+
+function useThrowRedirect() {
+ throw redirect({ to: '/redirect/component/second' })
+}
+
+function RouteComponent() {
+ useThrowRedirect()
+ return (
+
+
Redirecting...
+
+ )
+}
diff --git a/e2e/start/basic/app/routes/redirect/component/second.tsx b/e2e/start/basic/app/routes/redirect/component/second.tsx
new file mode 100644
index 0000000000..f66dd3f33f
--- /dev/null
+++ b/e2e/start/basic/app/routes/redirect/component/second.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/redirect/component/second')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return Hello "/redirect/component/second"!
+}
diff --git a/e2e/start/basic/tests/redirect.spec.ts b/e2e/start/basic/tests/redirect.spec.ts
index caff77d6ab..fa272da55f 100644
--- a/e2e/start/basic/tests/redirect.spec.ts
+++ b/e2e/start/basic/tests/redirect.spec.ts
@@ -193,4 +193,10 @@ test.describe('redirects', () => {
}
})
})
+
+ test('throw redirect in route component', async ({ page }) => {
+ await page.goto(`/redirect/component/first`)
+ await page.waitForURL('/redirect/component/second')
+ await expect(page.getByTestId('second')).toBeInViewport()
+ })
})
diff --git a/packages/react-router/src/CatchBoundary.tsx b/packages/react-router/src/CatchBoundary.tsx
index 68197b2eb6..c3ddc3e689 100644
--- a/packages/react-router/src/CatchBoundary.tsx
+++ b/packages/react-router/src/CatchBoundary.tsx
@@ -1,4 +1,7 @@
import * as React from 'react'
+import { useRouter } from './useRouter'
+import { isRedirect } from './redirects'
+import { Navigate } from './useNavigate'
import type { ErrorRouteComponent } from './route'
import type { ErrorInfo } from 'react'
@@ -9,6 +12,7 @@ export function CatchBoundary(props: {
onCatch?: (error: Error, errorInfo: ErrorInfo) => void
}) {
const errorComponent = props.errorComponent ?? ErrorComponent
+ const router = useRouter()
return (
{
if (error) {
+ if (isRedirect(error)) {
+ const redirect = router.resolveRedirect({
+ ...error,
+ _fromLocation: router.state.location,
+ })
+ return (
+
+ )
+ }
return React.createElement(errorComponent, {
error,
reset,
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index 6873cf0d98..dd3bab94ce 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -2696,7 +2696,11 @@ export class Router<
}))
} catch (err) {
if (isResolvedRedirect(err)) {
- await this.navigate(err)
+ await this.navigate({
+ ...err,
+ replace: true,
+ ignoreBlocker: true,
+ })
}
}
})()
diff --git a/packages/react-router/tests/redirect.test.tsx b/packages/react-router/tests/redirect.test.tsx
index 679158c2c0..91067f2358 100644
--- a/packages/react-router/tests/redirect.test.tsx
+++ b/packages/react-router/tests/redirect.test.tsx
@@ -252,6 +252,84 @@ describe('redirect', () => {
expect(await screen.findByText('Final')).toBeInTheDocument()
expect(window.location.pathname).toBe('/final')
})
+
+ test('when `redirect` is thrown in route component', async () => {
+ const nestedLoaderMock = vi.fn()
+ const nestedFooLoaderMock = vi.fn()
+
+ const rootRoute = createRootRoute({})
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => {
+ return (
+
+
Index page
+ link to about
+
+ )
+ },
+ })
+ const aboutRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/about',
+ component: () => {
+ throw redirect({
+ to: '/nested/foo',
+ hash: 'some-hash',
+ search: { someSearch: 'hello123' },
+ })
+ },
+ })
+ const nestedRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/nested',
+ loader: async () => {
+ await sleep(WAIT_TIME)
+ nestedLoaderMock('nested')
+ },
+ })
+ const fooRoute = createRoute({
+ validateSearch: (search) => {
+ return {
+ someSearch: search.someSearch as string,
+ }
+ },
+ getParentRoute: () => nestedRoute,
+ path: '/foo',
+ loader: async () => {
+ await sleep(WAIT_TIME)
+ nestedFooLoaderMock('foo')
+ },
+ component: () => Nested Foo page
,
+ })
+ const routeTree = rootRoute.addChildren([
+ nestedRoute.addChildren([fooRoute]),
+ aboutRoute,
+ indexRoute,
+ ])
+ const router = createRouter({ routeTree })
+
+ render()
+
+ const linkToAbout = await screen.findByText('link to about')
+
+ expect(linkToAbout).toBeInTheDocument()
+
+ fireEvent.click(linkToAbout)
+
+ const fooElement = await screen.findByText('Nested Foo page')
+
+ expect(fooElement).toBeInTheDocument()
+
+ expect(router.state.location.href).toBe(
+ '/nested/foo?someSearch=hello123#some-hash',
+ )
+ expect(window.location.pathname).toBe('/nested/foo')
+
+ expect(nestedLoaderMock).toHaveBeenCalled()
+ expect(nestedFooLoaderMock).toHaveBeenCalled()
+ })
})
describe('SSR', () => {