From 57eaeb0d20c9d8e848e7724f1864ef13ad9cc9a1 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 20 Dec 2025 16:55:11 +0200 Subject: [PATCH 1/2] resolve issues with redirect and basepath --- .../basepath-file-based/src/routeTree.gen.ts | 24 ++++++++++++++++--- .../basepath-file-based/src/routes/index.tsx | 10 ++++++++ .../src/routes/redirect.tsx | 12 ++++++++++ .../tests/reload-document.test.ts | 10 ++++++++ .../custom-basepath/tests/navigation.spec.ts | 5 ++-- packages/router-core/src/rewrite.ts | 8 +++++++ packages/router-core/src/router.ts | 6 ++--- 7 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 e2e/react-router/basepath-file-based/src/routes/redirect.tsx 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..2b2e9545cd5 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..44cfea6d667 100644 --- a/packages/router-core/src/rewrite.ts +++ b/packages/router-core/src/rewrite.ts @@ -52,7 +52,15 @@ export function rewriteBasepath(opts: { return url }, output: ({ url }) => { + if (url.pathname === checkBasepath) { + url.pathname = '/' + } else if (url.pathname.startsWith(checkBasepathWithSlash)) { + // Handle basepath with trailing content (e.g., /my-app/users -> /users) + url.pathname = url.pathname.slice(normalizedBasepath.length) + } + url.pathname = joinPaths(['/', trimmedBasepath, url.pathname]) + return url }, } satisfies LocationRewrite diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 54da83a33ec..e99e72fc391 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}${searchStr}${hashStr}` return { - publicHref: - rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash, - href: fullPath, + publicHref: rewrittenFullPath, + href: rewrittenFullPath, url: rewrittenUrl, pathname: nextPathname, search: nextSearch, From 9ea0f936057f3e1689e1a812b22b521f2fcc0483 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Sat, 20 Dec 2025 18:07:05 +0200 Subject: [PATCH 2/2] some corrections --- .../basepath-file-based/src/routes/index.tsx | 2 +- packages/router-core/src/rewrite.ts | 42 +++++++++++-------- packages/router-core/src/router.ts | 2 +- 3 files changed, 26 insertions(+), 20 deletions(-) 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 2b2e9545cd5..9a25196630e 100644 --- a/e2e/react-router/basepath-file-based/src/routes/index.tsx +++ b/e2e/react-router/basepath-file-based/src/routes/index.tsx @@ -28,7 +28,7 @@ function App() { }) } > - Navigate to /redirect with document reload + Navigate to /redirect ) diff --git a/packages/router-core/src/rewrite.ts b/packages/router-core/src/rewrite.ts index 44cfea6d667..8eb3f400f44 100644 --- a/packages/router-core/src/rewrite.ts +++ b/packages/router-core/src/rewrite.ts @@ -36,30 +36,36 @@ 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 }) => { - if (url.pathname === checkBasepath) { - url.pathname = '/' - } else if (url.pathname.startsWith(checkBasepathWithSlash)) { - // Handle basepath with trailing content (e.g., /my-app/users -> /users) - url.pathname = url.pathname.slice(normalizedBasepath.length) - } - - url.pathname = joinPaths(['/', trimmedBasepath, url.pathname]) + url.pathname = joinPaths([ + '/', + trimmedBasepath, + removeBasePath(url.pathname), + ]) return url }, diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index e99e72fc391..f5aa2118c52 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -1758,7 +1758,7 @@ 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}${searchStr}${hashStr}` + const rewrittenFullPath = `${rewrittenUrl.pathname}${rewrittenUrl.search}${rewrittenUrl.hash}` return { publicHref: rewrittenFullPath,