diff --git a/packages/react-router/src/Transitioner.tsx b/packages/react-router/src/Transitioner.tsx index f547cdd5f58..9cbf5995166 100644 --- a/packages/react-router/src/Transitioner.tsx +++ b/packages/react-router/src/Transitioner.tsx @@ -52,11 +52,16 @@ export function Transitioner() { _includeValidateSearch: true, }) - if ( - trimPathRight(router.latestLocation.href) !== - trimPathRight(nextLocation.href) - ) { - router.commitLocation({ ...nextLocation, replace: true }) + const latestPublicHref = trimPathRight(router.latestLocation.publicHref) + const nextPublicHref = trimPathRight(nextLocation.publicHref) + + if (latestPublicHref !== nextPublicHref) { + router.navigate({ + to: nextLocation.pathname, + search: nextLocation.search, + hash: nextLocation.hash, + replace: true, + }) } return () => { diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index 32fc3efa071..38db9e5eb4c 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -3126,6 +3126,72 @@ describe('basepath', () => { expect(router.state.location.pathname).toBe('/test') }) + it('should handle basepath when accessing root path and maintain basepath in browser URL', async () => { + const rootRoute = createRootRoute({ + component: () => , + }) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + + const routeTree = rootRoute.addChildren([indexRoute]) + + const history = createMemoryHistory({ + initialEntries: ['/my-app/'], + }) + + const router = createRouter({ + routeTree, + history, + basepath: '/my-app', + }) + + render() + + await waitFor(() => { + expect(screen.getByTestId('home')).toBeInTheDocument() + }) + + expect(router.state.location.pathname).toBe('/') + expect(history.location.pathname).toBe('/my-app/') + }) + + it('should handle basepath option for backward compatibility', async () => { + const rootRoute = createRootRoute({ + component: () => , + }) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + + const routeTree = rootRoute.addChildren([indexRoute]) + + const history = createMemoryHistory({ + initialEntries: ['/my-app/'], + }) + + const router = createRouter({ + routeTree, + history, + basepath: '/my-app', + }) + + render() + + await waitFor(() => { + expect(screen.getByTestId('home')).toBeInTheDocument() + }) + + expect(router.state.location.pathname).toBe('/') + expect(history.location.pathname).toBe('/my-app/') + }) + it('should combine basepath with additional input rewrite logic', async () => { const rootRoute = createRootRoute({ component: () => , diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 19b77531297..ce379e62f8f 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -1866,8 +1866,12 @@ export class RouterCore< return isEqual } + const latestPublicHref = + this.latestLocation.publicHref ?? this.latestLocation.href + const nextPublicHref = next.publicHref ?? next.href + const isSameUrl = - trimPathRight(this.latestLocation.href) === trimPathRight(next.href) + trimPathRight(latestPublicHref) === trimPathRight(nextPublicHref) const previousCommitPromise = this.commitLocationPromise this.commitLocationPromise = createControlledPromise(() => { @@ -1997,10 +2001,26 @@ export class RouterCore< } catch {} } + const nextLocation = this.buildLocation({ + to, + ...rest, + _includeValidateSearch: true, + _isNavigate: true, + } as any) + + if (!reloadDocument) { + const currentOrigin = new URL(this.latestLocation.url).origin + const nextOrigin = new URL(nextLocation.url).origin + + if (currentOrigin !== nextOrigin) { + reloadDocument = true + href = nextLocation.url + } + } + if (reloadDocument) { if (!href) { - const location = this.buildLocation({ to, ...rest } as any) - href = location.url + href = nextLocation.url } // Check blockers for external URLs unless ignoreBlocker is true @@ -2030,11 +2050,13 @@ export class RouterCore< return Promise.resolve() } - return this.buildAndCommitLocation({ - ...rest, - href, - to: to as string, - _isNavigate: true, + return this.commitLocation({ + ...nextLocation, + replace: rest.replace, + resetScroll: rest.resetScroll, + hashScrollIntoView: rest.hashScrollIntoView, + viewTransition: rest.viewTransition, + ignoreBlocker: rest.ignoreBlocker, }) } diff --git a/packages/solid-router/src/Transitioner.tsx b/packages/solid-router/src/Transitioner.tsx index 1708f1bcb8c..ab8f25ecd56 100644 --- a/packages/solid-router/src/Transitioner.tsx +++ b/packages/solid-router/src/Transitioner.tsx @@ -55,10 +55,20 @@ export function Transitioner() { _includeValidateSearch: true, }) - if ( - trimPathRight(router.latestLocation.href) !== - trimPathRight(nextLocation.href) - ) { + const latestPublicHref = trimPathRight(router.latestLocation.publicHref) + const nextPublicHref = trimPathRight(nextLocation.publicHref) + + if (latestPublicHref !== nextPublicHref) { + const latestOrigin = new URL(router.latestLocation.url).origin + const nextOrigin = new URL(nextLocation.url).origin + + if (latestOrigin !== nextOrigin) { + if (typeof window !== 'undefined') { + window.location.href = nextLocation.url + } + return + } + router.commitLocation({ ...nextLocation, replace: true }) }