From f02485b1d13dbc6bdc7ebfce839d3b2913cc6755 Mon Sep 17 00:00:00 2001 From: Alaister Young <10985857+alaister@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:39:35 +0800 Subject: [PATCH] chore(studio): support NEXT_PUBLIC_BASE_PATH and port / redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire Vite `base`, Nitro `baseURL`, and the TanStack router `basepath` off the same `NEXT_PUBLIC_BASE_PATH` env var so the prefix stays in sync (all three layers are required — there's no single knob in TanStack Start). Defaults are unchanged when the var is empty. Also replace the `/` placeholder with the redirect rules that used to live in `next.config.ts`'s `redirects()`: platform → `/org` (or `/new/new-project` when deep-linked with `?next=new-project`), self-hosted → `/project/default`. Kept as a route (not a Nitro `routeRule`) so it works in `vite dev` and can branch on the query param. --- apps/studio/router.tsx | 4 ++++ apps/studio/routes/index.tsx | 31 +++++++++++++++++++++++++------ apps/studio/vite.config.ts | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/apps/studio/router.tsx b/apps/studio/router.tsx index 5c1804b3035..8e31e6add7e 100644 --- a/apps/studio/router.tsx +++ b/apps/studio/router.tsx @@ -23,6 +23,10 @@ export function getRouter() { context, scrollRestoration: true, defaultPreload: 'intent', + // Inlined via Vite's `define` at build time; stays undefined (= app at `/`) + // unless NEXT_PUBLIC_BASE_PATH is set. Must agree with Vite `base` and + // Nitro `baseURL` — see vite.config.ts. + basepath: process.env.NEXT_PUBLIC_BASE_PATH || undefined, }) setupRouterSsrQueryIntegration({ router, queryClient: context.queryClient }) diff --git a/apps/studio/routes/index.tsx b/apps/studio/routes/index.tsx index d58928d9ed0..c23073c5fad 100644 --- a/apps/studio/routes/index.tsx +++ b/apps/studio/routes/index.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, redirect } from '@tanstack/react-router' +import { IS_PLATFORM } from '@/lib/constants' + +// `/` is never rendered — it always redirects. Mirrors the Next.js +// `redirects()` rules in next.config.ts: platform sends users to `/org` +// (or `/new/new-project` when deep-linked with `?next=new-project`), +// self-hosted sends them straight to `/project/default`. +// +// Kept as a route (rather than a Nitro `routeRule`) so the redirect works +// in both `vite dev` and prod — `routeRules` only fire under Nitro — and +// so the query-param branch has somewhere to run. export const Route = createFileRoute('/')({ - component: RouteComponent, + validateSearch: (search: Record) => ({ + next: typeof search.next === 'string' ? search.next : undefined, + }), + beforeLoad: ({ search }) => { + // `href` instead of `to` because these targets aren't in the TanStack + // routeTree yet — they're still on the Next.js pages side during the + // migration. Swap to `to` once `/org`, `/new/new-project`, and + // `/project/default` are migrated. + if (IS_PLATFORM) { + if (search.next === 'new-project') throw redirect({ href: '/new/new-project' }) + throw redirect({ href: '/org' }) + } + throw redirect({ href: '/project/default' }) + }, }) - -function RouteComponent() { - return
Hello "/"!
-} diff --git a/apps/studio/vite.config.ts b/apps/studio/vite.config.ts index 7484d0388db..08bf6f1dd11 100644 --- a/apps/studio/vite.config.ts +++ b/apps/studio/vite.config.ts @@ -88,6 +88,16 @@ export default defineConfig(({ mode }) => { .map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]) ) + // 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 + // - Nitro `baseURL` — mounts the production server at that path + // - 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. + const basePath = env.NEXT_PUBLIC_BASE_PATH || undefined + return { server: { port: 3000, @@ -95,6 +105,7 @@ export default defineConfig(({ mode }) => { resolve: { tsconfigPaths: true, }, + ...(basePath && { base: basePath }), define: publicEnvDefines, ssr: { // `lodash` is CJS; its named-export interop fails in Node ESM unless bundled. @@ -131,6 +142,11 @@ export default defineConfig(({ mode }) => { // `modules/index.js` via `package.json#exports`) out of the picture. exportConditions: ['module'], noExternals: ['tslib'], + // See the `basePath` comment above — Nitro's own mount point needs the + // same prefix as Vite/router. `routeRules` paths are matched AFTER + // baseURL is stripped, so if we add redirects later they should be + // written relative to the base. + ...(basePath && { baseURL: basePath }), }), viteReact(), ],