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

Deployment

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

The live demo runs two Vercel projects, one rooted at apps/web and one at apps/admin, against a single Neon database. This page covers what is specific to this kit; the shared Vercel deployment guide has the platform-generic steps.

What the repository ships for deployment

apps/web/vercel.json, apps/web/index.mjs, apps/admin/vercel.json and apps/admin/index.mjs are the only deployment files. There is no Dockerfile or production Compose file. The README's "Deployment" section describes a generic three-process layout (migrate, worker, web) for hosts that run long-lived Node processes; the commands in it are the package scripts listed at the end of this page.

1. Create the Neon database and roles

Create a Neon project and a database named nextbase (the local scripts assume that name). Then create the same roles the local apps/database/docker/postgres-init.sql creates, with your own passwords:

  • migration_owner: login, owns the database and the public schema. Used only for migrations and the admin scripts.
  • auth_runtime, app_runtime, worker_runtime: login, NOSUPERUSER, NOBYPASSRLS, NOINHERIT. They must not inherit neon_superuser.
  • function_owner: NOLOGIN and BYPASSRLS; it only owns the reviewed SECURITY DEFINER helpers. Grant it to migration_owner with SET TRUE.

Grant CONNECT on the database and USAGE on public to the three runtime roles. Neon's SQL editor or psql as the project owner can run the same statements as the init file, with the passwords replaced.

Neon gives you two endpoints. Use the direct endpoint for DATABASE_DIRECT_URL and the pooled endpoint for DATABASE_URL, AUTH_DATABASE_URL and WORKER_DATABASE_URL. Both use port 5432 on Neon (locally the equivalents are 35432 and 36432). Append ?sslmode=verify-full to all four.

The demo uses a Singapore Neon project; pick the region closest to your Vercel region.

2. Apply migrations and check the roles

From your machine, with the production direct connection:

DATABASE_DIRECT_URL='postgresql://migration_owner:...@<direct-host>:5432/nextbase?sslmode=verify-full' pnpm db:migrate
DATABASE_DIRECT_URL='postgresql://migration_owner:...@<direct-host>:5432/nextbase?sslmode=verify-full' pnpm db:check

db:migrate is forward-only and checksum-protected. db:check fails if a runtime role has BYPASSRLS or superuser or is a member of neon_superuser or function_owner, if a tenant table lost forced RLS, or if an app function is executable by PUBLIC. Run both before the first deploy and after every release that adds a migration. Migrations never run during web or worker startup. The MCP migration (0003_user_admin_mcp.sql) is part of the baseline set, so a fresh database gets it on the first run.

Don't run pnpm db:seed against production unless you want the demo workspace. To make yourself an admin, run pnpm admin:grant -- you@example.com with the same DATABASE_DIRECT_URL after you have signed up on the live site.

3. Production environment variables

Set every value from .env.example in each Vercel project's environment settings. The names are listed on environment variables. The ones that differ between the two projects or most often go wrong:

VariableWeb projectAdmin project
APP_BASE_URLThe product origin, for example https://app.example.com. Better Auth base URL and User MCP issuerThe admin origin, for example https://admin.example.com. Admin MCP issuer
TRUSTED_ORIGINSThe product origin, plus preview origins if you use themThe admin origin
WEB_APP_URLThe product originThe product origin (where impersonation handoffs land)
ADMIN_APP_URLThe admin origin (where support sessions return)The admin origin
BETTER_AUTH_SECRETAt least 32 random characters. Changing it signs everyone outThe same value, or its own; cookies are host-only either way
DATABASE_DIRECT_URL, DATABASE_URL, AUTH_DATABASE_URL, WORKER_DATABASE_URLThe four Neon connection strings from step 1The same four
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, EMAIL_FROMYour provider. For Resend use smtp.resend.com, port 587, and your API key as the password. EMAIL_FROM must be a verified senderThe same; the admin app also enqueues auth emails
CRON_SECRETAt least 32 characters. Vercel sends it to /internal/jobs/runNot used; the admin app has no cron route
NODE_ENVproductionproduction

Configure OAuth provider callbacks under the web APP_BASE_URL. /api/v1/webhooks/stripe exists but answers 501 until you implement billing, so there is no webhook to register yet.

4. Deploy the web app on Vercel

Create a Vercel project from your repository with Root Directory set to apps/web. apps/web/vercel.json does the rest:

  • framework: express, with index.mjs as the function entry. It imports the bundled build/runtime/server.js and mounts it on an Express app.
  • buildCommand: pnpm build && cp -R build/client/. public/. pnpm build runs react-router build, bundles server.ts and worker.ts with esbuild, and then runs scripts/check-built-auth.mjs, which boots the bundle with VERCEL=1 and asserts that /app redirects anonymous visitors to /login.
  • The function includes build/** and has maxDuration: 300.
  • A cron calls /internal/jobs/run every 15 minutes with Authorization: Bearer {CRON_SECRET}.

On Vercel there is no long-running worker. When process.env.VERCEL is set, the server dispatches a bounded outbox batch for each new email with waitUntil, and the cron retries whatever is still pending. This lets Neon suspend after its idle timeout. The /mcp/* and /oauth/* routes run inside the same function, so the User MCP endpoint is {APP_BASE_URL}/mcp/user.

Deploy a preview first with its own APP_BASE_URL and TRUSTED_ORIGINS, then promote to production.

5. Deploy the admin app on Vercel

Create a second Vercel project from the same repository with Root Directory set to apps/admin. apps/admin/vercel.json is the same shape as the web one (framework: express, index.mjs, build/**, maxDuration: 300) without the cron. Its build runs react-router build and bundles server.ts; the admin server imports the web app's server modules by relative path, so the whole repository must be available to the build, which is the default for a monorepo root directory on Vercel.

Give it the admin values from the table above. The demo runs at https://tanstack-neon-admin.usenextbase.com. After deploy, / redirects to /app-admin, which sends anonymous visitors to /login. The Admin MCP endpoint is {admin APP_BASE_URL}/mcp/admin.

Deploy order for a release that touches both apps: migrations first, then the web app, then the admin app. The two apps share the database and the auth code, so keep them on the same commit.

6. Smoke checks after deploy

  1. GET {APP_BASE_URL}/healthz returns {"status":"ok"} on both origins; GET /readyz returns {"status":"ready"} once both database pools answer.
  2. Open /sign-up on the web origin, create an account, receive the verification mail from your real sender, and sign in. If nothing arrives, check the SMTP values and, on Vercel, the function logs for "Immediate email dispatch failed" and the cron logs for /internal/jobs/run.
  3. Create a workspace. This exercises withActorTransaction and RLS through the pooled connection.
  4. Run pnpm admin:grant -- you@example.com against production, sign in again on the web origin, then sign in on the admin origin and open /app-admin/users. Start an impersonation with a reason; you should land on the web origin with the banner, and Return to admin should bring you back.
  5. Open /mcp/connections/user on the web origin and /mcp/connections/admin on the admin origin; the server address shown must be the matching origin. GET /mcp/user and GET /mcp/admin without a token should return 401 with a WWW-Authenticate challenge. Then follow connect an agent and run projects_list.

Watch dead outbox jobs (status = 'dead' in app.outbox_jobs), readiness, error rate and Neon connection usage during the first rollout. Rollback is a redeploy of the previous build; migrations are forward-only, so write additive migrations and remove old columns in a later release.

Other Node hosts

If you run the apps as long-lived processes instead of Vercel functions, the README describes three commands from one build:

pnpm --filter @nextbase/database migrate   # deployment job, before anything else
pnpm --filter @nextbase/web worker:start   # NEXTBASE_WORKER_MODE=daemon node build/runtime/worker.js
pnpm --filter @nextbase/web start          # node build/runtime/server.js on PORT
pnpm --filter @nextbase/admin start        # the admin server, on its own PORT and origin

Without VERCEL set, the servers listen on PORT and drain connections for up to 30 seconds on SIGTERM, and the daemon worker polls the outbox instead of relying on a cron. Deploy migrations first, then a worker compatible with the new job types, then the web and admin servers. You supply the container or process manager; the repository doesn't include one.

Project structure

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

Next.js + Cloudflare D1

What the Next.js + Cloudflare D1 starter includes, what you need before you start, and where to go next.

On this page

1. Create the Neon database and roles2. Apply migrations and check the roles3. Production environment variables4. Deploy the web app on Vercel5. Deploy the admin app on Vercel6. Smoke checks after deployOther Node hosts