fix(studio): make dev:studio-local work with the tanstack dev server (#48090)

With `STUDIO_FRAMEWORK=tanstack`, `pnpm dev:studio-local` ran Studio in
platform mode against the management API instead of the local CLI stack
(`/` redirected to `/org` instead of `/project/default`). Vite selects
env files by *mode* while the Next dev server selects them via
`NODE_ENV=test`, so the tanstack dev server never loaded `.env.test` and
the developer's `.env.local` (`NEXT_PUBLIC_IS_PLATFORM="true"`) won. The
shell `NODE_ENV=test` also gets inlined into the dev client bundle by
Vite, flipping `API_URL` to the vitest-only MSW host.

`vite dev --mode test` isn't a viable fix: TanStack Start's dev-server
plugin treats mode `test` as "running under vitest" and skips installing
its SSR middleware, so every route 404s. Instead, dev keeps mode
`development` and overlays the env cascade named by `MODE` on top.

**Changed:**
- `dev:studio-local` now also sets `MODE=test` (the same knob
`build:tanstack` / `e2e:setup:selfhosted` already use)
- `vite.config.ts` dev server: loads the `MODE`-named env cascade for
the `NEXT_PUBLIC_*` client defines and seeds it into `process.env` for
SSR, without clobbering shell-provided values (matching `serve.js`
semantics, and safe against TanStack's own load-env plugin since
`loadEnv` gives existing `process.env` priority)
- `vite.config.ts` dev server: remaps a shell `NODE_ENV=test` to
`development` so it can't be baked into the client bundle (mirrors `next
dev` behavior)

Platform-mode `pnpm dev:studio` sets no `MODE`, so the overlay is a
no-op there. Build (`--mode test`), `serve.js`, and vitest (separate
`vitest.config`) paths are unchanged.

## To test

- `supabase` CLI installed, then: `STUDIO_FRAMEWORK=tanstack pnpm
dev:studio-local`
- Visit http://localhost:8082 — it should redirect to `/project/default`
(not `/org`) and the Default Project page should load with data from the
local stack (network requests go to `localhost:8082/api/platform/...`,
no `api.supabase.(com|green)` calls)
- `pnpm dev:studio` (platform mode, tanstack) still behaves as before —
redirects to `/org`
- Next path regression check: plain `pnpm dev:studio-local` (no
`STUDIO_FRAMEWORK`) still works

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved local Studio development and test-mode environment handling.
* Prevented test settings from being incorrectly embedded in the client
application.
* Ensured environment values are loaded consistently across development
and test scenarios.

* **Chores**
* Updated the local Studio development command to explicitly use test
mode.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-07-20 16:52:51 +08:00
1 parent 13659ecc50
commit bf0e84d55a
2 files changed
+35 -2

No files matched your search

+34 -1
View File
@@ -538,11 +538,44 @@ export default defineConfig(({ command, mode }) => {
if (command === 'build') { if (command === 'build') {
// Next's types declare NODE_ENV as read-only, so cast to assign it. // Next's types declare NODE_ENV as read-only, so cast to assign it.
;(process.env as Record<string, string>).NODE_ENV = 'production' ;(process.env as Record<string, string>).NODE_ENV = 'production'
} else if (process.env.NODE_ENV === 'test') {
// `pnpm dev:studio-local` runs with a shell NODE_ENV=test (the Next
// path needs it to load `.env.test`), and Vite's
// define plugin inlines `process.env.NODE_ENV || mode` into the client —
// which would bake 'test' in and trip the vitest-only API_URL path.
// `next dev` always runs the bundle at 'development' regardless of the
// shell NODE_ENV; mirror that. Env-file selection is unaffected — the
// vite dev path selects `.env.test` via MODE=test (see envMode below),
// not NODE_ENV.
;(process.env as Record<string, string>).NODE_ENV = 'development'
} }
// `pnpm dev:studio-local` needs the `.env.test` cascade (self-hosted mode
// plus the supabase-cli keys that generateLocalEnv.js writes) — the Next
// path selects it via NODE_ENV=test, and the tanstack build via
// `--mode test` (e2e:setup:selfhosted). But `vite dev --mode test` is not
// an option: TanStack Start's dev-server plugin treats mode 'test' as
// "running under vitest" and skips installing its SSR middleware entirely,
// so every route 404s (see the `isTest` guard in devServerPlugin,
// @tanstack/start-plugin-core). So dev keeps mode 'development' and
// emulates the env cascade of the mode named by MODE instead: load it for
// the NEXT_PUBLIC_* defines below, and seed process.env for the SSR
// runtime. The seeding must not clobber shell-provided values (matching
// serve.js), and survives TanStack's own load-env plugin: that plugin
// Object.assigns loadEnv(mode) at configResolved — after this runs — and
// loadEnv gives existing process.env values priority over env-file values.
const envMode = command === 'serve' && process.env.MODE ? process.env.MODE : mode
// Inline NEXT_PUBLIC_* env vars at build time so `process.env.NEXT_PUBLIC_*` // Inline NEXT_PUBLIC_* env vars at build time so `process.env.NEXT_PUBLIC_*`
// works in the browser bundle (mirrors Next.js behaviour). // works in the browser bundle (mirrors Next.js behaviour).
const env = loadEnv(mode, rootDir, '') const env = loadEnv(envMode, rootDir, '')
if (envMode !== mode) {
const processEnv = process.env as Record<string, string | undefined>
for (const [key, value] of Object.entries(env)) {
processEnv[key] ??= value
}
}
const publicEnvDefines = Object.fromEntries( const publicEnvDefines = Object.fromEntries(
Object.entries(env) Object.entries(env)
.filter(([key]) => key.startsWith('NEXT_PUBLIC_')) .filter(([key]) => key.startsWith('NEXT_PUBLIC_'))
+1 -1
View File
@@ -15,7 +15,7 @@
"clean": "turbo run clean --parallel && rimraf -G node_modules/{*,.bin,.modules.yaml} .turbo/cache", "clean": "turbo run clean --parallel && rimraf -G node_modules/{*,.bin,.modules.yaml} .turbo/cache",
"dev": "turbo run dev --parallel", "dev": "turbo run dev --parallel",
"dev:studio": "turbo run dev --filter=studio --parallel", "dev:studio": "turbo run dev --filter=studio --parallel",
"dev:studio-local": "pnpm setup:cli && NODE_ENV=test pnpm --prefix ./apps/studio dev", "dev:studio-local": "pnpm setup:cli && NODE_ENV=test MODE=test pnpm --prefix ./apps/studio dev",
"dev:docs": "turbo run dev --filter=docs --parallel", "dev:docs": "turbo run dev --filter=docs --parallel",
"dev:www": "turbo run dev --filter=www --parallel", "dev:www": "turbo run dev --filter=www --parallel",
"dev:design-system": "turbo run dev --filter=design-system --parallel", "dev:design-system": "turbo run dev --filter=design-system --parallel",