Files
supabase/apps/studio/turbo.jsonc
T
Alaister YoungandAlaister Young 3bac7165bd chore(studio): move the TanStack Start deploy onto Nitro (#50030)
Moves the Studio TanStack Start build off the hand-rolled Vercel setup
(an `api/server.js` function shim, rewrites in `vercel.ts`, a custom
`?dpl=` skew-protection Vite plugin, and `scripts/serve.js` for
self-hosted) and onto Nitro, which TanStack Start documents as its
deployment path. Documents are served from the static SPA shell on the
CDN; only `/api/*` and `/_serverFn/*` invoke the function.

**Removed:**
- `api/server.js`, `scripts/serve.js`, `scripts/smoke-server.mjs`
- The `skewProtectionDpl` Vite plugin, `renderBuiltUrl`, and the
`vite:preloadError` reload backstop in `router.tsx` (TanStack Router
already reloads once on a failed lazy import)
- Rewrites, `functions`, `outputDirectory`, and `cleanUrls` from
`vercel.ts` (redirects and headers stay)
- `magic-string` and `@jridgewell/remapping` devDependencies, the
`preview` script

**Added:**
- `nitro` plugin in `vite.config.ts`. Preset is auto-detected:
`.vercel/output` on Vercel, a self-contained node server in `.output`
everywhere else. `vercel.immutableStaticFiles` puts hashed chunks under
`/_vercel/immutable/` so tabs opened before a redeploy keep loading
their chunks; `functions.maxDuration: 300` carries over the old function
timeout
- `scripts/vercel-spa-routes.ts`: Nitro module that rewrites the
generated Build Output routes (documents -> `_shell.html`, allow-list ->
`__server`, missing chunk -> 404, base-path prefixes), with a unit test
- `server.ts`: TanStack Start server entry that initializes Sentry
before the route tree loads and wraps the handler with
`wrapFetchWithSentry`

**Changed:**
- `start:tanstack` runs `.output/server/index.mjs` directly with Node's
`--env-file-if-exists` for the `.env` cascade. Node doesn't expand
`$VAR` references, so `scripts/generateLocalEnv.js` now writes literal
values into `.env.test`
- Dockerfile's TanStack stage copies `.output` instead of running `pnpm
deploy`; the `server.js` shim loads `.env` and imports the Nitro server
- `NEXT_PUBLIC_BASE_PATH` (the platform's `/dashboard`) only sets the
router basepath; Vite's `base` stays at the root so chunks can use the
immutable store. The routes module emits prefixed rules for
`/dashboard/api/*` and `/dashboard/_serverFn/*` and rewrites `public/`
files requested under the prefix back to the root
- Self-hosted security headers come from a Nitro `routeRules` entry; on
Vercel they stay in `vercel.ts`
- `tslib` is inlined for the build only: Nitro's dev runner has no
interop for its CJS wrapper
- Monaco's worker chunks follow the client assets dir so they land in
the immutable store too

Verified on the `studio-staging` preview (`STUDIO_FRAMEWORK=tanstack` is
scoped to this branch there): documents come back as the static shell,
`/dashboard/api/*` hits the function, `public/` files resolve under the
prefix, a missing immutable chunk 404s. Across two deployments of this
branch, the older deployment's chunks still load from the immutable
store and requests carrying its `__vdpl` cookie are answered by that
deployment. Self-hosted path covered by the TanStack E2E job and the
Docker build job.

## To test

- On the `studio-staging` preview: `/dashboard/project/<ref>` should
show `content-disposition: inline; filename="_shell.html"` and a
single-region `x-vercel-id`; `/dashboard/api/get-utc-time` a two-region
id
- Sign in and click through a few pages, including one that opens Monaco
(SQL editor) so the worker chunks load
- After the next deploy, a tab left open on the previous one should
still navigate (lazy chunks) and call the API without errors
- Self-hosted: `STUDIO_FRAMEWORK=tanstack pnpm --filter studio build &&
pnpm --filter studio start`, then check `/api/platform/profile` and that
responses carry the security headers


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

* **New Features**
* Production TanStack deployments now run on Nitro’s self-contained
server output.
* Vercel routing serves static pages first while directing API and
server-function requests appropriately.
* Server-function requests can include deployment identification for
consistent handling.
* Local environment generation now writes resolved configuration values.

* **Bug Fixes**
  * Improved handling of missing static assets and SPA fallback routing.
* Server-side error monitoring now captures request errors in the new
runtime.

* **Refactor**
* Replaced the legacy production server and smoke-test workflow with
Nitro-based startup.
  * Removed automatic reload handling for stale client assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-09-15 21:46:45 +10:00

151 lines
5.3 KiB
JSON

{
"$schema": "./../../node_modules/turbo/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": [
"ANALYZE",
"CI",
"NEXT_PUBLIC_SUPPORT_API_URL",
"NEXT_PUBLIC_CONTENT_API_URL",
"NEXT_PUBLIC_BASE_PATH",
"NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
"NEXT_PUBLIC_SUPPORT_ANON_KEY",
"NEXT_PUBLIC_ENVIRONMENT",
"NEXT_PUBLIC_IS_PLATFORM",
"NEXT_PUBLIC_SITE_URL",
"NEXT_PUBLIC_API_URL",
"NEXT_PUBLIC_DOCS_URL",
"NEXT_PUBLIC_CONFIGCAT_SDK_KEY",
"NEXT_PUBLIC_CONFIGCAT_PROXY_URL",
"NEXT_PUBLIC_HCAPTCHA_SITE_KEY",
"NEXT_PUBLIC_SUPABASE_URL",
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
"NEXT_PUBLIC_NODE_ENV",
"NEXT_PUBLIC_GOTRUE_URL",
"NEXT_PUBLIC_VERCEL_BRANCH_URL",
"NEXT_PUBLIC_GOOGLE_MAPS_KEY",
"NEXT_RUNTIME",
"NIMBUS_PROD_PROJECTS_URL",
"NIMBUS_PROD_PROJECTS_URL_WS",
"NODE_ENV",
"SUPABASE_URL",
"VERCEL",
"VERCEL_ENV",
// Project-level Vercel settings Nitro's vercel preset reads at build
// time; they change the emitted routes and asset paths.
"VERCEL_SKEW_PROTECTION_ENABLED",
"VERCEL_IMMUTABLE_STATIC_FILES_ENABLED",
"VERCEL_HASH_SALT",
"MAINTENANCE_MODE",
// These envs are used in the packages
"NEXT_PUBLIC_STORAGE_KEY",
"NEXT_PUBLIC_AUTH_DEBUG_KEY",
"NEXT_PUBLIC_AUTH_PERSISTED_KEY",
"NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY",
"NEXT_PUBLIC_AUTH_DETECT_SESSION_IN_URL",
"NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID",
"NEXT_PUBLIC_VERCEL_ENV",
"NEXT_PUBLIC_USERCENTRICS_RULESET_ID",
"NEXT_PUBLIC_MCP_URL",
"NEXT_PUBLIC_IS_NIMBUS",
"NEXT_PUBLIC_ONGOING_INCIDENT",
// These envs are technically passthrough env vars because they're only used on the server side of Nextjs
"PLATFORM_PG_META_URL",
"STUDIO_PG_META_URL",
"PG_META_CRYPTO_KEY",
"PGRST_DB_SCHEMAS",
"PGRST_DB_MAX_ROWS",
"PGRST_DB_EXTRA_SEARCH_PATH",
"POSTGRES_PASSWORD",
"POSTGRES_HOST",
"POSTGRES_USER_READ_WRITE",
"POSTGRES_USER_READ_ONLY",
"POSTGRES_DB",
"POSTGRES_PORT",
"READ_ONLY_URL",
"READ_ONLY_API_KEY",
"SUPABASE_SERVICE_KEY",
"SUPABASE_ANON_KEY",
"SUPABASE_PUBLISHABLE_KEY",
"SUPABASE_SECRET_KEY",
"SUPABASE_PUBLIC_URL",
"DEFAULT_PROJECT_NAME",
"DEFAULT_ORGANIZATION_NAME",
"OPENAI_API_KEY",
"BRAINTRUST_API_KEY",
"BRAINTRUST_PROJECT_ID",
"AUTH_JWT_SECRET",
"LOGFLARE_API_KEY",
"LOGFLARE_PUBLIC_ACCESS_TOKEN",
"LOGFLARE_PRIVATE_ACCESS_TOKEN",
"LOGFLARE_URL",
"SENTRY_ORG",
"SENTRY_PROJECT",
"SENTRY_AUTH_TOKEN",
"SKIP_ASSET_UPLOAD",
"NEXT_PUBLIC_SENTRY_DSN",
"AWS_BEDROCK_PROFILE",
"AWS_BEDROCK_ROLE_ARN",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"FORCE_ASSET_CDN",
"ASSET_CDN_S3_ENDPOINT",
"SITE_NAME",
"VERCEL_URL",
"IS_BRAINTRUST_PUSH",
"GITHUB_HEAD_REF",
"GITHUB_REF_NAME",
"GITHUB_PR_NUMBER",
"IS_THROTTLED",
"AI_PRO_MODEL",
"AI_NORMAL_MODEL",
"SUPPORT_SUPABASE_SECRET_KEY",
"STATUSPAGE_API_KEY",
"STATUSPAGE_PAGE_ID",
"INCIDENT_IO_API_KEY",
"LIVE_SUPABASE_SECRET_KEY",
// Selects the build mode for the studio's `build`/`start` scripts
// (e.g. e2e sets `MODE=test`). Listed so turbo invalidates the
// cache when it changes — without this, switching between test and
// production builds reuses a stale cached output.
"MODE",
// Read by the Nitro server behind `pnpm start`. Declared so the turbo
// env lint rule passes even though turbo doesn't drive `start`.
"PORT",
// Gates the TanStack vs Next path in vercel.ts and
// scripts/dispatch.js (the dev/build/start dispatcher).
"STUDIO_FRAMEWORK",
// Vite's built-in `import.meta.env.SSR` flag (used in ConnectStepsSection
// to gate Vite-only `import.meta.glob`). Not a real process env var
// but turbo's `no-undeclared-env-vars` lint flags any `env.SSR` access.
"SSR",
],
"passThroughEnv": [
// Vite bakes this into TanStack's server-function request header. Passed
// through rather than hashed so Next's same-commit redeploys keep
// hitting the turbo cache as before; a TanStack cache hit reuses the
// earlier deployment's id, which is the same code and falls back to
// latest if that deployment is gone.
"VERCEL_DEPLOYMENT_ID",
"CURRENT_CLI_VERSION",
"VERCEL_GIT_COMMIT_REF",
"VERCEL_GIT_COMMIT_SHA",
"SNIPPETS_MANAGEMENT_FOLDER",
"EDGE_FUNCTIONS_MANAGEMENT_FOLDER",
"S3_PROTOCOL_ACCESS_KEY_ID",
"S3_PROTOCOL_ACCESS_KEY_SECRET",
],
"outputs": [
".next/**",
"!.next/cache/**",
"!.next/dev/**/*",
// TanStack Start via Nitro: node server and Vercel Build Output.
".output/**",
".vercel/output/**",
],
},
},
}