Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
TanStack + NeonInstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsTanStack + Neon

Project structure

The TanStack + Neon monorepo layout and where to change routes, services, schema, auth, the admin app, emails, jobs, MCP and tests.

The repository is a pnpm and Turborepo monorepo with four workspace apps and one shared package. Route loaders, actions and HTTP controllers all call the same authorization-aware services, and server rendering never fetches from its own HTTP API. The admin app compiles its own route graph and server but imports its server modules from apps/web/src/server, so there is one copy of the auth, database and authorization code.

Tree

server.ts
worker.ts
index.mjs
vercel.json
react-router.config.ts
vite.config.ts
routes.ts
root.tsx
entry.server.tsx
auth.ts
env.ts
db.ts
email.ts
api.ts
actor-transaction.ts
authorization.ts
principal.ts
session.ts
request-context.ts
support-handoff.ts
cron-auth.ts
logger.ts
server.ts
index.mjs
vercel.json
.env.example
routes.ts
root.tsx
shared-auth-client.ts
drizzle.config.ts
schema.ts
migrate.ts
seed.ts
check.ts
grant-admin.ts
env.ts
.env.example
docker-compose.yml
package.json
pnpm-workspace.yaml
turbo.json
AGENTS.md
TESTING.md
CHANGELOG.md
PathResponsibility
apps/web (@nextbase/web)React Router UI, Express server, Better Auth, /api/v1, domain services, the User MCP server and the outbox worker. Deploys as the product on port 3000
apps/admin (@nextbase/admin)The administration app: its own route graph, Express server, users list, audited impersonation and the Admin MCP server. Deploys separately on port 3001
apps/database (@nextbase/database)Drizzle schema, reviewed SQL migrations, RLS policies, role bootstrap, seed, db:check and the admin grant script
apps/email (@nextbase/email)React Email templates (VerificationEmail, TeamInvitationEmail) with a preview server. apps/web doesn't depend on it yet; the auth emails are inline HTML
packages/typescript-configShared base.json and react.json TypeScript configs
docsmcp-connections.md (OAuth and MCP protocol details) and admin-app.md (origins, impersonation and admin deployment)
scripts/agentssync.mjs generates every AI tool config from AGENTS.md, .agents/config.json and .agents/skills/
.agentsHand-edited source for the generated agent files: MCP servers for coding tools and the new-page, db-migration and mcp-tool skills

Where to make changes

Routes and pages

Routes are declared in apps/web/app/routes.ts with React Router's route(), layout() and index() helpers, and each file lives in apps/web/app/routes/. Public pages are /, /login, /sign-up, /forgot-password, /reset-password and /auth/impersonation. routes/app-layout.tsx wraps /app, /app/workspaces, /app/projects and /app/settings and calls requireSession. Add a route entry, create the file, then run pnpm typecheck to regenerate the +types route types. The new-page skill in .agents/skills/ describes the same steps for an AI coding tool.

Admin pages live in apps/admin/app/routes.ts and apps/admin/app/routes/: / redirects to /app-admin, /login is the admin sign-in, admin-layout.tsx wraps /app-admin and /app-admin/users, and the remaining sections share admin-section.tsx as a placeholder. Every protected loader and action calls requireAppAdmin itself; the layout check alone is not enough.

Shared UI lives in apps/web/app/components/ (app-shell.tsx, ui.tsx, logo.tsx, theme-toggle.tsx). The admin app has its own copies under apps/admin/app/components/ and apps/admin/app/shared/, and re-exports the Better Auth client from web through shared-auth-client.ts. Client-only helpers (auth-client.ts, auth-return.ts) are in apps/web/app/lib/.

Path aliases in web: ~/* is app/*, @server/* is src/server/*, @contracts/* is src/contracts/*. In the admin app @server/* resolves to ../web/src/server/*.

Server and data access

Loaders and actions get requestId and getPrincipal() from apps/web/src/server/request-context.ts and call services in apps/web/src/server/services/ (workspaces.ts ships; add yours next to it). The same services back the HTTP API in apps/web/src/server/api.ts, mounted at /api/v1 with GET /bootstrap, GET /workspaces and POST /workspaces. Mutating API requests must be JSON and same-origin; failures are application/problem+json with a requestId. Input shapes are zod schemas in apps/web/src/contracts/.

Put new business rules in a service, wrap protected work in withActorTransaction from apps/web/src/server/actor-transaction.ts, and write an audit_events row for security-relevant mutations. Don't query app.* tables from a route file or outside an actor transaction.

Schema and migrations

The Drizzle schema is apps/database/src/schema.ts: Better Auth tables in public (user, session, account, verification, rate_limit, mcp_records) and domain tables in the app schema. Migrations are plain SQL files in apps/database/migrations/, applied in filename order by apps/database/src/migrate.ts, which stores a SHA-256 checksum per file in public.nextbase_migrations. The flow is:

  1. Edit schema.ts.
  2. pnpm --filter @nextbase/database exec drizzle-kit generate --name <change> writes migrations/NNNN_<change>.sql plus the meta/ snapshot and journal.
  3. Append grants, enable and force row level security, and policies to that SQL file, following 0001_security.sql and 0003_user_admin_mcp.sql. New app.* tables need RLS and column-scoped grant update to app_runtime.
  4. pnpm db:migrate && pnpm db:check, update src/seed.ts if needed, add RLS integration tests. Add the table to the list in src/check.ts so db:check keeps verifying it.

Never edit an applied migration; add a new one. Local role bootstrap and PgBouncer config live in apps/database/docker/ (postgres-init.sql, pgbouncer.ini, userlist.txt).

Authentication

Everything Better Auth is in apps/web/src/server/auth.ts, with helpers next to it: principal.ts, session.ts, request-context.ts, authorization.ts (requireSession, requireAppAdmin) and support-handoff.ts for the impersonation handoff records. Environment validation is apps/web/src/server/env.ts. See authentication.

Emails

Templates are React components in apps/email/src/templates/ (verification.tsx, team-invitation.tsx, layout.tsx), exported from apps/email/src/index.ts. Preview them with pnpm --filter @nextbase/email dev (port 3001, which clashes with the admin app when both run). Sending goes through enqueueEmail in apps/web/src/server/email.ts, which writes an email.send outbox job; sendEmailNow is the nodemailer transport used by the worker. To use the templates from web, add "@nextbase/email": "workspace:*" to apps/web/package.json and render them before calling enqueueEmail.

Background jobs and the worker

apps/web/worker.ts claims rows from app.outbox_jobs with two-minute leases renewed by a heartbeat, retries with exponential backoff and marks jobs dead after 10 attempts. The only job type today is email.send. Add a new type by inserting a job with an idempotency key from a service and handling it in executeJob. runOutboxBatch is exported for the /internal/jobs/run cron and for request-time dispatch with waitUntil on Vercel.

MCP

apps/web/src/mcp/ holds core.ts (protocol and tools), adapter.ts (this kit's session, workspace and project access), pg-store.ts (storage) and express.ts (the Express bridge). apps/web/server.ts mounts the user audience and apps/admin/server.ts mounts the admin audience. See agent connections for how to add a tool.

Tests

KindWhereCommand
Unit and component*.test.ts and *.test.tsx next to the code (web uses jsdom and Testing Library, setup in apps/web/src/test/setup.ts)pnpm test
Database and RLSapps/database/src/rls.integration.test.ts, apps/web/src/server/services/workspaces.integration.test.ts, apps/web/src/mcp/pg-store.integration.test.tspnpm test:integration (needs pnpm infra:start and pnpm db:migrate)
Admin impersonationapps/admin/app/routes/admin-users.integration.test.tspnpm --filter @nextbase/admin test:integration
Everything before a pushpnpm verify (format check, lint, typecheck, unit tests, build)

Single file: pnpm --filter @nextbase/web exec vitest run <path>. TESTING.md asks for a meaningful assertion for new behaviour and both the allowed and denied side of every authorization branch. CI (.github/workflows/test.yml) starts postgres and pgbouncer, runs migrations, seed and db:check, then typecheck, unit tests, integration tests and the build on every pull request, and agent-config.yml fails when the generated agent files are stale (pnpm agents:check).

Agent connections

User MCP and Admin MCP in the TanStack + Neon starter: settings pages, endpoints on the web and admin origins, tools, OAuth behaviour and where the code lives.

Deployment

Provision Neon roles, apply migrations, and deploy the TanStack + Neon starter's web and admin apps as two Vercel projects.

On this page

TreeWhere to make changesRoutes and pagesServer and data accessSchema and migrationsAuthenticationEmailsBackground jobs and the workerMCPTests