diff --git a/e2e/react-router/basepath-file-based/src/routeTree.gen.ts b/e2e/react-router/basepath-file-based/src/routeTree.gen.ts
index 59499d9fbf3..c2b36e56082 100644
--- a/e2e/react-router/basepath-file-based/src/routeTree.gen.ts
+++ b/e2e/react-router/basepath-file-based/src/routeTree.gen.ts
@@ -9,9 +9,15 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
+import { Route as RedirectRouteImport } from './routes/redirect'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index'
+const RedirectRoute = RedirectRouteImport.update({
+ id: '/redirect',
+ path: '/redirect',
+ getParentRoute: () => rootRouteImport,
+} as any)
const AboutRoute = AboutRouteImport.update({
id: '/about',
path: '/about',
@@ -26,31 +32,42 @@ const IndexRoute = IndexRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
+ '/redirect': typeof RedirectRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
+ '/redirect': typeof RedirectRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/about': typeof AboutRoute
+ '/redirect': typeof RedirectRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
- fullPaths: '/' | '/about'
+ fullPaths: '/' | '/about' | '/redirect'
fileRoutesByTo: FileRoutesByTo
- to: '/' | '/about'
- id: '__root__' | '/' | '/about'
+ to: '/' | '/about' | '/redirect'
+ id: '__root__' | '/' | '/about' | '/redirect'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
+ RedirectRoute: typeof RedirectRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
+ '/redirect': {
+ id: '/redirect'
+ path: '/redirect'
+ fullPath: '/redirect'
+ preLoaderRoute: typeof RedirectRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/about': {
id: '/about'
path: '/about'
@@ -71,6 +88,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
+ RedirectRoute: RedirectRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/e2e/react-router/basepath-file-based/src/routes/index.tsx b/e2e/react-router/basepath-file-based/src/routes/index.tsx
index 9975e63698d..9a25196630e 100644
--- a/e2e/react-router/basepath-file-based/src/routes/index.tsx
+++ b/e2e/react-router/basepath-file-based/src/routes/index.tsx
@@ -20,6 +20,16 @@ function App() {
>
Navigate to /about with document reload
+
)
}
diff --git a/e2e/react-router/basepath-file-based/src/routes/redirect.tsx b/e2e/react-router/basepath-file-based/src/routes/redirect.tsx
new file mode 100644
index 00000000000..c3be9254654
--- /dev/null
+++ b/e2e/react-router/basepath-file-based/src/routes/redirect.tsx
@@ -0,0 +1,12 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/redirect')({
+ beforeLoad: async () => {
+ throw redirect({ to: '/about' })
+ },
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return
Hello "/redirect"!
+}
diff --git a/e2e/react-router/basepath-file-based/tests/reload-document.test.ts b/e2e/react-router/basepath-file-based/tests/reload-document.test.ts
index 3e60f3bedcb..1baf6807851 100644
--- a/e2e/react-router/basepath-file-based/tests/reload-document.test.ts
+++ b/e2e/react-router/basepath-file-based/tests/reload-document.test.ts
@@ -16,3 +16,13 @@ test('navigate() respects basepath for when reloadDocument=true', async ({
await page.waitForURL('/app/')
await expect(page.getByTestId(`home-component`)).toBeInViewport()
})
+
+test('redirect respects basepath', async ({ page }) => {
+ await page.goto(`/app/`)
+ await expect(page.getByTestId(`home-component`)).toBeInViewport()
+
+ const redirectBtn = page.getByTestId(`to-redirect-btn`)
+ await redirectBtn.click()
+ await page.waitForURL('/app/about')
+ await expect(page.getByTestId(`about-component`)).toBeInViewport()
+})
diff --git a/e2e/react-start/custom-basepath/tests/navigation.spec.ts b/e2e/react-start/custom-basepath/tests/navigation.spec.ts
index e66f14be9e2..e54ec6dbfdd 100644
--- a/e2e/react-start/custom-basepath/tests/navigation.spec.ts
+++ b/e2e/react-start/custom-basepath/tests/navigation.spec.ts
@@ -47,9 +47,8 @@ test('Server function URLs correctly include app basepath', async ({
test('client-side redirect', async ({ page, baseURL }) => {
await page.goto('/redirect')
await page.getByTestId('link-to-throw-it').click()
- await page.waitForLoadState('networkidle')
-
- expect(await page.getByTestId('post-view').isVisible()).toBe(true)
+ await page.waitForURL(`${baseURL}/posts/1`)
+ await expect(page.getByTestId('post-view')).toBeInViewport()
expect(page.url()).toBe(`${baseURL}/posts/1`)
})
diff --git a/packages/router-core/src/rewrite.ts b/packages/router-core/src/rewrite.ts
index 0da50c5b620..8eb3f400f44 100644
--- a/packages/router-core/src/rewrite.ts
+++ b/packages/router-core/src/rewrite.ts
@@ -36,23 +36,37 @@ export function rewriteBasepath(opts: {
? normalizedBasepathWithSlash
: normalizedBasepathWithSlash.toLowerCase()
+ const removeBasePath = (pathname: string) => {
+ const normalizedPath = opts.caseSensitive
+ ? pathname
+ : pathname.toLowerCase()
+
+ // Handle exact basepath match (e.g., /my-app -> /)
+ if (normalizedPath === checkBasepath) {
+ return '/'
+ }
+
+ if (normalizedPath.startsWith(checkBasepathWithSlash)) {
+ // Handle basepath with trailing content (e.g., /my-app/users -> /users)
+ return pathname.slice(normalizedBasepath.length)
+ }
+
+ return pathname
+ }
+
return {
input: ({ url }) => {
- const pathname = opts.caseSensitive
- ? url.pathname
- : url.pathname.toLowerCase()
+ url.pathname = removeBasePath(url.pathname)
- // Handle exact basepath match (e.g., /my-app -> /)
- if (pathname === checkBasepath) {
- url.pathname = '/'
- } else if (pathname.startsWith(checkBasepathWithSlash)) {
- // Handle basepath with trailing content (e.g., /my-app/users -> /users)
- url.pathname = url.pathname.slice(normalizedBasepath.length)
- }
return url
},
output: ({ url }) => {
- url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])
+ url.pathname = joinPaths([
+ '/',
+ trimmedBasepath,
+ removeBasePath(url.pathname),
+ ])
+
return url
},
} satisfies LocationRewrite
diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts
index 54da83a33ec..f5aa2118c52 100644
--- a/packages/router-core/src/router.ts
+++ b/packages/router-core/src/router.ts
@@ -1758,11 +1758,11 @@ export class RouterCore<
// If a rewrite function is provided, use it to rewrite the URL
const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
+ const rewrittenFullPath = `${rewrittenUrl.pathname}${rewrittenUrl.search}${rewrittenUrl.hash}`
return {
- publicHref:
- rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
- href: fullPath,
+ publicHref: rewrittenFullPath,
+ href: rewrittenFullPath,
url: rewrittenUrl,
pathname: nextPathname,
search: nextSearch,