Studio is a Next.js 15 App Router app on React 19 (apps/studio). One build serves two
products:
| Product | Served by | You get |
|---|---|---|
| Platform Console | apps/server/console/server.ts | Org, projects, team, usage, billing, platform settings |
| Project Studio | A project's own API server | The project surfaces — tables, SQL, auth, storage, logs… |
Both mount under the same basePath: '/studio', fixed in next.config.mjs — Next has
no runtime basePath, so the prefix cannot be chosen per deployment. Because the pathname no
longer tells the two products apart, the Console server stamps an mb_product=console
cookie on every non-API response it serves, and src/shared/lib/project.ts reads it. No
cookie means Project Studio, which is the safer default: the shell then skips the
platform-only calls a project API cannot answer.
next.config.mjs is deliberately .mjs, not .ts. The API server loads it at runtime
through nextApp.prepare(), and the production image runs npm prune --omit=dev, so
TypeScript is gone by then — Next reacted by shelling out to install it inside the
container on every boot. A .mjs config needs no compiler at runtime.
App shell
Route files live in apps/studio/app/; the shell components they compose live in
apps/studio/src/shell/.
| File | Role |
|---|---|
app/layout.tsx | Root document, fonts, theme |
app/providers.tsx | React Query and app-level context |
app/client-gate.tsx | Defers client-only work until after hydration |
src/shell/Layout.tsx | Chrome for platform-level screens |
src/shell/OrgLayout.tsx | Org-scoped shell (org switcher) — app/(org)/ |
src/shell/ProductLayout.tsx | Two-tier product panels (database, auth, observability) |
src/shell/RequireAuth.tsx | RequireAuth, RequirePasswordChange, RequirePlatformCapability, RequirePlatformFeature |
src/shell/ProjectGate.tsx | Resolves /p/:id and blocks the project shell until the project is known |
app/not-found.tsx / src/shell/NotFound.tsx | 404 |
Guards map onto the Console's model: RequirePlatformCapability capability="users.manage" gates Team, RequirePlatformFeature feature="billing" gates Billing (off unless CONSOLE_BILLING_ENABLED=true). See capabilities.
Platform routes
| Path | Page |
|---|---|
/login | Sign in |
/password-change-required | Forced rotation |
/ | Projects |
/new | Create project |
/team | Team (needs users.manage) |
/usage | Usage |
/logs | Platform logs |
/billing | Billing (feature-gated) |
/settings | Org settings |
/git/callback | GitHub OAuth return page |
/system | → /settings |
Project routes (/p/:id/*)
| Path | Page | Docs |
|---|---|---|
overview | Project overview | |
connect | Connection strings and keys | |
branches | Project branches, custom domains, database access and JWT keys | |
tables, tables/:model | Table editor | Table editor |
sql | SQL editor | SQL editor |
storage | Buckets and objects | Storage |
logs | Log stream | Logs & observability |
advisor | Advisor findings | |
realtime | Realtime inspector | |
api | API explorer | API tools |
edge-functions | Edge functions | |
integrations | Integrations | |
settings | Project settings |
A catch-all [...unknown] under /p/:id/ renders the 404 inside the project shell rather than dropping the reader out of it.
database/*
The database product includes schemas, tables, functions, triggers, types,
extensions, indexes, publications, roles, column-privileges, settings,
migrations, backups, webhooks, cron, and queues. The index redirects to the
database overview. See Database screens.
auth/*
Index is Users; then policies, access, providers, sessions, mfa, rate-limits,
url-configuration, protection, third-party, hooks, audit, passkeys,
organizations, and settings. The access page is the Role Access matrix; the query
simulator is available through the CLI/MCP, not as a Studio route. See
Authentication screens.
observability/*
Index overview, plus api, database, and reports.
Integrations
The integrations product contains the catalogue at integrations, plus vault and
wrappers. Database queues are documented separately from these integrations.
Products
The product switcher exposes overview, tables, sql, database, auth, storage,
realtime, edge-functions, integrations, branches, advisor, observability,
logs, api, connect, and settings. Some entries are cross-links into the project
routes above rather than additional route families.
Moved routes
Paths that moved are declared once in the MOVED map in next.config.mjs and served as
permanent (308) redirects, so they cost nothing at runtime:
| From | To |
|---|---|
/p/:id | /p/:id/overview |
/p/:id/database | /p/:id/database/functions |
/p/:id/functions, triggers, extensions, migrations, backups, webhooks, cron | the matching /p/:id/database/* |
/p/:id/policies | /p/:id/auth/policies |
/p/:id/auth/users | /p/:id/auth |
/p/:id/reports | /p/:id/observability |
/system | /settings |
Feature layout
Every feature folder follows the same shape:
features/<name>/
api/ typed calls to the admin API
components/ presentational pieces
hooks/ data loading and state
lib/ pure helpers
pages/ route entries
types.tsFeatures: api-tools, assistant, auth, database, integrations, logs, platform, projects, sql, storage, tables. Shared building blocks live in src/shared.
Mock mode
npm run studio:dev # next dev on :5173, proxying /api to localhost:5001
npm run studio:mock # the same, with NEXT_PUBLIC_MOCK=1
npm run studio:build # next buildMock mode sets NEXT_PUBLIC_MOCK=1 and installs an MSW service worker that answers from
src/mocks/handlers.ts, so you can work on screens with no backend and no database. It is
set by a small apps/studio/scripts/dev-mock.mjs wrapper rather than an inline VAR=value prefix,
which would not work on Windows.
Dev and production write to different build directories — .next-dev and .next.
Running npm run build at the repo root while a Studio dev server is up used to overwrite
the dev manifests underneath it, and every page then 500'd with "Could not find the module
… in the React Client Manifest".
In development Next also rewrites /api/:path* to http://localhost:5001 with
basePath: false, so the axios base URL of /api reaches the API server rather than
/studio/api. In production Express owns /api and never forwards it to Next.
How it talks to the backend
Every project screen calls /api/admin/*. Under the Console, those calls go to /api/projects/:id/admin/* and are proxied with the project's platform token — see Project proxy. Under a project's own /studio mount they hit that project's admin API directly with the operator's session.
Platform screens
The org, projects, team, usage and billing screens served at the Console root are documented in Platform & project screens.