fix(studio): pass router.basepath explicitly to tanstackStart()

The TanStack Start vite plugin's \`deriveRouterBasepath\` (planning.js:14
in @tanstack/start-plugin-core) only short-circuits when an explicit
\`router.basepath\` is configured. When omitted (our previous setup), it
derives the basepath from Vite's \`publicBase\` and strips both leading
and trailing slashes via \`opts.publicBase.replace(/^\\/|\\/$/g, "")\`.

That stripped value (\`/dashboard\` → \`dashboard\`) then propagates into
the runtime router and shows up in \`useRouter().basePath\`, breaking
\`\${BASE_PATH}/img/...\` style templates by producing relative URLs.

Passing \`router: { basepath }\` explicitly when NEXT_PUBLIC_BASE_PATH is
set keeps the value as-is. Mirrors what Next does with a single
\`basePath\` knob in next.config.ts.

The shim's \`toNextBasePath()\` stays — it still handles TanStack's
internal \`'/'\` default for the unset case (when no basepath is
configured at all) and trailing-slash edge cases that the start plugin
won't help with.
This commit is contained in:
Alaister Young committed 2026-05-08 17:41:31 +08:00
1 parent 81ddb37a41
commit 1cc0eff44a
1 file changed
+22 -4
+22 -4
View File
@@ -103,10 +103,25 @@ export default defineConfig(({ mode }) => {
// Mirror Next's `basePath` via NEXT_PUBLIC_BASE_PATH. Unlike Next, TanStack
// Start has no single knob — the prefix has to be declared in three places
// (see BASE_PATH_REDIRECT_GUIDE.md):
// - Vite `base` — bakes the prefix into asset URLs in the built bundle
// - Router `basepath` — makes client-side navigation match the prefix
// The router layer is set in router.tsx off the same env var (inlined via
// `define` above). Leaving the var empty keeps the app at `/` as today.
// - Vite `base` — bakes the prefix into asset URLs in the
// built bundle.
// - tanstackStart router.basepath — must be passed explicitly. If
// omitted, the plugin's internal
// `deriveRouterBasepath` derives a value
// from `publicBase` and strips both
// leading and trailing slashes
// (`/dashboard` → `dashboard`), which then
// surfaces in `useRouter().basePath`
// consumers as relative URLs (e.g.
// `${BASE_PATH}/img/...` becomes
// `dashboard/img/...` and the browser
// resolves it against the current path).
// See planning.js:14 in
// @tanstack/start-plugin-core.
// - createRouter({ basepath }) — runtime navigation prefix; configured
// in router.tsx off the same env var
// (inlined via `define` above).
// Leaving the var empty keeps the app at `/` as today.
const basePath = env.NEXT_PUBLIC_BASE_PATH || undefined
return {
@@ -196,6 +211,9 @@ export default defineConfig(({ mode }) => {
spa: {
enabled: true,
},
// Set `configuredBasepath` so `deriveRouterBasepath` short-circuits
// its slash-stripping branch. See the basePath comment above.
...(basePath && { router: { basepath: basePath } }),
}),
viteReact(),
],