CoreMVP

Vercel Deployment

Link the template to the correct Vercel project, configure Production values safely, deploy, and run bounded production checks.

Deploy the single Next.js app to Vercel after the production Supabase project, Stripe test-mode objects, and required environment values are ready. Vercel CLI owns project linking, environment configuration, and deployment.

Use the intended Vercel account, team, and project. Never put database URLs, API keys, signing secrets, or tokens in command arguments, terminal logs, or screenshots.

Deploy to production

Prepare hosted Supabase

Create or select the project in Supabase, then authenticate and link the checked-out template:

bunx supabase login
bunx supabase link --project-ref <project-ref>
bunx supabase db push

Put the hosted DATABASE_URL and the other required app values in ignored .env for local operator use. Check the app tables without calling the Supabase management API:

./coremvp db verify

Compare the configured connection identity—endpoint, configured db, and configured user—with the connection shown in that project's Connect panel. For a Shared Pooler URL, configured user must contain postgres.<project-ref>. Stop if any configured field differs. Continue only when every expected app table is present. The verifier does not query Supabase project metadata.

Check the Vercel identity and project

Authenticate, inspect the current identity, and list projects in the intended account or team before linking:

bunx vercel login
bunx vercel whoami
bunx vercel teams ls

For a personal-account project, omit --scope. Vercel rejects a personal username passed as a scope:

bunx vercel project ls
bunx vercel link --project <project-name>

For a team project, use the team slug shown by teams ls:

bunx vercel project ls --scope <team-slug>
bunx vercel link --project <project-name> --scope <team-slug>

If the identity, team, or project is wrong, stop before linking. Vercel writes ignored local link metadata. The provider CLI uses that link to select the project for later commands.

Add the Production environment

Add public values with Vercel's interactive prompt:

bunx vercel env add NEXT_PUBLIC_APP_URL production
bunx vercel env add NEXT_PUBLIC_SUPABASE_URL production
bunx vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY production
bunx vercel env add NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY production

Add server values the same way. Mark secrets sensitive:

bunx vercel env add DATABASE_URL production --sensitive
bunx vercel env add SUPABASE_SERVICE_ROLE_KEY production --sensitive
bunx vercel env add STRIPE_SECRET_KEY production --sensitive
bunx vercel env add STRIPE_WEBHOOK_SECRET production --sensitive
bunx vercel env add RESEND_API_KEY production --sensitive
bunx vercel env add TEST_EMAIL_CAPTURE_TOKEN production --sensitive
bunx vercel env add EMAIL_DELIVERY_MODE production
bunx vercel env add RESEND_FROM_EMAIL production

The CLI prompts for each value so secrets do not appear in argv. If a key already exists, update it without deleting it. Keep public updates readable, and mark every server-secret update sensitive:

bunx vercel env update NEXT_PUBLIC_APP_URL production
bunx vercel env update DATABASE_URL production --sensitive
bunx vercel env ls production

Repeat the first form for existing public keys and the --sensitive form for existing server secrets.

Create TEST_EMAIL_CAPTURE_TOKEN in a password manager before you paste it into the sensitive Vercel prompt, and retain that same value for ignored local .env. Vercel sensitive values cannot be read back after creation. If the retained token is lost, update it from a new password-manager value, deploy again, and replace the local copy before lifetime proof.

See Environment Variables for which values are public, server-only, proof-only, or required at launch.

Configure provider callbacks

In Supabase Dashboard → Authentication → URL Configuration, set the Site URL to the hosted app URL and add its redirect pattern.

In Stripe Dashboard test mode, create one webhook endpoint at https://<project-name>.vercel.app/api/webhooks/stripe. Select these exact events:

  • product.created
  • product.updated
  • product.deleted
  • price.created
  • price.updated
  • price.deleted
  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted

Update STRIPE_WEBHOOK_SECRET in Vercel Production with that endpoint's signing secret.

Deploy after Production values are complete

bunx vercel deploy --prod

Environment changes apply to subsequent deployments. Deploy again after adding or updating a Production value.

Check liveness, Auth, and billing

Run the bounded route smoke first:

./coremvp prod e2e smoke https://<project-name>.vercel.app

This checks only /, /api/health, and /pricing. It does not prove Auth, billing, callbacks, or webhook persistence.

The production proof CLI reads local values separately from Vercel. In a fresh checkout, restore readable Production values first:

bunx vercel env pull .env --environment=production
chmod 600 .env

Sensitive Vercel values cannot be read back. Treat every [SENSITIVE] value as missing. In the next steps, replace the existing SUPABASE_SERVICE_ROLE_KEY and TEST_EMAIL_CAPTURE_TOKEN assignments with the real local values. Keep exactly one assignment for each key; do not append duplicate lines because the template CLI reads the first assignment.

In Supabase Dashboard, open Project Settings → API Keys → Legacy API Keys for the project ref in NEXT_PUBLIC_SUPABASE_URL, then paste service_role into ignored .env as SUPABASE_SERVICE_ROLE_KEY. The authenticated CLI is also read-only, but keep its key response out of terminal output:

mkdir -p tmp/provider
umask 077
bunx supabase projects api-keys --project-ref <project-ref> --output-format json > tmp/provider/supabase-api-keys.json

Open the ignored file in a private editor, copy the entry named service_role into .env, then delete the temporary file. Stop if the key is missing, redacted, or belongs to a different project.

rm tmp/provider/supabase-api-keys.json

Retrieve TEST_EMAIL_CAPTURE_TOKEN from the password-manager item retained when the sensitive Vercel value was created and paste it into ignored .env. If it is unavailable, stop and rotate it through the Vercel prompt, deploy again, and save the replacement. Verify the deployed capture mode without sending or printing the token:

curl -sS -w '\nHTTP %{http_code}\n' https://<project-name>.vercel.app/api/testing/email-captures/onboarding

Expected result: the app response {"message":"Unauthorized","ok":false} followed by HTTP 401. Require both markers because a provider or deployment-protection 401 does not prove that the app route ran. An app response with HTTP 404 means test_capture is disabled; correct the Vercel Production values and redeploy before lifetime proof. The subsequent lifetime proof verifies that the retained local token matches the deployment. Stop if the local Supabase project or capture token does not match the deployed app.

Run the matching production flows against the same exact URL:

export TEST_BASE_URL=https://<project-name>.vercel.app
export PROD_E2E_AUTH_MODE=signup
./coremvp prod e2e auth
unset PROD_E2E_AUTH_MODE
./coremvp prod e2e billing:lifetime
export PROD_E2E_AUTH_MODE=signup
./coremvp prod e2e billing:subscription

Custom domain and live billing

Add the domain in Vercel, follow the DNS records Vercel provides, then update the Supabase Site URL and Redirect URLs. Update NEXT_PUBLIC_APP_URL in Vercel Production and deploy again before running the smoke, Auth, and billing checks against the custom domain.

Only after the test-mode path passes should you create the live Stripe objects and live webhook endpoint. Replace every Stripe test value with its matching live value, set real email delivery values, remove proof-only values such as TEST_EMAIL_CAPTURE_TOKEN, deploy again, and send a real transactional email before launch.

Preview deployments

Preview is not part of the beginner production path. Do not point an arbitrary feature-branch preview at production Supabase, Stripe, or webhook secrets. Use isolated provider values before configuring Preview environment variables.

Was this page helpful?

On this page