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..7ae0eca2bc3 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..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 @@ -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 reloadDocument = true on redirect', 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..1931a55a508 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -353,6 +353,8 @@ export interface NavigateOptionProps { * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/NavigateOptionsType#href) */ href?: string + /** @internal */ + 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() }