From 6aa5e7b811034134369272cd6efcd7951eb65d09 Mon Sep 17 00:00:00 2001
From: Birk Skyum <birk.skyum@pm.me>
Date: Wed, 17 Dec 2025 22:40:26 +0100
Subject: [PATCH 1/4] vue-start custom-basepath

---
 e2e/vue-start/custom-basepath/.gitignore      |  20 +
 e2e/vue-start/custom-basepath/.prettierignore |   4 +
 .../custom-basepath/express-server.ts         |  44 +++
 e2e/vue-start/custom-basepath/package.json    |  38 ++
 .../custom-basepath/playwright.config.ts      |  42 ++
 .../custom-basepath/postcss.config.mjs        |   5 +
 .../public/android-chrome-192x192.png         | Bin 0 -> 29964 bytes
 .../public/android-chrome-512x512.png         | Bin 0 -> 109271 bytes
 .../public/apple-touch-icon.png               | Bin 0 -> 27246 bytes
 .../custom-basepath/public/favicon-16x16.png  | Bin 0 -> 832 bytes
 .../custom-basepath/public/favicon-32x32.png  | Bin 0 -> 2115 bytes
 .../custom-basepath/public/favicon.ico        | Bin 0 -> 15406 bytes
 .../custom-basepath/public/favicon.png        | Bin 0 -> 1507 bytes
 .../custom-basepath/public/script.js          |   2 +
 .../custom-basepath/public/script2.js         |   2 +
 .../custom-basepath/public/site.webmanifest   |  19 +
 .../src/components/CustomMessage.tsx          |   8 +
 .../src/components/DefaultCatchBoundary.tsx   |  53 +++
 .../src/components/NotFound.tsx               |  25 ++
 .../src/components/PostErrorComponent.tsx     |   5 +
 .../src/components/UserErrorComponent.tsx     |   6 +
 .../custom-basepath/src/routeTree.gen.ts      | 367 ++++++++++++++++++
 e2e/vue-start/custom-basepath/src/router.tsx  |  18 +
 .../custom-basepath/src/routes/__root.tsx     | 114 ++++++
 .../src/routes/api/users.$userId.ts           |  34 ++
 .../custom-basepath/src/routes/api/users.ts   |  65 ++++
 .../custom-basepath/src/routes/deferred.tsx   |  65 ++++
 .../custom-basepath/src/routes/index.tsx      |  15 +
 .../custom-basepath/src/routes/logout.tsx     |  32 ++
 .../src/routes/posts.$postId.tsx              |  35 ++
 .../src/routes/posts.index.tsx                |   8 +
 .../custom-basepath/src/routes/posts.tsx      |  47 +++
 .../src/routes/posts_.$postId.deep.tsx        |  24 ++
 .../src/routes/redirect.index.tsx             |  15 +
 .../src/routes/redirect.throw-it.tsx          |  10 +
 .../src/routes/users.$userId.tsx              |  34 ++
 .../src/routes/users.index.tsx                |   8 +
 .../custom-basepath/src/routes/users.tsx      |  48 +++
 e2e/vue-start/custom-basepath/src/server.ts   |   7 +
 .../custom-basepath/src/styles/app.css        |  30 ++
 .../custom-basepath/src/utils/basepath.ts     |   1 +
 .../custom-basepath/src/utils/posts.tsx       |  42 ++
 .../custom-basepath/src/utils/seo.ts          |  33 ++
 .../custom-basepath/src/utils/users.tsx       |   9 +
 .../custom-basepath/src/vite-env.d.ts         |   4 +
 .../custom-basepath/tests/navigation.spec.ts  |  78 ++++
 .../tests/setup/global.setup.ts               |   6 +
 .../tests/setup/global.teardown.ts            |   6 +
 e2e/vue-start/custom-basepath/tsconfig.json   |  24 ++
 e2e/vue-start/custom-basepath/vite.config.ts  |  20 +
 pnpm-lock.yaml                                | 154 ++++++--
 ...e2e-react-basepath-file-based-external.txt |   1 +
 ...k-router-e2e-react-basepath-file-based.txt |   1 +
 53 files changed, 1588 insertions(+), 40 deletions(-)
 create mode 100644 e2e/vue-start/custom-basepath/.gitignore
 create mode 100644 e2e/vue-start/custom-basepath/.prettierignore
 create mode 100644 e2e/vue-start/custom-basepath/express-server.ts
 create mode 100644 e2e/vue-start/custom-basepath/package.json
 create mode 100644 e2e/vue-start/custom-basepath/playwright.config.ts
 create mode 100644 e2e/vue-start/custom-basepath/postcss.config.mjs
 create mode 100644 e2e/vue-start/custom-basepath/public/android-chrome-192x192.png
 create mode 100644 e2e/vue-start/custom-basepath/public/android-chrome-512x512.png
 create mode 100644 e2e/vue-start/custom-basepath/public/apple-touch-icon.png
 create mode 100644 e2e/vue-start/custom-basepath/public/favicon-16x16.png
 create mode 100644 e2e/vue-start/custom-basepath/public/favicon-32x32.png
 create mode 100644 e2e/vue-start/custom-basepath/public/favicon.ico
 create mode 100644 e2e/vue-start/custom-basepath/public/favicon.png
 create mode 100644 e2e/vue-start/custom-basepath/public/script.js
 create mode 100644 e2e/vue-start/custom-basepath/public/script2.js
 create mode 100644 e2e/vue-start/custom-basepath/public/site.webmanifest
 create mode 100644 e2e/vue-start/custom-basepath/src/components/CustomMessage.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/components/NotFound.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/components/PostErrorComponent.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/components/UserErrorComponent.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routeTree.gen.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/router.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/__root.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/api/users.$userId.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/api/users.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/deferred.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/index.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/logout.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/posts.index.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/posts.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/redirect.index.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/redirect.throw-it.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/users.index.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/routes/users.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/server.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/styles/app.css
 create mode 100644 e2e/vue-start/custom-basepath/src/utils/basepath.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/utils/posts.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/utils/seo.ts
 create mode 100644 e2e/vue-start/custom-basepath/src/utils/users.tsx
 create mode 100644 e2e/vue-start/custom-basepath/src/vite-env.d.ts
 create mode 100644 e2e/vue-start/custom-basepath/tests/navigation.spec.ts
 create mode 100644 e2e/vue-start/custom-basepath/tests/setup/global.setup.ts
 create mode 100644 e2e/vue-start/custom-basepath/tests/setup/global.teardown.ts
 create mode 100644 e2e/vue-start/custom-basepath/tsconfig.json
 create mode 100644 e2e/vue-start/custom-basepath/vite.config.ts
 create mode 100644 port-tanstack-router-e2e-react-basepath-file-based-external.txt
 create mode 100644 port-tanstack-router-e2e-react-basepath-file-based.txt

diff --git a/e2e/vue-start/custom-basepath/.gitignore b/e2e/vue-start/custom-basepath/.gitignore
new file mode 100644
index 0000000000..a79d5cf129
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/.gitignore
@@ -0,0 +1,20 @@
+node_modules
+package-lock.json
+yarn.lock
+
+.DS_Store
+.cache
+.env
+.vercel
+.output
+
+/build/
+/api/
+/server/build
+/public/build
+# Sentry Config File
+.env.sentry-build-plugin
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/vue-start/custom-basepath/.prettierignore b/e2e/vue-start/custom-basepath/.prettierignore
new file mode 100644
index 0000000000..2be5eaa6ec
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/.prettierignore
@@ -0,0 +1,4 @@
+**/build
+**/public
+pnpm-lock.yaml
+routeTree.gen.ts
\ No newline at end of file
diff --git a/e2e/vue-start/custom-basepath/express-server.ts b/e2e/vue-start/custom-basepath/express-server.ts
new file mode 100644
index 0000000000..6fa802e447
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/express-server.ts
@@ -0,0 +1,44 @@
+import express from 'express'
+import { toNodeHandler } from 'srvx/node'
+
+const DEVELOPMENT = process.env.NODE_ENV === 'development'
+const PORT = Number.parseInt(process.env.PORT || '3000')
+
+const app = express()
+
+if (DEVELOPMENT) {
+  const viteDevServer = await import('vite').then((vite) =>
+    vite.createServer({
+      server: { middlewareMode: true },
+    }),
+  )
+  app.use(viteDevServer.middlewares)
+  app.use(async (req, res, next) => {
+    try {
+      const { default: serverEntry } =
+        await viteDevServer.ssrLoadModule('./src/server.ts')
+      const handler = toNodeHandler(serverEntry.fetch)
+      await handler(req, res)
+    } catch (error) {
+      if (typeof error === 'object' && error instanceof Error) {
+        viteDevServer.ssrFixStacktrace(error)
+      }
+      next(error)
+    }
+  })
+} else {
+  const { default: handler } = await import('./dist/server/server.js')
+  const nodeHandler = toNodeHandler(handler.fetch)
+  app.use('/custom/basepath', express.static('dist/client'))
+  app.use(async (req, res, next) => {
+    try {
+      await nodeHandler(req, res)
+    } catch (error) {
+      next(error)
+    }
+  })
+}
+
+app.listen(PORT, () => {
+  console.log(`Server is running on http://localhost:${PORT}`)
+})
diff --git a/e2e/vue-start/custom-basepath/package.json b/e2e/vue-start/custom-basepath/package.json
new file mode 100644
index 0000000000..b897f78d83
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/package.json
@@ -0,0 +1,38 @@
+{
+  "name": "tanstack-vue-start-e2e-custom-basepath",
+  "private": true,
+  "sideEffects": false,
+  "type": "module",
+  "scripts": {
+    "dev": "cross-env NODE_ENV=development tsx express-server.ts",
+    "build": "vite build && tsc --noEmit",
+    "preview": "vite preview",
+    "start": "tsx express-server.ts",
+    "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+  },
+  "dependencies": {
+    "@tanstack/vue-router": "workspace:^",
+    "@tanstack/vue-router-devtools": "workspace:^",
+    "@tanstack/vue-start": "workspace:^",
+    "express": "^4.21.2",
+    "redaxios": "^0.5.1",
+    "vue": "^3.5.25"
+  },
+  "devDependencies": {
+    "@playwright/test": "^1.50.1",
+    "@tailwindcss/postcss": "^4.1.15",
+    "@tanstack/router-e2e-utils": "workspace:^",
+    "@types/express": "^5.0.3",
+    "@types/node": "^22.10.2",
+    "cross-env": "^10.0.0",
+    "postcss": "^8.5.1",
+    "srvx": "^0.9.8",
+    "tailwindcss": "^4.1.17",
+    "tsx": "^4.20.3",
+    "typescript": "^5.7.2",
+    "vite": "^7.1.7",
+    "@vitejs/plugin-vue": "^6.0.3",
+    "@vitejs/plugin-vue-jsx": "^5.1.2",
+    "vite-tsconfig-paths": "^5.1.4"
+  }
+}
diff --git a/e2e/vue-start/custom-basepath/playwright.config.ts b/e2e/vue-start/custom-basepath/playwright.config.ts
new file mode 100644
index 0000000000..5095425f47
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/playwright.config.ts
@@ -0,0 +1,42 @@
+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}/custom/basepath`
+
+/**
+ * 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_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} PORT=${PORT} pnpm start`,
+    url: baseURL,
+    reuseExistingServer: !process.env.CI,
+    stdout: 'pipe',
+  },
+
+  projects: [
+    {
+      name: 'chromium',
+      use: { ...devices['Desktop Chrome'] },
+    },
+  ],
+})
diff --git a/e2e/vue-start/custom-basepath/postcss.config.mjs b/e2e/vue-start/custom-basepath/postcss.config.mjs
new file mode 100644
index 0000000000..a7f73a2d1d
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/postcss.config.mjs
@@ -0,0 +1,5 @@
+export default {
+  plugins: {
+    '@tailwindcss/postcss': {},
+  },
+}
diff --git a/e2e/vue-start/custom-basepath/public/android-chrome-192x192.png b/e2e/vue-start/custom-basepath/public/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..09c8324f8c6781bc90fcf5dd38e0702bd5f171f3
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/android-chrome-512x512.png b/e2e/vue-start/custom-basepath/public/android-chrome-512x512.png
new file mode 100644
index 0000000000000000000000000000000000000000..11d626ea3d00fddd52861bf0af5554a92fc30d69
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/apple-touch-icon.png b/e2e/vue-start/custom-basepath/public/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a9423cc02c40ea066d2a061cdcdf621c0718cb1
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/favicon-16x16.png b/e2e/vue-start/custom-basepath/public/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3389b00443d602a249b48ae0e21e333971c9c1e
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/favicon-32x32.png b/e2e/vue-start/custom-basepath/public/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..900c77d444c9b31e6144b6ac0bb1ab7bb7ca767f
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/favicon.ico b/e2e/vue-start/custom-basepath/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..1a1751676f7e22811b1070572093996c93c87617
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/favicon.png b/e2e/vue-start/custom-basepath/public/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..1e77bc06091ade4496525a09d8900675afcf03f0
GIT binary patch
diff --git a/e2e/vue-start/custom-basepath/public/script.js b/e2e/vue-start/custom-basepath/public/script.js
new file mode 100644
index 0000000000..897477e7d0
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/public/script.js
@@ -0,0 +1,2 @@
+console.log('SCRIPT_1 loaded')
+window.SCRIPT_1 = true
diff --git a/e2e/vue-start/custom-basepath/public/script2.js b/e2e/vue-start/custom-basepath/public/script2.js
new file mode 100644
index 0000000000..819af30daf
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/public/script2.js
@@ -0,0 +1,2 @@
+console.log('SCRIPT_2 loaded')
+window.SCRIPT_2 = true
diff --git a/e2e/vue-start/custom-basepath/public/site.webmanifest b/e2e/vue-start/custom-basepath/public/site.webmanifest
new file mode 100644
index 0000000000..fa99de77db
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/public/site.webmanifest
@@ -0,0 +1,19 @@
+{
+  "name": "",
+  "short_name": "",
+  "icons": [
+    {
+      "src": "/android-chrome-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "/android-chrome-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "theme_color": "#ffffff",
+  "background_color": "#ffffff",
+  "display": "standalone"
+}
diff --git a/e2e/vue-start/custom-basepath/src/components/CustomMessage.tsx b/e2e/vue-start/custom-basepath/src/components/CustomMessage.tsx
new file mode 100644
index 0000000000..c038b66d2f
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/components/CustomMessage.tsx
@@ -0,0 +1,8 @@
+export function CustomMessage({ message }: { message: string }) {
+  return (
+    <div class="py-2">
+      <div class="italic">This is a custom message:</div>
+      <p>{message}</p>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx b/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
new file mode 100644
index 0000000000..d6b3c73288
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
@@ -0,0 +1,53 @@
+import {
+  ErrorComponent,
+  Link,
+  rootRouteId,
+  useMatch,
+  useRouter,
+} from '@tanstack/vue-router'
+import type { ErrorComponentProps } from '@tanstack/vue-router'
+
+export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
+  const router = useRouter()
+  const isRoot = useMatch({
+    strict: false,
+    select: (state) => state.id === rootRouteId,
+  })
+
+  console.error(error)
+
+  return (
+    <div class="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
+      <ErrorComponent error={error} />
+      <div class="flex gap-2 items-center flex-wrap">
+        <button
+          onClick={() => {
+            router.invalidate()
+          }}
+          class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+        >
+          Try Again
+        </button>
+        {isRoot() ? (
+          <Link
+            to="/"
+            class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+          >
+            Home
+          </Link>
+        ) : (
+          <Link
+            to="/"
+            class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+            onClick={(e) => {
+              e.preventDefault()
+              window.history.back()
+            }}
+          >
+            Go Back
+          </Link>
+        )}
+      </div>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/components/NotFound.tsx b/e2e/vue-start/custom-basepath/src/components/NotFound.tsx
new file mode 100644
index 0000000000..944e35c12c
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/components/NotFound.tsx
@@ -0,0 +1,25 @@
+import { Link } from '@tanstack/vue-router'
+
+export function NotFound({ children }: { children?: any }) {
+  return (
+    <div class="space-y-2 p-2" data-testid="default-not-found-component">
+      <div class="text-gray-600 dark:text-gray-400">
+        {children || <p>The page you are looking for does not exist.</p>}
+      </div>
+      <p class="flex items-center gap-2 flex-wrap">
+        <button
+          onClick={() => window.history.back()}
+          class="bg-emerald-500 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
+        >
+          Go back
+        </button>
+        <Link
+          to="/"
+          class="bg-cyan-600 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
+        >
+          Start Over
+        </Link>
+      </p>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/components/PostErrorComponent.tsx b/e2e/vue-start/custom-basepath/src/components/PostErrorComponent.tsx
new file mode 100644
index 0000000000..6aef8ebdc0
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/components/PostErrorComponent.tsx
@@ -0,0 +1,5 @@
+import { ErrorComponent, ErrorComponentProps } from '@tanstack/vue-router'
+
+export function PostErrorComponent({ error }: ErrorComponentProps) {
+  return <ErrorComponent error={error} />
+}
diff --git a/e2e/vue-start/custom-basepath/src/components/UserErrorComponent.tsx b/e2e/vue-start/custom-basepath/src/components/UserErrorComponent.tsx
new file mode 100644
index 0000000000..34151b8e52
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/components/UserErrorComponent.tsx
@@ -0,0 +1,6 @@
+import { ErrorComponent } from '@tanstack/vue-router'
+import type { ErrorComponentProps } from '@tanstack/vue-router'
+
+export function UserErrorComponent({ error }: ErrorComponentProps) {
+  return <ErrorComponent error={error} />
+}
diff --git a/e2e/vue-start/custom-basepath/src/routeTree.gen.ts b/e2e/vue-start/custom-basepath/src/routeTree.gen.ts
new file mode 100644
index 0000000000..2aa189fd3c
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routeTree.gen.ts
@@ -0,0 +1,367 @@
+/* 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 UsersRouteImport } from './routes/users'
+import { Route as PostsRouteImport } from './routes/posts'
+import { Route as LogoutRouteImport } from './routes/logout'
+import { Route as DeferredRouteImport } from './routes/deferred'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as UsersIndexRouteImport } from './routes/users.index'
+import { Route as RedirectIndexRouteImport } from './routes/redirect.index'
+import { Route as PostsIndexRouteImport } from './routes/posts.index'
+import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
+import { Route as RedirectThrowItRouteImport } from './routes/redirect.throw-it'
+import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
+import { Route as ApiUsersRouteImport } from './routes/api/users'
+import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep'
+import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId'
+
+const UsersRoute = UsersRouteImport.update({
+  id: '/users',
+  path: '/users',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const PostsRoute = PostsRouteImport.update({
+  id: '/posts',
+  path: '/posts',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const LogoutRoute = LogoutRouteImport.update({
+  id: '/logout',
+  path: '/logout',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const DeferredRoute = DeferredRouteImport.update({
+  id: '/deferred',
+  path: '/deferred',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+  id: '/',
+  path: '/',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const UsersIndexRoute = UsersIndexRouteImport.update({
+  id: '/',
+  path: '/',
+  getParentRoute: () => UsersRoute,
+} as any)
+const RedirectIndexRoute = RedirectIndexRouteImport.update({
+  id: '/redirect/',
+  path: '/redirect/',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const PostsIndexRoute = PostsIndexRouteImport.update({
+  id: '/',
+  path: '/',
+  getParentRoute: () => PostsRoute,
+} as any)
+const UsersUserIdRoute = UsersUserIdRouteImport.update({
+  id: '/$userId',
+  path: '/$userId',
+  getParentRoute: () => UsersRoute,
+} as any)
+const RedirectThrowItRoute = RedirectThrowItRouteImport.update({
+  id: '/redirect/throw-it',
+  path: '/redirect/throw-it',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const PostsPostIdRoute = PostsPostIdRouteImport.update({
+  id: '/$postId',
+  path: '/$postId',
+  getParentRoute: () => PostsRoute,
+} as any)
+const ApiUsersRoute = ApiUsersRouteImport.update({
+  id: '/api/users',
+  path: '/api/users',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({
+  id: '/posts_/$postId/deep',
+  path: '/posts/$postId/deep',
+  getParentRoute: () => rootRouteImport,
+} as any)
+const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({
+  id: '/$userId',
+  path: '/$userId',
+  getParentRoute: () => ApiUsersRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+  '/': typeof IndexRoute
+  '/deferred': typeof DeferredRoute
+  '/logout': typeof LogoutRoute
+  '/posts': typeof PostsRouteWithChildren
+  '/users': typeof UsersRouteWithChildren
+  '/api/users': typeof ApiUsersRouteWithChildren
+  '/posts/$postId': typeof PostsPostIdRoute
+  '/redirect/throw-it': typeof RedirectThrowItRoute
+  '/users/$userId': typeof UsersUserIdRoute
+  '/posts/': typeof PostsIndexRoute
+  '/redirect': typeof RedirectIndexRoute
+  '/users/': typeof UsersIndexRoute
+  '/api/users/$userId': typeof ApiUsersUserIdRoute
+  '/posts/$postId/deep': typeof PostsPostIdDeepRoute
+}
+export interface FileRoutesByTo {
+  '/': typeof IndexRoute
+  '/deferred': typeof DeferredRoute
+  '/logout': typeof LogoutRoute
+  '/api/users': typeof ApiUsersRouteWithChildren
+  '/posts/$postId': typeof PostsPostIdRoute
+  '/redirect/throw-it': typeof RedirectThrowItRoute
+  '/users/$userId': typeof UsersUserIdRoute
+  '/posts': typeof PostsIndexRoute
+  '/redirect': typeof RedirectIndexRoute
+  '/users': typeof UsersIndexRoute
+  '/api/users/$userId': typeof ApiUsersUserIdRoute
+  '/posts/$postId/deep': typeof PostsPostIdDeepRoute
+}
+export interface FileRoutesById {
+  __root__: typeof rootRouteImport
+  '/': typeof IndexRoute
+  '/deferred': typeof DeferredRoute
+  '/logout': typeof LogoutRoute
+  '/posts': typeof PostsRouteWithChildren
+  '/users': typeof UsersRouteWithChildren
+  '/api/users': typeof ApiUsersRouteWithChildren
+  '/posts/$postId': typeof PostsPostIdRoute
+  '/redirect/throw-it': typeof RedirectThrowItRoute
+  '/users/$userId': typeof UsersUserIdRoute
+  '/posts/': typeof PostsIndexRoute
+  '/redirect/': typeof RedirectIndexRoute
+  '/users/': typeof UsersIndexRoute
+  '/api/users/$userId': typeof ApiUsersUserIdRoute
+  '/posts_/$postId/deep': typeof PostsPostIdDeepRoute
+}
+export interface FileRouteTypes {
+  fileRoutesByFullPath: FileRoutesByFullPath
+  fullPaths:
+    | '/'
+    | '/deferred'
+    | '/logout'
+    | '/posts'
+    | '/users'
+    | '/api/users'
+    | '/posts/$postId'
+    | '/redirect/throw-it'
+    | '/users/$userId'
+    | '/posts/'
+    | '/redirect'
+    | '/users/'
+    | '/api/users/$userId'
+    | '/posts/$postId/deep'
+  fileRoutesByTo: FileRoutesByTo
+  to:
+    | '/'
+    | '/deferred'
+    | '/logout'
+    | '/api/users'
+    | '/posts/$postId'
+    | '/redirect/throw-it'
+    | '/users/$userId'
+    | '/posts'
+    | '/redirect'
+    | '/users'
+    | '/api/users/$userId'
+    | '/posts/$postId/deep'
+  id:
+    | '__root__'
+    | '/'
+    | '/deferred'
+    | '/logout'
+    | '/posts'
+    | '/users'
+    | '/api/users'
+    | '/posts/$postId'
+    | '/redirect/throw-it'
+    | '/users/$userId'
+    | '/posts/'
+    | '/redirect/'
+    | '/users/'
+    | '/api/users/$userId'
+    | '/posts_/$postId/deep'
+  fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+  IndexRoute: typeof IndexRoute
+  DeferredRoute: typeof DeferredRoute
+  LogoutRoute: typeof LogoutRoute
+  PostsRoute: typeof PostsRouteWithChildren
+  UsersRoute: typeof UsersRouteWithChildren
+  ApiUsersRoute: typeof ApiUsersRouteWithChildren
+  RedirectThrowItRoute: typeof RedirectThrowItRoute
+  RedirectIndexRoute: typeof RedirectIndexRoute
+  PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
+}
+
+declare module '@tanstack/vue-router' {
+  interface FileRoutesByPath {
+    '/users': {
+      id: '/users'
+      path: '/users'
+      fullPath: '/users'
+      preLoaderRoute: typeof UsersRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/posts': {
+      id: '/posts'
+      path: '/posts'
+      fullPath: '/posts'
+      preLoaderRoute: typeof PostsRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/logout': {
+      id: '/logout'
+      path: '/logout'
+      fullPath: '/logout'
+      preLoaderRoute: typeof LogoutRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/deferred': {
+      id: '/deferred'
+      path: '/deferred'
+      fullPath: '/deferred'
+      preLoaderRoute: typeof DeferredRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof IndexRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/users/': {
+      id: '/users/'
+      path: '/'
+      fullPath: '/users/'
+      preLoaderRoute: typeof UsersIndexRouteImport
+      parentRoute: typeof UsersRoute
+    }
+    '/redirect/': {
+      id: '/redirect/'
+      path: '/redirect'
+      fullPath: '/redirect'
+      preLoaderRoute: typeof RedirectIndexRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/posts/': {
+      id: '/posts/'
+      path: '/'
+      fullPath: '/posts/'
+      preLoaderRoute: typeof PostsIndexRouteImport
+      parentRoute: typeof PostsRoute
+    }
+    '/users/$userId': {
+      id: '/users/$userId'
+      path: '/$userId'
+      fullPath: '/users/$userId'
+      preLoaderRoute: typeof UsersUserIdRouteImport
+      parentRoute: typeof UsersRoute
+    }
+    '/redirect/throw-it': {
+      id: '/redirect/throw-it'
+      path: '/redirect/throw-it'
+      fullPath: '/redirect/throw-it'
+      preLoaderRoute: typeof RedirectThrowItRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/posts/$postId': {
+      id: '/posts/$postId'
+      path: '/$postId'
+      fullPath: '/posts/$postId'
+      preLoaderRoute: typeof PostsPostIdRouteImport
+      parentRoute: typeof PostsRoute
+    }
+    '/api/users': {
+      id: '/api/users'
+      path: '/api/users'
+      fullPath: '/api/users'
+      preLoaderRoute: typeof ApiUsersRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/posts_/$postId/deep': {
+      id: '/posts_/$postId/deep'
+      path: '/posts/$postId/deep'
+      fullPath: '/posts/$postId/deep'
+      preLoaderRoute: typeof PostsPostIdDeepRouteImport
+      parentRoute: typeof rootRouteImport
+    }
+    '/api/users/$userId': {
+      id: '/api/users/$userId'
+      path: '/$userId'
+      fullPath: '/api/users/$userId'
+      preLoaderRoute: typeof ApiUsersUserIdRouteImport
+      parentRoute: typeof ApiUsersRoute
+    }
+  }
+}
+
+interface PostsRouteChildren {
+  PostsPostIdRoute: typeof PostsPostIdRoute
+  PostsIndexRoute: typeof PostsIndexRoute
+}
+
+const PostsRouteChildren: PostsRouteChildren = {
+  PostsPostIdRoute: PostsPostIdRoute,
+  PostsIndexRoute: PostsIndexRoute,
+}
+
+const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
+
+interface UsersRouteChildren {
+  UsersUserIdRoute: typeof UsersUserIdRoute
+  UsersIndexRoute: typeof UsersIndexRoute
+}
+
+const UsersRouteChildren: UsersRouteChildren = {
+  UsersUserIdRoute: UsersUserIdRoute,
+  UsersIndexRoute: UsersIndexRoute,
+}
+
+const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren)
+
+interface ApiUsersRouteChildren {
+  ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute
+}
+
+const ApiUsersRouteChildren: ApiUsersRouteChildren = {
+  ApiUsersUserIdRoute: ApiUsersUserIdRoute,
+}
+
+const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren(
+  ApiUsersRouteChildren,
+)
+
+const rootRouteChildren: RootRouteChildren = {
+  IndexRoute: IndexRoute,
+  DeferredRoute: DeferredRoute,
+  LogoutRoute: LogoutRoute,
+  PostsRoute: PostsRouteWithChildren,
+  UsersRoute: UsersRouteWithChildren,
+  ApiUsersRoute: ApiUsersRouteWithChildren,
+  RedirectThrowItRoute: RedirectThrowItRoute,
+  RedirectIndexRoute: RedirectIndexRoute,
+  PostsPostIdDeepRoute: PostsPostIdDeepRoute,
+}
+export const routeTree = rootRouteImport
+  ._addFileChildren(rootRouteChildren)
+  ._addFileTypes<FileRouteTypes>()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+  interface Register {
+    ssr: true
+    router: Awaited<ReturnType<typeof getRouter>>
+  }
+}
diff --git a/e2e/vue-start/custom-basepath/src/router.tsx b/e2e/vue-start/custom-basepath/src/router.tsx
new file mode 100644
index 0000000000..81103bf0bd
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/router.tsx
@@ -0,0 +1,18 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
+import { NotFound } from './components/NotFound'
+import { basepath } from './utils/basepath'
+
+export function getRouter() {
+  const router = createRouter({
+    routeTree,
+    defaultPreload: 'intent',
+    defaultErrorComponent: DefaultCatchBoundary,
+    defaultNotFoundComponent: () => <NotFound />,
+    scrollRestoration: true,
+    basepath: basepath,
+  })
+
+  return router
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/__root.tsx b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
new file mode 100644
index 0000000000..d62c0e68fd
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
@@ -0,0 +1,114 @@
+import {
+  HeadContent,
+  Link,
+  Outlet,
+  Scripts,
+  createRootRoute,
+} from '@tanstack/vue-router'
+
+import { TanStackRouterDevtoolsInProd } from '@tanstack/vue-router-devtools'
+import { HydrationScript } from 'solid-js/web'
+import { NotFound } from '~/components/NotFound'
+import appCss from '~/styles/app.css?url'
+import { seo } from '~/utils/seo'
+
+export const Route = createRootRoute({
+  head: () => ({
+    meta: [
+      {
+        name: 'viewport',
+        content: 'width=device-width, initial-scale=1',
+      },
+      ...seo({
+        title:
+          'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
+        description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
+      }),
+    ],
+    links: [
+      { rel: 'stylesheet', href: appCss },
+      {
+        rel: 'apple-touch-icon',
+        sizes: '180x180',
+        href: '/apple-touch-icon.png',
+      },
+      {
+        rel: 'icon',
+        type: 'image/png',
+        sizes: '32x32',
+        href: '/favicon-32x32.png',
+      },
+      {
+        rel: 'icon',
+        type: 'image/png',
+        sizes: '16x16',
+        href: '/favicon-16x16.png',
+      },
+      { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
+      { rel: 'icon', href: '/favicon.ico' },
+    ],
+  }),
+  errorComponent: (props) => <p>{props.error.stack}</p>,
+  notFoundComponent: () => <NotFound />,
+  component: RootComponent,
+})
+
+function RootComponent() {
+  return (
+    <html>
+      <head>
+        <HydrationScript />
+      </head>
+      <body>
+        <HeadContent />
+        <div class="p-2 flex gap-2 text-lg">
+          <Link
+            to="/"
+            activeProps={{
+              class: 'font-bold',
+            }}
+            activeOptions={{ exact: true }}
+          >
+            Home
+          </Link>{' '}
+          <Link
+            to="/posts"
+            activeProps={{
+              class: 'font-bold',
+            }}
+          >
+            Posts
+          </Link>{' '}
+          <Link
+            to="/users"
+            activeProps={{
+              class: 'font-bold',
+            }}
+          >
+            Users
+          </Link>{' '}
+          <Link
+            to="/deferred"
+            activeProps={{
+              class: 'font-bold',
+            }}
+          >
+            Deferred
+          </Link>{' '}
+          <Link
+            // @ts-expect-error
+            to="/this-route-does-not-exist"
+            activeProps={{
+              class: 'font-bold',
+            }}
+          >
+            This Route Does Not Exist
+          </Link>
+        </div>
+        <Outlet />
+        <TanStackRouterDevtoolsInProd />
+        <Scripts />
+      </body>
+    </html>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/api/users.$userId.ts b/e2e/vue-start/custom-basepath/src/routes/api/users.$userId.ts
new file mode 100644
index 0000000000..5c55163c7f
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/api/users.$userId.ts
@@ -0,0 +1,34 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { json } from '@tanstack/vue-start'
+import type { User } from '~/utils/users'
+
+let queryURL = 'https://jsonplaceholder.typicode.com'
+
+if (import.meta.env.VITE_NODE_ENV === 'test') {
+  queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
+}
+
+export const Route = createFileRoute('/api/users/$userId')({
+  server: {
+    handlers: {
+      GET: async ({ params, request }) => {
+        console.info(`Fetching users by id=${params.userId}... @`, request.url)
+        try {
+          const res = await fetch(`${queryURL}/users/${params.userId}`)
+          if (!res.ok) {
+            throw new Error('Failed to fetch user')
+          }
+          const user = (await res.json()) as User
+          return json({
+            id: user.id,
+            name: user.name,
+            email: user.email,
+          })
+        } catch (e) {
+          console.error(e)
+          return json({ error: 'User not found' }, { status: 404 })
+        }
+      },
+    },
+  },
+})
diff --git a/e2e/vue-start/custom-basepath/src/routes/api/users.ts b/e2e/vue-start/custom-basepath/src/routes/api/users.ts
new file mode 100644
index 0000000000..5f2ab51270
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/api/users.ts
@@ -0,0 +1,65 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { createMiddleware, json } from '@tanstack/vue-start'
+import type { User } from '~/utils/users'
+
+const userLoggerMiddleware = createMiddleware().server(
+  async ({ next, request }) => {
+    console.info('In: /users')
+    const result = await next()
+    result.response.headers.set('x-users', 'true')
+    console.info('Out: /users')
+    return result
+  },
+)
+
+const testParentMiddleware = createMiddleware().server(
+  async ({ next, request }) => {
+    console.info('In: testParentMiddleware')
+    const result = await next()
+    result.response.headers.set('x-test-parent', 'true')
+    console.info('Out: testParentMiddleware')
+    return result
+  },
+)
+
+const testMiddleware = createMiddleware()
+  .middleware([testParentMiddleware])
+  .server(async ({ next, request }) => {
+    console.info('In: testMiddleware')
+    const result = await next()
+    result.response.headers.set('x-test', 'true')
+    // if (Math.random() > 0.5) {
+    //   throw new Response(null, {
+    //     status: 302,
+    //     headers: { Location: 'https://www.google.com' },
+    //   })
+    // }
+    console.info('Out: testMiddleware')
+    return result
+  })
+
+let queryURL = 'https://jsonplaceholder.typicode.com'
+
+if (import.meta.env.VITE_NODE_ENV === 'test') {
+  queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
+}
+
+export const Route = createFileRoute('/api/users')({
+  server: {
+    middleware: [testMiddleware, userLoggerMiddleware, testParentMiddleware],
+    handlers: {
+      GET: async ({ request }) => {
+        console.info('Fetching users... @', request.url)
+        const res = await fetch(`${queryURL}/users`)
+        if (!res.ok) {
+          throw new Error('Failed to fetch users')
+        }
+        const data = (await res.json()) as Array<User>
+        const list = data.slice(0, 10)
+        return json(
+          list.map((u) => ({ id: u.id, name: u.name, email: u.email })),
+        )
+      },
+    },
+  },
+})
diff --git a/e2e/vue-start/custom-basepath/src/routes/deferred.tsx b/e2e/vue-start/custom-basepath/src/routes/deferred.tsx
new file mode 100644
index 0000000000..b85132ea42
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/deferred.tsx
@@ -0,0 +1,65 @@
+import { Await, createFileRoute } from '@tanstack/vue-router'
+import { createServerFn } from '@tanstack/vue-start'
+import { Suspense, createSignal } from 'solid-js'
+
+const personServerFn = createServerFn({ method: 'GET' })
+  .inputValidator((data: { name: string }) => data)
+  .handler(({ data }) => {
+    return { name: data.name, randomNumber: Math.floor(Math.random() * 100) }
+  })
+
+const slowServerFn = createServerFn({ method: 'GET' })
+  .inputValidator((data: { name: string }) => data)
+  .handler(async ({ data }) => {
+    await new Promise((r) => setTimeout(r, 1000))
+    return { name: data.name, randomNumber: Math.floor(Math.random() * 100) }
+  })
+
+export const Route = createFileRoute('/deferred')({
+  loader: async () => {
+    return {
+      deferredStuff: new Promise<string>((r) =>
+        setTimeout(() => r('Hello deferred!'), 2000),
+      ),
+      deferredPerson: slowServerFn({ data: { name: 'Tanner Linsley' } }),
+      person: await personServerFn({ data: { name: 'John Doe' } }),
+    }
+  },
+  component: Deferred,
+})
+
+function Deferred() {
+  const [count, setCount] = createSignal(0)
+  // const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
+  const loaderData = Route.useLoaderData()
+
+  return (
+    <div class="p-2">
+      <div data-testid="regular-person">
+        {loaderData().person.name} - {loaderData().person.randomNumber}
+      </div>
+      <Suspense fallback={<div>Loading person...</div>}>
+        <Await
+          promise={loaderData().deferredPerson}
+          children={(data) => (
+            <div data-testid="deferred-person">
+              {data.name} - {data.randomNumber}
+            </div>
+          )}
+        />
+      </Suspense>
+      <Suspense fallback={<div>Loading stuff...</div>}>
+        <Await
+          promise={loaderData().deferredStuff}
+          children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
+        />
+      </Suspense>
+      <div>Count: {count()}</div>
+      <div>
+        <button onClick={() => setCount((count) => count + 1)}>
+          Increment
+        </button>
+      </div>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/index.tsx b/e2e/vue-start/custom-basepath/src/routes/index.tsx
new file mode 100644
index 0000000000..67b348889b
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/index.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { CustomMessage } from '~/components/CustomMessage'
+
+export const Route = createFileRoute('/')({
+  component: Home,
+})
+
+function Home() {
+  return (
+    <div class="p-2">
+      <h3>Welcome Home!!!</h3>
+      <CustomMessage message="Hello from a custom component!" />
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/logout.tsx b/e2e/vue-start/custom-basepath/src/routes/logout.tsx
new file mode 100644
index 0000000000..f5db27dfc7
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/logout.tsx
@@ -0,0 +1,32 @@
+import { createFileRoute, redirect } from '@tanstack/vue-router'
+import { createServerFn } from '@tanstack/vue-start'
+
+const logoutFn = createServerFn({
+  method: 'POST',
+}).handler(async () => {
+  // do logout stuff here
+  throw redirect({
+    to: '/',
+  })
+})
+
+export const Route = createFileRoute('/logout')({
+  component: Home,
+})
+
+function Home() {
+  return (
+    <div class="p-2">
+      <h3>Logout Page</h3>
+      <p>
+        This form tests that server function URLs correctly include the app's
+        basepath. The form action should be '/custom/basepath/_serverFn/...' not
+        just '/_serverFn/...'
+      </p>
+      <form action={logoutFn.url} method="post">
+        <input type="hidden" name="csrfToken" value="123abc" />
+        <button type="submit">Logout</button>
+      </form>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
new file mode 100644
index 0000000000..04d40949aa
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
@@ -0,0 +1,35 @@
+import { Link, createFileRoute } from '@tanstack/vue-router'
+
+import { fetchPost } from '~/utils/posts'
+import { NotFound } from '~/components/NotFound'
+import { PostErrorComponent } from '~/components/PostErrorComponent'
+
+export const Route = createFileRoute('/posts/$postId')({
+  loader: async ({ params: { postId } }) => fetchPost({ data: postId }),
+  errorComponent: PostErrorComponent,
+  component: PostComponent,
+  notFoundComponent: () => {
+    return <NotFound>Post not found</NotFound>
+  },
+})
+
+function PostComponent() {
+  const post = Route.useLoaderData()
+
+  return (
+    <div class="space-y-2" data-testid="post-view">
+      <h4 class="text-xl font-bold underline">{post().title}</h4>
+      <div class="text-sm">{post().body}</div>
+      <Link
+        to="/posts/$postId/deep"
+        params={{
+          postId: post().id,
+        }}
+        activeProps={{ class: 'text-black font-bold' }}
+        class="block py-1 text-blue-800 hover:text-blue-600"
+      >
+        Deep View
+      </Link>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.index.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.index.tsx
new file mode 100644
index 0000000000..85e7324ced
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.index.tsx
@@ -0,0 +1,8 @@
+import { createFileRoute } from '@tanstack/vue-router'
+export const Route = createFileRoute('/posts/')({
+  component: PostsIndexComponent,
+})
+
+function PostsIndexComponent() {
+  return <div data-testid="PostsIndexComponent">Select a post.</div>
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
new file mode 100644
index 0000000000..f30504a0ca
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
@@ -0,0 +1,47 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/vue-router'
+import { For } from 'solid-js'
+
+import { fetchPosts } from '~/utils/posts'
+
+export const Route = createFileRoute('/posts')({
+  head: () => ({
+    meta: [
+      {
+        title: 'Posts page',
+      },
+    ],
+  }),
+  loader: async () => fetchPosts(),
+  component: PostsComponent,
+})
+
+function PostsComponent() {
+  const posts = Route.useLoaderData()
+
+  return (
+    <div class="p-2 flex gap-2">
+      <ul class="list-disc pl-4">
+        <For each={posts()}>
+          {(post) => {
+            return (
+              <li class="whitespace-nowrap">
+                <Link
+                  to="/posts/$postId"
+                  params={{
+                    postId: post.id,
+                  }}
+                  class="block py-1 text-blue-800 hover:text-blue-600"
+                  activeProps={{ class: 'text-black font-bold' }}
+                >
+                  <div>{post.title.substring(0, 20)}</div>
+                </Link>
+              </li>
+            )
+          }}
+        </For>
+      </ul>
+      <hr />
+      <Outlet />
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx b/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
new file mode 100644
index 0000000000..f2f3f4ccb9
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
@@ -0,0 +1,24 @@
+import { Link, createFileRoute } from '@tanstack/vue-router'
+import { PostErrorComponent } from '~/components/PostErrorComponent'
+
+import { fetchPost } from '~/utils/posts'
+
+export const Route = createFileRoute('/posts_/$postId/deep')({
+  loader: async ({ params: { postId } }) => fetchPost({ data: postId }),
+  errorComponent: PostErrorComponent,
+  component: PostDeepComponent,
+})
+
+function PostDeepComponent() {
+  const post = Route.useLoaderData()
+
+  return (
+    <div class="p-2 space-y-2">
+      <Link to="/posts" class="block py-1 text-blue-800 hover:text-blue-600">
+        ← All Posts
+      </Link>
+      <h4 class="text-xl font-bold underline">{post().title}</h4>
+      <div class="text-sm">{post().body}</div>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/redirect.index.tsx b/e2e/vue-start/custom-basepath/src/routes/redirect.index.tsx
new file mode 100644
index 0000000000..33e31cae53
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/redirect.index.tsx
@@ -0,0 +1,15 @@
+import { Link, createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/redirect/')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return (
+    <div class="p-2 flex gap-2">
+      <Link to="/redirect/throw-it">
+        <div data-testid="link-to-throw-it">Throw It</div>
+      </Link>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/redirect.throw-it.tsx b/e2e/vue-start/custom-basepath/src/routes/redirect.throw-it.tsx
new file mode 100644
index 0000000000..e3d6b849c8
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/redirect.throw-it.tsx
@@ -0,0 +1,10 @@
+import { createFileRoute, redirect } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/redirect/throw-it')({
+  beforeLoad: () => {
+    throw redirect({
+      to: '/posts/$postId',
+      params: { postId: '1' },
+    })
+  },
+})
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
new file mode 100644
index 0000000000..578b0bf32f
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
@@ -0,0 +1,34 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import axios from 'redaxios'
+
+import type { User } from '~/utils/users'
+import { NotFound } from '~/components/NotFound'
+import { UserErrorComponent } from '~/components/UserErrorComponent'
+import { basepath } from '~/utils/basepath'
+
+export const Route = createFileRoute('/users/$userId')({
+  loader: async ({ params: { userId } }) => {
+    return await axios
+      .get<User>(basepath + '/api/users/' + userId)
+      .then((r) => r.data)
+      .catch(() => {
+        throw new Error('Failed to fetch user')
+      })
+  },
+  errorComponent: UserErrorComponent,
+  component: UserComponent,
+  notFoundComponent: () => {
+    return <NotFound>User not found</NotFound>
+  },
+})
+
+function UserComponent() {
+  const user = Route.useLoaderData()
+
+  return (
+    <div class="space-y-2">
+      <h4 class="text-xl font-bold underline">{user().name}</h4>
+      <div class="text-sm">{user().email}</div>
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.index.tsx b/e2e/vue-start/custom-basepath/src/routes/users.index.tsx
new file mode 100644
index 0000000000..8fc7daca95
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/users.index.tsx
@@ -0,0 +1,8 @@
+import { createFileRoute } from '@tanstack/vue-router'
+export const Route = createFileRoute('/users/')({
+  component: UsersIndexComponent,
+})
+
+function UsersIndexComponent() {
+  return <div>Select a user.</div>
+}
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.tsx b/e2e/vue-start/custom-basepath/src/routes/users.tsx
new file mode 100644
index 0000000000..f6c509c474
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/routes/users.tsx
@@ -0,0 +1,48 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/vue-router'
+import axios from 'redaxios'
+import type { User } from '~/utils/users'
+import { basepath } from '~/utils/basepath'
+
+export const Route = createFileRoute('/users')({
+  loader: async () => {
+    return await axios
+      .get<Array<User>>(basepath + '/api/users')
+      .then((r) => r.data)
+      .catch(() => {
+        throw new Error('Failed to fetch users')
+      })
+  },
+  component: UsersComponent,
+})
+
+function UsersComponent() {
+  const users = Route.useLoaderData()
+
+  return (
+    <div class="p-2 flex gap-2">
+      <ul class="list-disc pl-4">
+        {[
+          ...users(),
+          { id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
+        ].map((user) => {
+          return (
+            <li class="whitespace-nowrap">
+              <Link
+                to="/users/$userId"
+                params={{
+                  userId: String(user.id),
+                }}
+                class="block py-1 text-blue-800 hover:text-blue-600"
+                activeProps={{ class: 'text-black font-bold' }}
+              >
+                <div>{user.name}</div>
+              </Link>
+            </li>
+          )
+        })}
+      </ul>
+      <hr />
+      <Outlet />
+    </div>
+  )
+}
diff --git a/e2e/vue-start/custom-basepath/src/server.ts b/e2e/vue-start/custom-basepath/src/server.ts
new file mode 100644
index 0000000000..2dbc91706d
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/server.ts
@@ -0,0 +1,7 @@
+import handler from '@tanstack/vue-start/server-entry'
+
+export default {
+  fetch(request: Request) {
+    return handler.fetch(request)
+  },
+}
diff --git a/e2e/vue-start/custom-basepath/src/styles/app.css b/e2e/vue-start/custom-basepath/src/styles/app.css
new file mode 100644
index 0000000000..c36c737cd4
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/styles/app.css
@@ -0,0 +1,30 @@
+@import 'tailwindcss';
+
+@layer base {
+  *,
+  ::after,
+  ::before,
+  ::backdrop,
+  ::file-selector-button {
+    border-color: var(--color-gray-200, currentcolor);
+  }
+}
+
+@layer base {
+  html {
+    color-scheme: light dark;
+  }
+
+  * {
+    @apply border-gray-200 dark:border-gray-800;
+  }
+
+  html,
+  body {
+    @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
+  }
+
+  .using-mouse * {
+    outline: none !important;
+  }
+}
diff --git a/e2e/vue-start/custom-basepath/src/utils/basepath.ts b/e2e/vue-start/custom-basepath/src/utils/basepath.ts
new file mode 100644
index 0000000000..6e719f196c
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/utils/basepath.ts
@@ -0,0 +1 @@
+export const basepath = '/custom/basepath'
diff --git a/e2e/vue-start/custom-basepath/src/utils/posts.tsx b/e2e/vue-start/custom-basepath/src/utils/posts.tsx
new file mode 100644
index 0000000000..a193bbc7af
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/utils/posts.tsx
@@ -0,0 +1,42 @@
+import { notFound } from '@tanstack/vue-router'
+import { createServerFn } from '@tanstack/vue-start'
+import axios from 'redaxios'
+
+export type PostType = {
+  id: string
+  title: string
+  body: string
+}
+
+let queryURL = 'https://jsonplaceholder.typicode.com'
+
+if (import.meta.env.VITE_NODE_ENV === 'test') {
+  queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
+}
+
+export const fetchPost = createServerFn({ method: 'GET' })
+  .inputValidator((postId: string) => postId)
+  .handler(async ({ data: postId }) => {
+    console.info(`Fetching post with id ${postId}...`)
+    const post = await axios
+      .get<PostType>(`${queryURL}/posts/${postId}`)
+      .then((r) => r.data)
+      .catch((err) => {
+        console.error(err)
+        if (err.status === 404) {
+          throw notFound()
+        }
+        throw err
+      })
+
+    return post
+  })
+
+export const fetchPosts = createServerFn({ method: 'GET' }).handler(
+  async () => {
+    console.info('Fetching posts...')
+    return axios
+      .get<Array<PostType>>(`${queryURL}/posts`)
+      .then((r) => r.data.slice(0, 10))
+  },
+)
diff --git a/e2e/vue-start/custom-basepath/src/utils/seo.ts b/e2e/vue-start/custom-basepath/src/utils/seo.ts
new file mode 100644
index 0000000000..d18ad84b74
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/utils/seo.ts
@@ -0,0 +1,33 @@
+export const seo = ({
+  title,
+  description,
+  keywords,
+  image,
+}: {
+  title: string
+  description?: string
+  image?: string
+  keywords?: string
+}) => {
+  const tags = [
+    { title },
+    { name: 'description', content: description },
+    { name: 'keywords', content: keywords },
+    { name: 'twitter:title', content: title },
+    { name: 'twitter:description', content: description },
+    { name: 'twitter:creator', content: '@tannerlinsley' },
+    { name: 'twitter:site', content: '@tannerlinsley' },
+    { name: 'og:type', content: 'website' },
+    { name: 'og:title', content: title },
+    { name: 'og:description', content: description },
+    ...(image
+      ? [
+          { name: 'twitter:image', content: image },
+          { name: 'twitter:card', content: 'summary_large_image' },
+          { name: 'og:image', content: image },
+        ]
+      : []),
+  ]
+
+  return tags
+}
diff --git a/e2e/vue-start/custom-basepath/src/utils/users.tsx b/e2e/vue-start/custom-basepath/src/utils/users.tsx
new file mode 100644
index 0000000000..46be4b1580
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/utils/users.tsx
@@ -0,0 +1,9 @@
+export type User = {
+  id: number
+  name: string
+  email: string
+}
+
+const PORT = process.env.VITE_SERVER_PORT || 3000
+
+export const DEPLOY_URL = `http://localhost:${PORT}`
diff --git a/e2e/vue-start/custom-basepath/src/vite-env.d.ts b/e2e/vue-start/custom-basepath/src/vite-env.d.ts
new file mode 100644
index 0000000000..0b2af560d6
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/src/vite-env.d.ts
@@ -0,0 +1,4 @@
+declare module '*?url' {
+  const url: string
+  export default url
+}
diff --git a/e2e/vue-start/custom-basepath/tests/navigation.spec.ts b/e2e/vue-start/custom-basepath/tests/navigation.spec.ts
new file mode 100644
index 0000000000..e66f14be9e
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/tests/navigation.spec.ts
@@ -0,0 +1,78 @@
+import { expect, test } from '@playwright/test'
+
+test('Navigating to post', async ({ page }) => {
+  await page.goto('/')
+
+  await page.getByRole('link', { name: 'Posts' }).click()
+  await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
+  await page.getByRole('link', { name: 'Deep View' }).click()
+  await expect(page.getByRole('heading')).toContainText('sunt aut facere')
+})
+
+test('Navigating to user', async ({ page }) => {
+  await page.goto('/')
+
+  await page.getByRole('link', { name: 'Users' }).click()
+  await page.getByRole('link', { name: 'Leanne Graham' }).click()
+  await expect(page.getByRole('heading')).toContainText('Leanne Graham')
+})
+
+test('Navigating to a not-found route', async ({ page }) => {
+  await page.goto('/')
+
+  await page.getByRole('link', { name: 'This Route Does Not Exist' }).click()
+  await page.getByRole('link', { name: 'Start Over' }).click()
+  await expect(page.getByRole('heading')).toContainText('Welcome Home!')
+})
+
+test('Should change title on client side navigation', async ({ page }) => {
+  await page.goto('/')
+
+  await page.getByRole('link', { name: 'Posts' }).click()
+
+  await expect(page).toHaveTitle('Posts page')
+})
+
+test('Server function URLs correctly include app basepath', async ({
+  page,
+}) => {
+  await page.goto('/logout')
+
+  const form = page.locator('form')
+  const actionUrl = await form.getAttribute('action')
+
+  expect(actionUrl).toMatch(/^\/custom\/basepath\/_serverFn\//)
+})
+
+test('client-side redirect', async ({ page, baseURL }) => {
+  await page.goto('/redirect')
+  await page.getByTestId('link-to-throw-it').click()
+  await page.waitForLoadState('networkidle')
+
+  expect(await page.getByTestId('post-view').isVisible()).toBe(true)
+  expect(page.url()).toBe(`${baseURL}/posts/1`)
+})
+
+test('server-side redirect', async ({ page, baseURL }) => {
+  await page.goto('/redirect/throw-it')
+  await page.waitForLoadState('networkidle')
+
+  expect(await page.getByTestId('post-view').isVisible()).toBe(true)
+  expect(page.url()).toBe(`${baseURL}/posts/1`)
+
+  // do not follow redirects since we want to test the Location header
+  // first go to the route WITHOUT the base path, this will just add the base path
+  await page.request
+    .get('/redirect/throw-it', { maxRedirects: 0 })
+    .then((res) => {
+      const headers = new Headers(res.headers())
+      expect(headers.get('location')).toBe('/custom/basepath/redirect/throw-it')
+    })
+  // now go to the route WITH the base path, this will redirect to the final destination
+  await page.request
+    .get('/custom/basepath/redirect/throw-it', { maxRedirects: 0 })
+    .then((res) => {
+      const headers = new Headers(res.headers())
+      expect(headers.get('location')).toBe('/custom/basepath/posts/1')
+    })
+})
diff --git a/e2e/vue-start/custom-basepath/tests/setup/global.setup.ts b/e2e/vue-start/custom-basepath/tests/setup/global.setup.ts
new file mode 100644
index 0000000000..3593d10ab9
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/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-start/custom-basepath/tests/setup/global.teardown.ts b/e2e/vue-start/custom-basepath/tests/setup/global.teardown.ts
new file mode 100644
index 0000000000..62fd79911c
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/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-start/custom-basepath/tsconfig.json b/e2e/vue-start/custom-basepath/tsconfig.json
new file mode 100644
index 0000000000..6d933a5b2c
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/tsconfig.json
@@ -0,0 +1,24 @@
+{
+  "include": ["**/*.ts", "**/*.tsx", "public/script*.js"],
+  "compilerOptions": {
+    "strict": true,
+    "esModuleInterop": true,
+    "jsx": "preserve",
+    "jsxImportSource": "vue",
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "lib": ["DOM", "DOM.Iterable", "ES2022"],
+    "isolatedModules": true,
+    "resolveJsonModule": true,
+    "skipLibCheck": true,
+    "target": "ES2022",
+    "allowJs": true,
+    "forceConsistentCasingInFileNames": true,
+    "baseUrl": ".",
+    "paths": {
+      "~/*": ["./src/*"]
+    },
+    "noEmit": true,
+    "types": ["vite/client"]
+  }
+}
diff --git a/e2e/vue-start/custom-basepath/vite.config.ts b/e2e/vue-start/custom-basepath/vite.config.ts
new file mode 100644
index 0000000000..fd37f91814
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/vite.config.ts
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vite'
+import tsConfigPaths from 'vite-tsconfig-paths'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+export default defineConfig({
+	base: '/custom/basepath',
+  server: {
+    port: 3000,
+  },
+  plugins: [
+    tsConfigPaths({
+      projects: ['./tsconfig.json'],
+    }),
+    tanstackStart(),
+    vue(),
+    vueJsx(),
+  ],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9185025de5..bf7f2bd990 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1458,7 +1458,7 @@ importers:
         version: 2.6.0
       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)
+        version: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
     devDependencies:
       '@playwright/test':
         specifier: ^1.56.1
@@ -1477,7 +1477,7 @@ importers:
         version: 19.2.2(@types/react@19.2.2)
       '@vitejs/plugin-react':
         specifier: ^4.3.4
-        version: 4.7.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))
+        version: 4.7.0(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
       autoprefixer:
         specifier: ^10.4.20
         version: 10.4.20(postcss@8.5.6)
@@ -1492,7 +1492,7 @@ importers:
         version: 5.9.2
       vite-tsconfig-paths:
         specifier: ^5.1.4
-        version: 5.1.4(typescript@5.9.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))
+        version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
 
   e2e/react-start/custom-basepath:
     dependencies:
@@ -4800,6 +4800,73 @@ importers:
         specifier: ^5.1.4
         version: 5.1.4(typescript@5.9.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-start/custom-basepath:
+    dependencies:
+      '@tanstack/vue-router':
+        specifier: workspace:*
+        version: link:../../../packages/vue-router
+      '@tanstack/vue-router-devtools':
+        specifier: workspace:*
+        version: link:../../../packages/vue-router-devtools
+      '@tanstack/vue-start':
+        specifier: workspace:*
+        version: link:../../../packages/vue-start
+      express:
+        specifier: ^4.21.2
+        version: 4.21.2
+      redaxios:
+        specifier: ^0.5.1
+        version: 0.5.1
+      vue:
+        specifier: ^3.5.25
+        version: 3.5.25(typescript@5.9.2)
+    devDependencies:
+      '@playwright/test':
+        specifier: ^1.56.1
+        version: 1.56.1
+      '@tailwindcss/postcss':
+        specifier: ^4.1.15
+        version: 4.1.15
+      '@tanstack/router-e2e-utils':
+        specifier: workspace:^
+        version: link:../../e2e-utils
+      '@types/express':
+        specifier: ^5.0.3
+        version: 5.0.3
+      '@types/node':
+        specifier: 22.10.2
+        version: 22.10.2
+      '@vitejs/plugin-vue':
+        specifier: ^6.0.3
+        version: 6.0.3(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.9.2))
+      '@vitejs/plugin-vue-jsx':
+        specifier: ^5.1.2
+        version: 5.1.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))(vue@3.5.25(typescript@5.9.2))
+      cross-env:
+        specifier: ^10.0.0
+        version: 10.0.0
+      postcss:
+        specifier: ^8.5.1
+        version: 8.5.6
+      srvx:
+        specifier: ^0.9.8
+        version: 0.9.8
+      tailwindcss:
+        specifier: ^4.1.17
+        version: 4.1.17
+      tsx:
+        specifier: ^4.20.3
+        version: 4.20.3
+      typescript:
+        specifier: ^5.7.2
+        version: 5.9.2
+      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)
+      vite-tsconfig-paths:
+        specifier: ^5.1.4
+        version: 5.1.4(typescript@5.9.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-start/query-integration:
     dependencies:
       '@tanstack/vue-query':
@@ -7599,7 +7666,7 @@ importers:
         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)
       vitest:
         specifier: ^3.2.4
-        version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.56.1)(typescript@5.9.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))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+        version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
       web-vitals:
         specifier: ^5.1.0
         version: 5.1.0
@@ -10006,7 +10073,7 @@ importers:
     devDependencies:
       '@netlify/vite-plugin-tanstack-start':
         specifier: ^1.1.4
-        version: 1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(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))
+        version: 1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(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))
       '@tailwindcss/postcss':
         specifier: ^4.1.15
         version: 4.1.15
@@ -10244,7 +10311,7 @@ importers:
         version: 2.11.10(@testing-library/jest-dom@6.6.3)(solid-js@1.9.10)(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))
       vitest:
         specifier: ^3.2.4
-        version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.56.1)(typescript@5.9.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))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+        version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
       web-vitals:
         specifier: ^5.1.0
         version: 5.1.0
@@ -10735,16 +10802,16 @@ importers:
     devDependencies:
       '@vitejs/plugin-vue':
         specifier: ^5.2.3
-        version: 5.2.4(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.2))
+        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.9.2))
       '@vitejs/plugin-vue-jsx':
         specifier: ^4.1.2
-        version: 4.2.0(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.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.9.2))
       typescript:
         specifier: ^5.7.2
         version: 5.9.2
       vite:
         specifier: ^7.1.7
-        version: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+        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.9.2)
@@ -26270,13 +26337,13 @@ snapshots:
       uuid: 11.1.0
       write-file-atomic: 5.0.1
 
-  '@netlify/dev@4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)':
+  '@netlify/dev@4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)':
     dependencies:
       '@netlify/blobs': 10.1.0
       '@netlify/config': 23.2.0
       '@netlify/dev-utils': 4.3.0
       '@netlify/edge-functions-dev': 1.0.0
-      '@netlify/functions-dev': 1.0.0(encoding@0.1.13)(rollup@4.52.5)
+      '@netlify/functions-dev': 1.0.0(rollup@4.52.5)
       '@netlify/headers': 2.1.0
       '@netlify/images': 1.3.0(@netlify/blobs@10.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)
       '@netlify/redirects': 3.1.0
@@ -26344,12 +26411,12 @@ snapshots:
     dependencies:
       '@netlify/types': 2.1.0
 
-  '@netlify/functions-dev@1.0.0(encoding@0.1.13)(rollup@4.52.5)':
+  '@netlify/functions-dev@1.0.0(rollup@4.52.5)':
     dependencies:
       '@netlify/blobs': 10.1.0
       '@netlify/dev-utils': 4.3.0
       '@netlify/functions': 5.0.0
-      '@netlify/zip-it-and-ship-it': 14.1.11(encoding@0.1.13)(rollup@4.52.5)
+      '@netlify/zip-it-and-ship-it': 14.1.11(rollup@4.52.5)
       cron-parser: 4.9.0
       decache: 4.6.2
       extract-zip: 2.0.1
@@ -26439,9 +26506,9 @@ snapshots:
 
   '@netlify/types@2.1.0': {}
 
-  '@netlify/vite-plugin-tanstack-start@1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(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))':
+  '@netlify/vite-plugin-tanstack-start@1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(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))':
     dependencies:
-      '@netlify/vite-plugin': 2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(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))
+      '@netlify/vite-plugin': 2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(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))
       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)
     optionalDependencies:
       '@tanstack/solid-start': link:packages/solid-start
@@ -26469,9 +26536,9 @@ snapshots:
       - supports-color
       - uploadthing
 
-  '@netlify/vite-plugin@2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(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))':
+  '@netlify/vite-plugin@2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(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))':
     dependencies:
-      '@netlify/dev': 4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)
+      '@netlify/dev': 4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)
       '@netlify/dev-utils': 4.3.0
       dedent: 1.7.0(babel-plugin-macros@3.1.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)
@@ -26499,13 +26566,13 @@ snapshots:
       - supports-color
       - uploadthing
 
-  '@netlify/zip-it-and-ship-it@14.1.11(encoding@0.1.13)(rollup@4.52.5)':
+  '@netlify/zip-it-and-ship-it@14.1.11(rollup@4.52.5)':
     dependencies:
       '@babel/parser': 7.28.5
       '@babel/types': 7.28.4
       '@netlify/binary-info': 1.0.0
       '@netlify/serverless-functions-api': 2.7.1
-      '@vercel/nft': 0.29.4(encoding@0.1.13)(rollup@4.52.5)
+      '@vercel/nft': 0.29.4(rollup@4.52.5)
       archiver: 7.0.1
       common-path-prefix: 3.0.0
       copy-file: 11.1.0
@@ -29581,7 +29648,7 @@ snapshots:
   '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
     optional: true
 
-  '@vercel/nft@0.29.4(encoding@0.1.13)(rollup@4.52.5)':
+  '@vercel/nft@0.29.4(rollup@4.52.5)':
     dependencies:
       '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)
       '@rollup/pluginutils': 5.1.4(rollup@4.52.5)
@@ -29642,6 +29709,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@vitejs/plugin-react@4.7.0(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))':
+    dependencies:
+      '@babel/core': 7.28.5
+      '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+      '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+      '@rolldown/pluginutils': 1.0.0-beta.27
+      '@types/babel__core': 7.20.5
+      react-refresh: 0.17.0
+      vite: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+    transitivePeerDependencies:
+      - supports-color
+
   '@vitejs/plugin-react@4.7.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))':
     dependencies:
       '@babel/core': 7.28.5
@@ -29678,17 +29757,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue-jsx@4.2.0(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.2))':
-    dependencies:
-      '@babel/core': 7.28.5
-      '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
-      '@rolldown/pluginutils': 1.0.0-beta.40
-      '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5)
-      vite: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
-      vue: 3.5.25(typescript@5.9.2)
-    transitivePeerDependencies:
-      - supports-color
-
   '@vitejs/plugin-vue-jsx@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))':
     dependencies:
       '@babel/core': 7.28.5
@@ -29723,11 +29791,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue@5.2.4(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.2))':
-    dependencies:
-      vite: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
-      vue: 3.5.25(typescript@5.9.2)
-
   '@vitejs/plugin-vue@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))':
     dependencies:
       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)
@@ -36560,6 +36623,17 @@ snapshots:
       - supports-color
       - typescript
 
+  vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)):
+    dependencies:
+      debug: 4.4.3
+      globrex: 0.1.2
+      tsconfck: 3.1.4(typescript@5.9.2)
+    optionalDependencies:
+      vite: 7.1.7(@types/node@22.10.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+
   vite-tsconfig-paths@5.1.4(typescript@5.9.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)):
     dependencies:
       debug: 4.4.3
@@ -36609,7 +36683,7 @@ snapshots:
     optionalDependencies:
       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)
 
-  vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.56.1)(typescript@5.9.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))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1):
+  vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1):
     dependencies:
       '@types/chai': 5.2.2
       '@vitest/expect': 3.2.4
@@ -36638,7 +36712,7 @@ snapshots:
       '@types/node': 22.10.2
       '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.56.1)(typescript@5.9.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))(vitest@3.2.4)
       '@vitest/ui': 3.0.6(vitest@3.2.4)
-      jsdom: 27.0.0(postcss@8.5.6)
+      jsdom: 25.0.1
     transitivePeerDependencies:
       - jiti
       - less
@@ -36653,7 +36727,7 @@ snapshots:
       - tsx
       - yaml
 
-  vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1):
+  vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1):
     dependencies:
       '@types/chai': 5.2.2
       '@vitest/expect': 3.2.4
@@ -36682,7 +36756,7 @@ snapshots:
       '@types/node': 22.10.2
       '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.56.1)(typescript@5.9.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))(vitest@3.2.4)
       '@vitest/ui': 3.0.6(vitest@3.2.4)
-      jsdom: 25.0.1
+      jsdom: 27.0.0(postcss@8.5.6)
     transitivePeerDependencies:
       - jiti
       - less
diff --git a/port-tanstack-router-e2e-react-basepath-file-based-external.txt b/port-tanstack-router-e2e-react-basepath-file-based-external.txt
new file mode 100644
index 0000000000..f0c469dc4c
--- /dev/null
+++ b/port-tanstack-router-e2e-react-basepath-file-based-external.txt
@@ -0,0 +1 @@
+52266
\ No newline at end of file
diff --git a/port-tanstack-router-e2e-react-basepath-file-based.txt b/port-tanstack-router-e2e-react-basepath-file-based.txt
new file mode 100644
index 0000000000..7f6b832d29
--- /dev/null
+++ b/port-tanstack-router-e2e-react-basepath-file-based.txt
@@ -0,0 +1 @@
+52265
\ No newline at end of file

From f8c08f2b0de8bbd8b4a931cdf50e13f0c60eafa7 Mon Sep 17 00:00:00 2001
From: Birk Skyum <birk.skyum@pm.me>
Date: Wed, 17 Dec 2025 22:48:06 +0100
Subject: [PATCH 2/4] port to vue-start

---
 .../custom-basepath/src/routes/__root.tsx          | 14 +++++++-------
 .../custom-basepath/src/routes/posts.$postId.tsx   |  6 +++---
 e2e/vue-start/custom-basepath/src/routes/posts.tsx |  4 ++--
 .../src/routes/posts_.$postId.deep.tsx             |  4 ++--
 4 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/e2e/vue-start/custom-basepath/src/routes/__root.tsx b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
index d62c0e68fd..e4e1b557ed 100644
--- a/e2e/vue-start/custom-basepath/src/routes/__root.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
@@ -1,5 +1,7 @@
 import {
+	Body,
   HeadContent,
+  Html,
   Link,
   Outlet,
   Scripts,
@@ -7,7 +9,6 @@ import {
 } from '@tanstack/vue-router'
 
 import { TanStackRouterDevtoolsInProd } from '@tanstack/vue-router-devtools'
-import { HydrationScript } from 'solid-js/web'
 import { NotFound } from '~/components/NotFound'
 import appCss from '~/styles/app.css?url'
 import { seo } from '~/utils/seo'
@@ -55,12 +56,11 @@ export const Route = createRootRoute({
 
 function RootComponent() {
   return (
-    <html>
+    <Html>
       <head>
-        <HydrationScript />
-      </head>
-      <body>
         <HeadContent />
+      </head>
+      <Body>
         <div class="p-2 flex gap-2 text-lg">
           <Link
             to="/"
@@ -108,7 +108,7 @@ function RootComponent() {
         <Outlet />
         <TanStackRouterDevtoolsInProd />
         <Scripts />
-      </body>
-    </html>
+      </Body>
+    </Html>
   )
 }
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
index 04d40949aa..b4e5a8a6a2 100644
--- a/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.$postId.tsx
@@ -18,12 +18,12 @@ function PostComponent() {
 
   return (
     <div class="space-y-2" data-testid="post-view">
-      <h4 class="text-xl font-bold underline">{post().title}</h4>
-      <div class="text-sm">{post().body}</div>
+      <h4 class="text-xl font-bold underline">{post.value.title}</h4>
+      <div class="text-sm">{post.value.body}</div>
       <Link
         to="/posts/$postId/deep"
         params={{
-          postId: post().id,
+          postId: post.value.id,
         }}
         activeProps={{ class: 'text-black font-bold' }}
         class="block py-1 text-blue-800 hover:text-blue-600"
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
index f30504a0ca..7daf669c91 100644
--- a/e2e/vue-start/custom-basepath/src/routes/posts.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
@@ -11,7 +11,7 @@ export const Route = createFileRoute('/posts')({
       },
     ],
   }),
-  loader: async () => fetchPosts(),
+  loader: async () => fetchposts.value,
   component: PostsComponent,
 })
 
@@ -21,7 +21,7 @@ function PostsComponent() {
   return (
     <div class="p-2 flex gap-2">
       <ul class="list-disc pl-4">
-        <For each={posts()}>
+        <For each={posts.value}>
           {(post) => {
             return (
               <li class="whitespace-nowrap">
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx b/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
index f2f3f4ccb9..a52c6f32f7 100644
--- a/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/posts_.$postId.deep.tsx
@@ -17,8 +17,8 @@ function PostDeepComponent() {
       <Link to="/posts" class="block py-1 text-blue-800 hover:text-blue-600">
         ← All Posts
       </Link>
-      <h4 class="text-xl font-bold underline">{post().title}</h4>
-      <div class="text-sm">{post().body}</div>
+      <h4 class="text-xl font-bold underline">{post.value.title}</h4>
+      <div class="text-sm">{post.value.body}</div>
     </div>
   )
 }

From 02ecb96c32ccabb5d9b95ec46e9cd3682c4be281 Mon Sep 17 00:00:00 2001
From: Birk Skyum <birk.skyum@pm.me>
Date: Wed, 17 Dec 2025 22:53:05 +0100
Subject: [PATCH 3/4] port remaining to custom-basepath

---
 .../src/components/DefaultCatchBoundary.tsx   |  4 +-
 .../custom-basepath/src/routes/__root.tsx     |  6 +-
 .../custom-basepath/src/routes/deferred.tsx   | 88 +++++++++++--------
 .../custom-basepath/src/routes/posts.tsx      | 35 ++++----
 .../src/routes/users.$userId.tsx              |  4 +-
 .../custom-basepath/src/routes/users.tsx      | 32 ++++---
 6 files changed, 88 insertions(+), 81 deletions(-)

diff --git a/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx b/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
index d6b3c73288..b1f818dd74 100644
--- a/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
+++ b/e2e/vue-start/custom-basepath/src/components/DefaultCatchBoundary.tsx
@@ -28,7 +28,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
         >
           Try Again
         </button>
-        {isRoot() ? (
+        {isRoot.value ? (
           <Link
             to="/"
             class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
@@ -39,7 +39,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
           <Link
             to="/"
             class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
-            onClick={(e) => {
+            onClick={(e: MouseEvent) => {
               e.preventDefault()
               window.history.back()
             }}
diff --git a/e2e/vue-start/custom-basepath/src/routes/__root.tsx b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
index e4e1b557ed..8bc947b03a 100644
--- a/e2e/vue-start/custom-basepath/src/routes/__root.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/__root.tsx
@@ -1,5 +1,5 @@
 import {
-	Body,
+  Body,
   HeadContent,
   Html,
   Link,
@@ -22,8 +22,8 @@ export const Route = createRootRoute({
       },
       ...seo({
         title:
-          'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
-        description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
+          'TanStack Start | Type-Safe, Client-First, Full-Stack Vue Framework',
+        description: `TanStack Start is a type-safe, client-first, full-stack Vue framework. `,
       }),
     ],
     links: [
diff --git a/e2e/vue-start/custom-basepath/src/routes/deferred.tsx b/e2e/vue-start/custom-basepath/src/routes/deferred.tsx
index b85132ea42..7dbd50b030 100644
--- a/e2e/vue-start/custom-basepath/src/routes/deferred.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/deferred.tsx
@@ -1,6 +1,6 @@
 import { Await, createFileRoute } from '@tanstack/vue-router'
 import { createServerFn } from '@tanstack/vue-start'
-import { Suspense, createSignal } from 'solid-js'
+import { Suspense, defineComponent, ref } from 'vue'
 
 const personServerFn = createServerFn({ method: 'GET' })
   .inputValidator((data: { name: string }) => data)
@@ -15,6 +15,56 @@ const slowServerFn = createServerFn({ method: 'GET' })
     return { name: data.name, randomNumber: Math.floor(Math.random() * 100) }
   })
 
+const Deferred = defineComponent({
+  setup() {
+    const count = ref(0)
+    const loaderData = Route.useLoaderData()
+
+    return () => (
+      <div class="p-2">
+        <div data-testid="regular-person">
+          {loaderData.value.person.name} -{' '}
+          {loaderData.value.person.randomNumber}
+        </div>
+        <Suspense>
+          {{
+            default: () => (
+              <Await
+                promise={loaderData.value.deferredPerson}
+                children={(data: { name: string; randomNumber: number }) => (
+                  <div data-testid="deferred-person">
+                    {data.name} - {data.randomNumber}
+                  </div>
+                )}
+              />
+            ),
+            fallback: () => <div>Loading person...</div>,
+          }}
+        </Suspense>
+        <Suspense>
+          {{
+            default: () => (
+              <Await
+                promise={loaderData.value.deferredStuff}
+                children={(data: string) => (
+                  <h3 data-testid="deferred-stuff">{data}</h3>
+                )}
+              />
+            ),
+            fallback: () => <div>Loading stuff...</div>,
+          }}
+        </Suspense>
+        <div data-testid="count">Count: {count.value}</div>
+        <div>
+          <button data-testid="increment" onClick={() => count.value++}>
+            Increment
+          </button>
+        </div>
+      </div>
+    )
+  },
+})
+
 export const Route = createFileRoute('/deferred')({
   loader: async () => {
     return {
@@ -27,39 +77,3 @@ export const Route = createFileRoute('/deferred')({
   },
   component: Deferred,
 })
-
-function Deferred() {
-  const [count, setCount] = createSignal(0)
-  // const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
-  const loaderData = Route.useLoaderData()
-
-  return (
-    <div class="p-2">
-      <div data-testid="regular-person">
-        {loaderData().person.name} - {loaderData().person.randomNumber}
-      </div>
-      <Suspense fallback={<div>Loading person...</div>}>
-        <Await
-          promise={loaderData().deferredPerson}
-          children={(data) => (
-            <div data-testid="deferred-person">
-              {data.name} - {data.randomNumber}
-            </div>
-          )}
-        />
-      </Suspense>
-      <Suspense fallback={<div>Loading stuff...</div>}>
-        <Await
-          promise={loaderData().deferredStuff}
-          children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
-        />
-      </Suspense>
-      <div>Count: {count()}</div>
-      <div>
-        <button onClick={() => setCount((count) => count + 1)}>
-          Increment
-        </button>
-      </div>
-    </div>
-  )
-}
diff --git a/e2e/vue-start/custom-basepath/src/routes/posts.tsx b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
index 7daf669c91..1a9d58a31d 100644
--- a/e2e/vue-start/custom-basepath/src/routes/posts.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/posts.tsx
@@ -1,5 +1,4 @@
 import { Link, Outlet, createFileRoute } from '@tanstack/vue-router'
-import { For } from 'solid-js'
 
 import { fetchPosts } from '~/utils/posts'
 
@@ -11,7 +10,7 @@ export const Route = createFileRoute('/posts')({
       },
     ],
   }),
-  loader: async () => fetchposts.value,
+  loader: async () => fetchPosts(),
   component: PostsComponent,
 })
 
@@ -21,24 +20,20 @@ function PostsComponent() {
   return (
     <div class="p-2 flex gap-2">
       <ul class="list-disc pl-4">
-        <For each={posts.value}>
-          {(post) => {
-            return (
-              <li class="whitespace-nowrap">
-                <Link
-                  to="/posts/$postId"
-                  params={{
-                    postId: post.id,
-                  }}
-                  class="block py-1 text-blue-800 hover:text-blue-600"
-                  activeProps={{ class: 'text-black font-bold' }}
-                >
-                  <div>{post.title.substring(0, 20)}</div>
-                </Link>
-              </li>
-            )
-          }}
-        </For>
+        {posts.value.map((post) => (
+          <li class="whitespace-nowrap" key={post.id}>
+            <Link
+              to="/posts/$postId"
+              params={{
+                postId: post.id,
+              }}
+              class="block py-1 text-blue-800 hover:text-blue-600"
+              activeProps={{ class: 'text-black font-bold' }}
+            >
+              <div>{post.title.substring(0, 20)}</div>
+            </Link>
+          </li>
+        ))}
       </ul>
       <hr />
       <Outlet />
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
index 578b0bf32f..fb7fce5f4c 100644
--- a/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
@@ -27,8 +27,8 @@ function UserComponent() {
 
   return (
     <div class="space-y-2">
-      <h4 class="text-xl font-bold underline">{user().name}</h4>
-      <div class="text-sm">{user().email}</div>
+      <h4 class="text-xl font-bold underline">{user.value.name}</h4>
+      <div class="text-sm">{user.value.email}</div>
     </div>
   )
 }
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.tsx b/e2e/vue-start/custom-basepath/src/routes/users.tsx
index f6c509c474..1432e50451 100644
--- a/e2e/vue-start/custom-basepath/src/routes/users.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/users.tsx
@@ -22,24 +22,22 @@ function UsersComponent() {
     <div class="p-2 flex gap-2">
       <ul class="list-disc pl-4">
         {[
-          ...users(),
+          ...users.value,
           { id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
-        ].map((user) => {
-          return (
-            <li class="whitespace-nowrap">
-              <Link
-                to="/users/$userId"
-                params={{
-                  userId: String(user.id),
-                }}
-                class="block py-1 text-blue-800 hover:text-blue-600"
-                activeProps={{ class: 'text-black font-bold' }}
-              >
-                <div>{user.name}</div>
-              </Link>
-            </li>
-          )
-        })}
+        ].map((user) => (
+          <li class="whitespace-nowrap" key={user.id}>
+            <Link
+              to="/users/$userId"
+              params={{
+                userId: String(user.id),
+              }}
+              class="block py-1 text-blue-800 hover:text-blue-600"
+              activeProps={{ class: 'text-black font-bold' }}
+            >
+              <div>{user.name}</div>
+            </Link>
+          </li>
+        ))}
       </ul>
       <hr />
       <Outlet />

From 7c1fbbb21066c112795e2f36d2542e0d324e42db Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Wed, 17 Dec 2025 21:55:53 +0000
Subject: [PATCH 4/4] ci: apply automated fixes

---
 e2e/vue-start/custom-basepath/vite.config.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/e2e/vue-start/custom-basepath/vite.config.ts b/e2e/vue-start/custom-basepath/vite.config.ts
index fd37f91814..8f0fee2ce7 100644
--- a/e2e/vue-start/custom-basepath/vite.config.ts
+++ b/e2e/vue-start/custom-basepath/vite.config.ts
@@ -5,7 +5,7 @@ import vue from '@vitejs/plugin-vue'
 import vueJsx from '@vitejs/plugin-vue-jsx'
 
 export default defineConfig({
-	base: '/custom/basepath',
+  base: '/custom/basepath',
   server: {
     port: 3000,
   },
