diff --git a/packages/react-router/src/Transitioner.tsx b/packages/react-router/src/Transitioner.tsx index 04e140acfc..882778b3c6 100644 --- a/packages/react-router/src/Transitioner.tsx +++ b/packages/react-router/src/Transitioner.tsx @@ -52,10 +52,10 @@ 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) { router.commitLocation({ ...nextLocation, replace: true }) } diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index 2214e9001e..a2834301f8 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -2836,6 +2836,72 @@ describe('rewriteBasepath utility', () => { 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, + rewrite: rewriteBasepath({ 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 8c644db78b..fc0d71c374 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -290,12 +290,15 @@ export interface RouterOptions< * ```ts * const router = createRouter({ * routeTree, - * rewrite: rewriteBasepath('/basepath') - * // Or wrap existing rewrite functionality - * rewrite: rewriteBasepath('/basepath', { - * output: ({ url }) => {...}, - * input: ({ url }) => {...}, - * }) + * rewrite: rewriteBasepath({ basepath: '/basepath' }) + * // Or compose with existing rewrite functionality + * rewrite: composeRewrites([ + * rewriteBasepath({ basepath: '/basepath', caseSensitive: true }), + * { + * input: ({ url }) => {...}, + * output: ({ url }) => {...}, + * } + * ]) * }) * ``` * @default '/' @@ -1688,7 +1691,7 @@ export class RouterCore< return { publicHref: rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash, - href: fullPath, + href: rewrittenUrl.href.replace(rewrittenUrl.origin, ''), url: rewrittenUrl.href, pathname: nextPathname, search: nextSearch, @@ -1784,8 +1787,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(() => { @@ -1942,11 +1949,14 @@ export class RouterCore< } } + const latestPublicHref = this.latestLocation.publicHref + const nextPublicHref = nextLocation.publicHref + if ( - trimPath(normalizeUrl(this.latestLocation.href)) !== - trimPath(normalizeUrl(nextLocation.href)) + trimPath(normalizeUrl(latestPublicHref)) !== + trimPath(normalizeUrl(nextPublicHref)) ) { - throw redirect({ href: nextLocation.href }) + throw redirect({ href: nextPublicHref }) } } diff --git a/packages/solid-router/src/Transitioner.tsx b/packages/solid-router/src/Transitioner.tsx index 2d602a2022..6aa52ba6dd 100644 --- a/packages/solid-router/src/Transitioner.tsx +++ b/packages/solid-router/src/Transitioner.tsx @@ -50,10 +50,10 @@ 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) { router.commitLocation({ ...nextLocation, replace: true }) }