Repository: TanStack/router (ts)

Issue:
Issue resolving navigations to routes when the basepath partially matches a route path
### Describe the bug

From https://discord.com/channels/719702312431386674/1265399137105481759

### Your Example Website or App

https://stackblitz.com/edit/tanstack-router-yw832e?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

1. Go to the index page of the reproduction
2. Try clicking on the link "Products" at the end of the page.
3. Notice the notFound component thrown and the incorrect URL in the address bar.

### Expected behavior

When calling a navigation with a basepath should resolve the same as when the property hasn't been set.

### Screenshots or Videos

_No response_

### Platform

TSR 1.43.4

### Additional context

_No response_

Pull request description:
fix: correctly resolve paths when basepath is set
fixes #2019

Patch:
diff --git a/packages/react-router/src/path.ts b/packages/react-router/src/path.ts
index c2ed2978e..cd936ce2b 100644
--- a/packages/react-router/src/path.ts
+++ b/packages/react-router/src/path.ts
@@ -89,8 +89,8 @@ export function resolvePath({
   to,
   trailingSlash = 'never',
 }: ResolvePathOptions) {
-  base = base.replace(new RegExp(`^${basepath}`), '/')
-  to = to.replace(new RegExp(`^${basepath}`), '/')
+  base = removeBasepath(basepath, base)
+  to = removeBasepath(basepath, to)
 
   let baseSegments = parsePathname(base)
   const toSegments = parsePathname(to)


Test patch:
diff --git a/packages/react-router/tests/path.test.ts b/packages/react-router/tests/path.test.ts
index 9f587606d..b43710313 100644
--- a/packages/react-router/tests/path.test.ts
+++ b/packages/react-router/tests/path.test.ts
@@ -1,5 +1,11 @@
+import exp from 'node:constants'
 import { describe, expect, it } from 'vitest'
-import { exactPathTest, removeBasepath, removeTrailingSlash } from '../src/path'
+import {
+  exactPathTest,
+  removeBasepath,
+  removeTrailingSlash,
+  resolvePath,
+} from '../src/path'
 
 describe('removeBasepath', () => {
   it.each([
@@ -112,3 +118,42 @@ describe.each([{ basepath: '/' }, { basepath: '/app' }, { basepath: '/app/' }])(
     })
   },
 )
+
+describe('resolvePath', () => {
+  it.each([
+    { base: '/a/b/c', to: './d', expected: '/a/b/c/d' },
+    { base: '/a/b/c', to: '../d', expected: '/a/b/d' },
+    { base: '/a/b/c', to: './d/', expected: '/a/b/c/d' },
+    { base: '/a/b/c', to: './', expected: '/a/b/c' },
+  ])(
+    'should resolve relative paths: $base + $to = $expected',
+    ({ base, to, expected }) => {
+      expect(resolvePath({ basepath: '/', base, to })).toBe(expected)
+    },
+  )
+
+  it.each([
+    { base: '/a/b/c', to: '/d', expected: '/d' },
+    { base: '/a/b/c', to: '/d/', expected: '/d' },
+    { base: '/a/b/c', to: '/', expected: '/' },
+  ])(
+    'should resolve absolute paths that start with `/`: $base + $to = $expected',
+    ({ base, to, expected }) => {
+      expect(resolvePath({ basepath: '/', base, to })).toBe(expected)
+    },
+  )
+
+  it.each([
+    { base: '/a/b/c', to: 'd', expected: '/a/b/c/d' },
+    { base: '/a/b/c', to: 'd/', expected: '/a/b/c/d' },
+    { base: '/a/b/c', to: 'd/e', expected: '/a/b/c/d/e' },
+  ])('should resolve non-.-prefixed paths', ({ base, to, expected }) => {
+    expect(resolvePath({ basepath: '/', base, to })).toBe(expected)
+  })
+
+  it('should correctly resolve paths when basepath is set', () => {
+    expect(
+      resolvePath({ basepath: '/products', base: '/', to: '/products-list' }),
+    ).toBe('/products/products-list')
+  })
+})