diff --git a/e2e/vue-router/basepath-file-based/.gitignore b/e2e/vue-router/basepath-file-based/.gitignore
new file mode 100644
index 00000000000..4d2da67b504
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/.gitignore
@@ -0,0 +1,11 @@
+node_modules
+.DS_Store
+dist
+dist-hash
+dist-ssr
+*.local
+
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/vue-router/basepath-file-based/index.html b/e2e/vue-router/basepath-file-based/index.html
new file mode 100644
index 00000000000..21e30f16951
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/vue-router/basepath-file-based/package.json b/e2e/vue-router/basepath-file-based/package.json
new file mode 100644
index 00000000000..c46fe082281
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "tanstack-router-e2e-vue-basepath-file-based",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port 3000",
+ "dev:e2e": "vite",
+ "build": "vite build && tsc --noEmit",
+ "preview": "vite preview",
+ "start": "vite",
+ "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+ },
+ "dependencies": {
+ "@tailwindcss/postcss": "^4.1.15",
+ "@tanstack/router-plugin": "workspace:^",
+ "@tanstack/vue-query": "^5.90.0",
+ "@tanstack/vue-query-devtools": "^6.1.2",
+ "@tanstack/vue-router": "workspace:^",
+ "@tanstack/vue-router-devtools": "workspace:^",
+ "postcss": "^8.5.1",
+ "redaxios": "^0.5.1",
+ "tailwindcss": "^4.1.17",
+ "vue": "^3.5.16"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.50.1",
+ "@tanstack/router-e2e-utils": "workspace:^",
+ "@vitejs/plugin-vue": "^5.2.3",
+ "@vitejs/plugin-vue-jsx": "^4.1.2",
+ "typescript": "~5.8.3",
+ "vite": "^7.1.7",
+ "vue-tsc": "^3.1.5"
+ }
+}
diff --git a/e2e/vue-router/basepath-file-based/playwright.config.ts b/e2e/vue-router/basepath-file-based/playwright.config.ts
new file mode 100644
index 00000000000..6069bbe59f4
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/playwright.config.ts
@@ -0,0 +1,41 @@
+import { defineConfig, devices } from '@playwright/test'
+import {
+ getDummyServerPort,
+ getTestServerPort,
+} from '@tanstack/router-e2e-utils'
+import packageJson from './package.json' with { type: 'json' }
+
+const PORT = await getTestServerPort(packageJson.name)
+const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
+const baseURL = `http://localhost:${PORT}`
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ workers: 1,
+
+ reporter: [['line']],
+
+ globalSetup: './tests/setup/global.setup.ts',
+ globalTeardown: './tests/setup/global.teardown.ts',
+
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL,
+ },
+
+ webServer: {
+ command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_SERVER_PORT=${PORT} pnpm preview --port ${PORT}`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+})
diff --git a/e2e/vue-router/basepath-file-based/src/main.tsx b/e2e/vue-router/basepath-file-based/src/main.tsx
new file mode 100644
index 00000000000..e573c4a2003
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/src/main.tsx
@@ -0,0 +1,30 @@
+import { createApp, h } from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+// Set up a Router instance
+const router = createRouter({
+ routeTree,
+ defaultPreload: 'intent',
+ defaultStaleTime: 5000,
+ scrollRestoration: true,
+ basepath: '/app/',
+})
+
+// Register things for typesafety
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: typeof router
+ }
+}
+
+const rootElement = document.getElementById('app')!
+
+if (!rootElement.innerHTML) {
+ const app = createApp({
+ setup() {
+ return () => h(RouterProvider, { router })
+ },
+ })
+ app.mount('#app')
+}
diff --git a/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts b/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts
new file mode 100644
index 00000000000..82b561baf1a
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// 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 AboutRouteImport } from './routes/about'
+import { Route as IndexRouteImport } from './routes/index'
+
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/about'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/about'
+ id: '__root__' | '/' | '/about'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AboutRoute: typeof AboutRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ AboutRoute: AboutRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/e2e/vue-router/basepath-file-based/src/routes/__root.tsx b/e2e/vue-router/basepath-file-based/src/routes/__root.tsx
new file mode 100644
index 00000000000..815b2762f6b
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/src/routes/__root.tsx
@@ -0,0 +1,3 @@
+import { createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute()
diff --git a/e2e/vue-router/basepath-file-based/src/routes/about.tsx b/e2e/vue-router/basepath-file-based/src/routes/about.tsx
new file mode 100644
index 00000000000..9fd1a78dd90
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/src/routes/about.tsx
@@ -0,0 +1,20 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/about')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ const navigate = Route.useNavigate()
+
+ return (
+
+
+
+ )
+}
diff --git a/e2e/vue-router/basepath-file-based/src/routes/index.tsx b/e2e/vue-router/basepath-file-based/src/routes/index.tsx
new file mode 100644
index 00000000000..9fc5d3b2adf
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/src/routes/index.tsx
@@ -0,0 +1,25 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: App,
+})
+
+function App() {
+ const navigate = Route.useNavigate()
+
+ return (
+
+
+
+ )
+}
diff --git a/e2e/vue-router/basepath-file-based/tests/reload-document.test.ts b/e2e/vue-router/basepath-file-based/tests/reload-document.test.ts
new file mode 100644
index 00000000000..3e60f3bedcb
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/tests/reload-document.test.ts
@@ -0,0 +1,18 @@
+import { expect, test } from '@playwright/test'
+
+test('navigate() respects basepath for when reloadDocument=true', async ({
+ page,
+}) => {
+ await page.goto(`/app/`)
+ await expect(page.getByTestId(`home-component`)).toBeInViewport()
+
+ const aboutBtn = page.getByTestId(`to-about-btn`)
+ await aboutBtn.click()
+ await page.waitForURL('/app/about')
+ await expect(page.getByTestId(`about-component`)).toBeInViewport()
+
+ const homeBtn = page.getByTestId(`to-home-btn`)
+ await homeBtn.click()
+ await page.waitForURL('/app/')
+ await expect(page.getByTestId(`home-component`)).toBeInViewport()
+})
diff --git a/e2e/vue-router/basepath-file-based/tests/setup/global.setup.ts b/e2e/vue-router/basepath-file-based/tests/setup/global.setup.ts
new file mode 100644
index 00000000000..3593d10ab90
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/tests/setup/global.setup.ts
@@ -0,0 +1,6 @@
+import { e2eStartDummyServer } from '@tanstack/router-e2e-utils'
+import packageJson from '../../package.json' with { type: 'json' }
+
+export default async function setup() {
+ await e2eStartDummyServer(packageJson.name)
+}
diff --git a/e2e/vue-router/basepath-file-based/tests/setup/global.teardown.ts b/e2e/vue-router/basepath-file-based/tests/setup/global.teardown.ts
new file mode 100644
index 00000000000..62fd79911cc
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/tests/setup/global.teardown.ts
@@ -0,0 +1,6 @@
+import { e2eStopDummyServer } from '@tanstack/router-e2e-utils'
+import packageJson from '../../package.json' with { type: 'json' }
+
+export default async function teardown() {
+ await e2eStopDummyServer(packageJson.name)
+}
diff --git a/e2e/vue-router/basepath-file-based/tsconfig.json b/e2e/vue-router/basepath-file-based/tsconfig.json
new file mode 100644
index 00000000000..3ed4d1e8fca
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "target": "ESNext",
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "types": ["vite/client"]
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/e2e/vue-router/basepath-file-based/vite.config.js b/e2e/vue-router/basepath-file-based/vite.config.js
new file mode 100644
index 00000000000..1ee80a8b80d
--- /dev/null
+++ b/e2e/vue-router/basepath-file-based/vite.config.js
@@ -0,0 +1,17 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ base: '/app/',
+ plugins: [
+ tanstackRouter({
+ target: 'vue',
+ autoCodeSplitting: true,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0794615d037..302823f5b2f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3795,6 +3795,61 @@ importers:
specifier: ^5.1.4
version: 5.1.4(typescript@5.8.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+ e2e/vue-router/basepath-file-based:
+ dependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4.1.15
+ version: 4.1.15
+ '@tanstack/router-plugin':
+ specifier: workspace:*
+ version: link:../../../packages/router-plugin
+ '@tanstack/vue-query':
+ specifier: ^5.90.0
+ version: 5.92.0(vue@3.5.25(typescript@5.8.3))
+ '@tanstack/vue-query-devtools':
+ specifier: ^6.1.2
+ version: 6.1.2(@tanstack/vue-query@5.92.0(vue@3.5.25(typescript@5.8.3)))(vue@3.5.25(typescript@5.8.3))
+ '@tanstack/vue-router':
+ specifier: workspace:*
+ version: link:../../../packages/vue-router
+ '@tanstack/vue-router-devtools':
+ specifier: workspace:*
+ version: link:../../../packages/vue-router-devtools
+ postcss:
+ specifier: ^8.5.1
+ version: 8.5.6
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ tailwindcss:
+ specifier: ^4.1.17
+ version: 4.1.17
+ vue:
+ specifier: ^3.5.16
+ version: 3.5.25(typescript@5.8.3)
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.56.1
+ version: 1.56.1
+ '@tanstack/router-e2e-utils':
+ specifier: workspace:^
+ version: link:../../e2e-utils
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.8.3))
+ '@vitejs/plugin-vue-jsx':
+ specifier: ^4.1.2
+ version: 4.2.0(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.8.3))
+ typescript:
+ specifier: ~5.8.3
+ version: 5.8.3
+ vite:
+ specifier: ^7.1.7
+ version: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ vue-tsc:
+ specifier: ^3.1.5
+ version: 3.1.5(typescript@5.8.3)
+
e2e/vue-router/basic:
dependencies:
'@tailwindcss/postcss':