diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx
index d58dfe2126c..2214e9001eb 100644
--- a/packages/react-router/tests/router.test.tsx
+++ b/packages/react-router/tests/router.test.tsx
@@ -2670,6 +2670,144 @@ describe('rewriteBasepath utility', () => {
expect(router.state.location.pathname).toBe('/users')
})
+ it.each([
+ {
+ description: 'basepath with leading slash but without trailing slash',
+ basepath: '/api/v1',
+ },
+ {
+ description: 'basepath without leading slash but with trailing slash',
+ basepath: 'api/v1/',
+ },
+ {
+ description: 'basepath without leading and trailing slashes',
+ basepath: 'api/v1',
+ },
+ ])('should handle $description', async ({ basepath }) => {
+ const rootRoute = createRootRoute({
+ component: () => ,
+ })
+
+ const usersRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/users',
+ component: () =>
Users
,
+ })
+
+ const routeTree = rootRoute.addChildren([usersRoute])
+
+ const router = createRouter({
+ routeTree,
+ history: createMemoryHistory({
+ initialEntries: ['/api/v1/users'],
+ }),
+ rewrite: rewriteBasepath({ basepath }),
+ })
+
+ render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('users')).toBeInTheDocument()
+ })
+
+ expect(router.state.location.pathname).toBe('/users')
+ })
+
+ it.each([
+ { description: 'has trailing slash', basepath: '/my-app/' },
+ { description: 'has no trailing slash', basepath: '/my-app' },
+ ])(
+ 'should not resolve to 404 when basepath $description and URL matches',
+ async ({ basepath }) => {
+ const rootRoute = createRootRoute({
+ component: () => ,
+ })
+
+ const homeRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => Home
,
+ })
+
+ const usersRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/users',
+ component: () => Users
,
+ })
+
+ const routeTree = rootRoute.addChildren([homeRoute, usersRoute])
+
+ const router = createRouter({
+ routeTree,
+ history: createMemoryHistory({
+ initialEntries: ['/my-app/'],
+ }),
+ rewrite: rewriteBasepath({ basepath }),
+ })
+
+ render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('home')).toBeInTheDocument()
+ })
+
+ expect(router.state.location.pathname).toBe('/')
+ expect(router.state.statusCode).toBe(200)
+ },
+ )
+
+ it.each([
+ { description: 'with trailing slash', basepath: '/my-app/' },
+ { description: 'without trailing slash', basepath: '/my-app' },
+ ])(
+ 'should handle basepath $description when navigating to root path',
+ async ({ basepath }) => {
+ const rootRoute = createRootRoute({
+ component: () => ,
+ })
+
+ const homeRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => (
+
+
+ About
+
+
+ ),
+ })
+
+ const aboutRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/about',
+ component: () => About
,
+ })
+
+ const routeTree = rootRoute.addChildren([homeRoute, aboutRoute])
+
+ const history = createMemoryHistory({ initialEntries: ['/my-app/'] })
+
+ const router = createRouter({
+ routeTree,
+ history,
+ rewrite: rewriteBasepath({ basepath }),
+ })
+
+ render()
+
+ const aboutLink = await screen.findByTestId('about-link')
+ fireEvent.click(aboutLink)
+
+ await waitFor(() => {
+ expect(screen.getByTestId('about')).toBeInTheDocument()
+ })
+
+ expect(router.state.location.pathname).toBe('/about')
+ expect(history.location.pathname).toBe('/my-app/about')
+ },
+ )
+
it('should handle empty basepath gracefully', async () => {
const rootRoute = createRootRoute({
component: () => ,
diff --git a/packages/router-core/src/rewrite.ts b/packages/router-core/src/rewrite.ts
index c0cef0cc879..7ffb49b8e15 100644
--- a/packages/router-core/src/rewrite.ts
+++ b/packages/router-core/src/rewrite.ts
@@ -23,13 +23,28 @@ export function rewriteBasepath(opts: {
caseSensitive?: boolean
}) {
const trimmedBasepath = trimPath(opts.basepath)
- const regex = new RegExp(
- `^/${trimmedBasepath}/`,
- opts.caseSensitive ? '' : 'i',
- )
+ const normalizedBasepath = `/${trimmedBasepath}`
+ const normalizedBasepathWithSlash = `${normalizedBasepath}/`
+ const checkBasepath = opts.caseSensitive
+ ? normalizedBasepath
+ : normalizedBasepath.toLowerCase()
+ const checkBasepathWithSlash = opts.caseSensitive
+ ? normalizedBasepathWithSlash
+ : normalizedBasepathWithSlash.toLowerCase()
+
return {
input: ({ url }) => {
- url.pathname = url.pathname.replace(regex, '/')
+ const pathname = opts.caseSensitive
+ ? url.pathname
+ : url.pathname.toLowerCase()
+
+ // 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 }) => {