From 538e21881c6d5c1f92bc501c70b3c49ce371ed9f Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 23 Dec 2025 23:49:37 +0200 Subject: [PATCH 1/3] update navigate to use publicHref when reload document --- .../basepath-file-based/src/routeTree.gen.ts | 42 +++++++++++++++++-- .../basepath-file-based/src/routes/index.tsx | 20 +++++++++ .../src/routes/redirect.tsx | 12 ++++++ .../src/routes/redirectReload.tsx | 12 ++++++ .../tests/reload-document.test.ts | 22 ++++++++++ packages/router-core/src/link.ts | 1 + packages/router-core/src/router.ts | 29 +++++++++---- 7 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 e2e/react-router/basepath-file-based/src/routes/redirect.tsx create mode 100644 e2e/react-router/basepath-file-based/src/routes/redirectReload.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..177ee7a3136 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,21 @@ // 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 RedirectReloadRouteImport } from './routes/redirectReload' +import { Route as RedirectRouteImport } from './routes/redirect' import { Route as AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +const RedirectReloadRoute = RedirectReloadRouteImport.update({ + id: '/redirectReload', + path: '/redirectReload', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', + getParentRoute: () => rootRouteImport, +} as any) const AboutRoute = AboutRouteImport.update({ id: '/about', path: '/about', @@ -26,31 +38,53 @@ const IndexRoute = IndexRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/about': typeof AboutRoute + '/redirect': typeof RedirectRoute + '/redirectReload': typeof RedirectReloadRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/about': typeof AboutRoute + '/redirect': typeof RedirectRoute + '/redirectReload': typeof RedirectReloadRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/about': typeof AboutRoute + '/redirect': typeof RedirectRoute + '/redirectReload': typeof RedirectReloadRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/about' + fullPaths: '/' | '/about' | '/redirect' | '/redirectReload' fileRoutesByTo: FileRoutesByTo - to: '/' | '/about' - id: '__root__' | '/' | '/about' + to: '/' | '/about' | '/redirect' | '/redirectReload' + id: '__root__' | '/' | '/about' | '/redirect' | '/redirectReload' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute AboutRoute: typeof AboutRoute + RedirectRoute: typeof RedirectRoute + RedirectReloadRoute: typeof RedirectReloadRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/redirectReload': { + id: '/redirectReload' + path: '/redirectReload' + fullPath: '/redirectReload' + preLoaderRoute: typeof RedirectReloadRouteImport + parentRoute: typeof rootRouteImport + } + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport + parentRoute: typeof rootRouteImport + } '/about': { id: '/about' path: '/about' @@ -71,6 +105,8 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AboutRoute: AboutRoute, + RedirectRoute: RedirectRoute, + RedirectReloadRoute: RedirectReloadRoute, } 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..c494c1bf2fd 100644 --- a/e2e/react-router/basepath-file-based/src/routes/index.tsx +++ b/e2e/react-router/basepath-file-based/src/routes/index.tsx @@ -19,6 +19,26 @@ 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..5c7093630dc --- /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: () => { + throw redirect({ to: '/about' }) + }, + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/redirect"!
+} diff --git a/e2e/react-router/basepath-file-based/src/routes/redirectReload.tsx b/e2e/react-router/basepath-file-based/src/routes/redirectReload.tsx new file mode 100644 index 00000000000..517ff6c5a13 --- /dev/null +++ b/e2e/react-router/basepath-file-based/src/routes/redirectReload.tsx @@ -0,0 +1,12 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/redirectReload')({ + beforeLoad: () => { + throw redirect({ to: '/about', reloadDocument: true }) + }, + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/redirectReload"!
+} 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..51f30cb965a 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,25 @@ 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() +}) + +test('redirect respects basepath with reload document = true', async ({ + page, +}) => { + await page.goto(`/app/`) + await expect(page.getByTestId(`home-component`)).toBeInViewport() + + const redirectBtn = page.getByTestId(`to-redirect-reload-btn`) + await redirectBtn.click() + await page.waitForURL('/app/about') + await expect(page.getByTestId(`about-component`)).toBeInViewport() +}) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index e2ec7f8253f..20e2b8d2eac 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -353,6 +353,7 @@ export interface NavigateOptionProps { * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/NavigateOptionsType#href) */ href?: string + publicHref?: string } export type ToOptions< diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index eb846b0afa1..042af0abb17 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -2029,20 +2029,35 @@ export class RouterCore< * * @link https://tanstack.com/router/latest/docs/framework/react/api/router/NavigateOptionsType */ - navigate: NavigateFn = async ({ to, reloadDocument, href, ...rest }) => { - if (!reloadDocument && href) { + navigate: NavigateFn = async ({ + to, + reloadDocument, + href, + publicHref, + ...rest + }) => { + let hrefIsUrl = false + + if (href) { try { new URL(`${href}`) - reloadDocument = true + hrefIsUrl = true } catch {} } + if (hrefIsUrl && !reloadDocument) { + reloadDocument = true + } + if (reloadDocument) { - if (!href) { + if (!href || (!publicHref && !hrefIsUrl)) { const location = this.buildLocation({ to, ...rest } as any) - href = location.url.href + href = href ?? location.url.href + publicHref = publicHref ?? location.url.href } + const reloadHref = !hrefIsUrl && publicHref ? publicHref : href + // Check blockers for external URLs unless ignoreBlocker is true if (!rest.ignoreBlocker) { // Cast to access internal getBlockers method @@ -2063,9 +2078,9 @@ export class RouterCore< } if (rest.replace) { - window.location.replace(href) + window.location.replace(reloadHref) } else { - window.location.href = href + window.location.href = reloadHref } return Promise.resolve() } From 35b8446f705c4fbe66f7a3d33734fe62dedb80ef Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 24 Dec 2025 00:02:17 +0200 Subject: [PATCH 2/3] resolve codeRabbit nitpick --- e2e/react-router/basepath-file-based/src/routes/index.tsx | 4 ++-- .../basepath-file-based/tests/reload-document.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 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 c494c1bf2fd..7ae0eca2bc3 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 without document reload + Navigate to /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 51f30cb965a..f35ddd5670d 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 @@ -27,7 +27,7 @@ test('redirect respects basepath', async ({ page }) => { await expect(page.getByTestId(`about-component`)).toBeInViewport() }) -test('redirect respects basepath with reload document = true', async ({ +test('redirect respects basepath with reloadDocument = true on redirect', async ({ page, }) => { await page.goto(`/app/`) From 386177d8074fa08f6c7f607243ff2dcc43431e83 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 24 Dec 2025 01:12:45 +0200 Subject: [PATCH 3/3] mark publicHref prop as internal --- packages/router-core/src/link.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index 20e2b8d2eac..1931a55a508 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -353,6 +353,7 @@ export interface NavigateOptionProps { * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/NavigateOptionsType#href) */ href?: string + /** @internal */ publicHref?: string }