Deployment
Deploy the TanStack + Convex starter: a Convex production deployment for the backend and two Vercel projects for the web app and the admin app.
Production is three deployments against one backend: the Convex deployment (schema, functions, HTTP actions, MCP), the web app and the admin app, each built by Vite and Nitro. The committed vercel.json builds the web frontend only; it does not run convex deploy, so you publish the backend separately and set the public VITE_ variables in each Vercel project by hand. This page follows the repository's deployment.md and docs/separate-admin-app.md; the shared Vercel guide covers the platform-generic steps.
Before you deploy
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm buildYou need a Convex account, the Convex CLI (installed with the repo), two Vercel projects, and a Resend sender, because sign-up cannot finish without AUTH_RESEND_KEY and AUTH_EMAIL_FROM.
Where each value lives
| Place | Variables |
|---|---|
Convex deployment (pnpm convex env set) | APP_URL, NEXTBASE_ADMIN_EMAIL, EMAIL_CHANGE_SECRET, SITE_URL, ADMIN_SITE_URL, JWT_PRIVATE_KEY and JWKS (from auth:setup), MCP_APP_URL, and every AUTH_*, RESEND_*, EMAIL_FROM, STRIPE_* and OPENAI_* secret |
| Web Vercel project | VITE_CONVEX_URL, VITE_ADMIN_APP_URL, optional VITE_OAUTH_PROVIDERS, VITE_POSTHOG_KEY, VITE_POSTHOG_HOST |
| Admin Vercel project | VITE_CONVEX_URL, VITE_WEB_APP_URL, optional VITE_OAUTH_PROVIDERS |
Your local .env.local files are only read by the Convex CLI and Vite on your machine; nothing in production reads them.
Provision the Convex deployment
pnpm convex login
pnpm convex initUse pnpm convex deployment select instead of init to pick an existing deployment. If you select the production deployment with the CLI, it writes CONVEX_DEPLOYMENT=prod:<name> and the URLs into your local .env.local; switch back with pnpm db:local:select before local development. The project's URLs are in the Convex dashboard under Settings > URL & Deploy Key:
https://<name>.convex.cloudis the functions URL (VITE_CONVEX_URLin both Vercel projects)https://<name>.convex.siteis the HTTP actions origin (CONVEX_SITE_URL), where/mcp/*,/oauth/*,/api/*and the auth routes live
Set the backend environment
All secrets live in the Convex deployment, never in a browser bundle. Set the required values:
pnpm convex env set APP_URL https://app.example.com
pnpm convex env set ADMIN_SITE_URL https://admin.example.com
pnpm convex env set NEXTBASE_ADMIN_EMAIL admin@example.com
pnpm convex env set EMAIL_CHANGE_SECRET <long-random-value>
pnpm convex env set AUTH_RESEND_KEY re_...
pnpm convex env set AUTH_EMAIL_FROM "Nextbase <auth@example.com>"APP_URL must be the public HTTPS origin of the web app without a path or credentials. ADMIN_SITE_URL is the admin origin; the auth redirect allowlist permits exactly these two origins, and admin MCP consent is routed to it. Then add the optional groups you use: AUTH_GOOGLE_ID/AUTH_GOOGLE_SECRET and the GitHub and Twitter pairs, RESEND_API_KEY and EMAIL_FROM, STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET, OPENAI_API_KEY and OPENAI_ALLOWED_MODELS. The environment variables page describes each one.
Run Convex Auth setup against the production deployment so it has signing keys and SITE_URL:
pnpm auth:setupSet SITE_URL to your web origin when the CLI asks, or correct it afterwards with pnpm convex env set SITE_URL https://app.example.com. Keep existing JWT_PRIVATE_KEY, JWKS and Auth tables when updating an app that already has users.
For the user MCP consent page, set the web origin explicitly:
pnpm convex env set MCP_APP_URL https://app.example.comWithout it the backend falls back to APP_URL, then SITE_URL.
Deploy the Convex backend
The Vercel builds do not touch the backend, so publish it first from your machine or your own CI:
pnpm install
pnpm convex deployThis publishes apps/database/convex (schema, functions, both MCP gateway components). Run it again whenever Convex code changes; frontend-only changes do not need it. If you prefer to build everything locally in one go, pnpm convex deploy --cmd 'pnpm build' --cmd-url-env-var-name VITE_CONVEX_URL also runs the Turbo build with VITE_CONVEX_URL set to the production URL; the flag is required because Vite lives in a workspace package and Convex cannot detect the VITE_ prefix from the root package.json.
Create the web Vercel project
Import the repository with the project root set to the repository root. vercel.json already sets:
- Install command:
pnpm install - Build command:
pnpm --filter @nextbase/web build && mkdir -p .vercel && cp -r apps/web/.vercel/output .vercel/output - Output directory:
.vercel/output - Framework: none
The copy step moves the Nitro Build Output API artifacts from apps/web/.vercel/output to the repository root where Vercel expects them. It also adds X-Frame-Options, Content-Security-Policy: frame-ancestors 'none' and Referrer-Policy headers on /mcp/*.
Add these Vercel environment variables:
| Variable | Value |
|---|---|
VITE_CONVEX_URL | https://<name>.convex.cloud from the Convex dashboard |
VITE_ADMIN_APP_URL | The admin origin, for example https://admin.example.com |
VITE_OAUTH_PROVIDERS | Optional, for example google,github,twitter |
VITE_POSTHOG_KEY, VITE_POSTHOG_HOST | Optional analytics |
Preview deployments need their own Convex deployment and VITE_CONVEX_URL if you want them to work; the repository does not document a preview setup.
Create the admin Vercel project
Create a second Vercel project from the same repository with the project root set to apps/admin. apps/admin/vercel.json sets:
- Install command:
pnpm install --frozen-lockfile - Build command:
pnpm run build - Output directory:
.vercel/output - Framework: none
It also sends X-Robots-Tag: noindex, nofollow, Referrer-Policy: no-referrer and X-Frame-Options: DENY on every response. If your Vercel project has to stay rooted at the repository, the root vercel.admin.json holds the equivalent configuration (pnpm --filter @nextbase/admin build plus the copy step); apply it to that project yourself.
Add these Vercel environment variables:
| Variable | Value |
|---|---|
VITE_CONVEX_URL | The same https://<name>.convex.cloud as the web project |
VITE_WEB_APP_URL | The web origin, for example https://app.example.com. Impersonation refuses non-HTTPS origins |
VITE_OAUTH_PROVIDERS | Optional, if the admin login should show social buttons |
Give the project its own domain and make sure it matches ADMIN_SITE_URL in the Convex deployment and VITE_ADMIN_APP_URL in the web project. The demo uses tanstack-convex-admin.usenextbase.com.
Seed and backfill
After the first deployment:
pnpm convex run dev/seed:seedWhen upgrading an existing deployment to the exact users aggregate, run once:
pnpm convex run migrations:backfillUsersAggregateAdd --prod if your CLI is not already targeting the production deployment.
Claim admin and register Stripe
Sign up in the web app with the NEXTBASE_ADMIN_EMAIL address, then open the admin app, sign in with the same account and click Claim admin access on /app-admin. Do this right after the first production deploy.
If you use Stripe, add a webhook endpoint at https://<name>.convex.site/api/stripe/webhook, then set STRIPE_WEBHOOK_SECRET and STRIPE_SECRET_KEY with pnpm convex env set.
Auth callbacks and origins
Convex Auth's HTTP routes are served from the .convex.site origin. SITE_URL must be the web origin and ADMIN_SITE_URL the admin origin so redirects, reset links and admin logins return to the right app; the redirect callback rejects any other origin. Register the provider callback URLs required by Convex Auth for Google, GitHub and Twitter against the same production deployment. Only providers listed in VITE_OAUTH_PROVIDERS appear in the UI of the app that lists them.
Deploy to another host
Change the Nitro preset in apps/web/vite.config.ts (and apps/admin/vite.config.ts) from vercel to one your host supports (node-server, netlify, cloudflare-pages), run pnpm build, and serve the output. The web build only needs VITE_CONVEX_URL and VITE_ADMIN_APP_URL at build time, the admin build VITE_CONVEX_URL and VITE_WEB_APP_URL. The Cloudflare guide is written for the D1 kits; the Convex backend stays on Convex whichever host serves the frontends.
Post-deploy smoke checks
GET https://<name>.convex.site/api/healthreturns{ "ok": true, "service": "nextbase-convex" }.- Open
/loginand/sign-upon the web app, create a test account, receive the verification email and finish onboarding with a workspace. - Open the admin origin with the admin email, sign in and claim access on
/app-admin. - From a user's detail page in the admin app, run Impersonate in web app and confirm the web app shows the support session banner and End support session returns you to the admin app.
- Open
/user/settings/connectionsin the web app and confirm the User MCP URL shows your.convex.siteorigin, not the demo's. Follow connect an agent to runaccount_contextandprojects_list. - Open
/app-admin/connectionsin the admin app and check the Admin MCP URL in the same way; the consent page for an admin connection should open on the admin origin. - If you use Stripe, send a test event from the Stripe dashboard and confirm it appears in the admin payment gateway pages.
- Check that
pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm buildstill pass on the deployed commit.
See troubleshooting if login loops, emails do not arrive or an agent cannot reach consent.