CoreMVP
Deployment

Vercel Deployment

Deploy to Vercel.

Overview

Vercel is the easiest way to deploy Next.js applications. Since this is a single Next.js app with API routes, everything deploys together.

Architecture

Vercel (Next.js + API Routes) → Supabase Auth + Postgres + Stripe

All API routes are serverless functions on Vercel.

Deployment Steps

Authenticate the Provider CLIs

Log in before running any connect or catalog commands:

bunx supabase login
bunx vercel login
stripe login

These commands authenticate your local CLIs. They do not deploy the app or copy credentials into the repository.

Connect Supabase

Create or link the production Supabase project first. This creates .env, writes the hosted Supabase and database values, and can apply the template migrations:

./coremvp connect supabase
./coremvp connect supabase verify

Prepare the Stripe Test Catalog

Use the same Stripe test account that you used for local billing proof. If that account does not already contain the template products and prices, create them:

stripe fixtures supabase/stripe/stripe-fixtures-lifetime.json
stripe fixtures supabase/stripe/stripe-fixtures-subscription.json
stripe products list --active
stripe prices list --active

Copy the returned test IDs into src/config/pricing.config.ts:

  • lifetimeTiers: productId and priceId
  • subscriptionTiers: productId, priceIdMonthly, and priceIdYearly

The sk_test_... value you deploy must belong to that same Stripe account. Do not continue with example IDs or IDs from another account. Commit the pricing config change before the final deployment so Vercel builds the catalog you just verified.

Choose the Vercel project name that will own the Production deployment, then link it without syncing env yet:

./coremvp connect vercel --project <project-name> --skip-env-sync
./coremvp connect vercel status

The expected Production alias is https://<project-name>.vercel.app. You will verify the exact deployment URL after the first deploy and correct the provider URLs if Vercel reports a different alias.

Configure the Hosted Test Webhook and Env

In Stripe Dashboard test mode, create an endpoint at:

https://<project-name>.vercel.app/api/webhooks/stripe

Select these 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

Copy that endpoint's whsec_... signing secret. A local stripe listen secret does not verify events delivered to Vercel.

Now replace the example values in .env with the expected hosted URL, the verified Stripe test-mode account values, the deployed endpoint secret, and a server-only capture token:

NEXT_PUBLIC_APP_URL=https://<project-name>.vercel.app
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
EMAIL_DELIVERY_MODE=test_capture
TEST_EMAIL_CAPTURE_TOKEN=replace-with-a-random-server-only-token

Keep SUPABASE_SERVICE_ROLE_KEY and TEST_EMAIL_CAPTURE_TOKEN server-only. This first Production deployment is the isolated pre-launch proof target. It must not contain live Stripe keys or accept real charges. Do not sync http://localhost:3000 or *_replace_me values.

Now open Supabase URL Configuration for the linked project. Set Site URL to https://<project-name>.vercel.app, add https://<project-name>.vercel.app/** to Redirect URLs, and save both fields.

The domain-scoped redirect pattern allows the template's callback and account paths without allowing other Vercel projects. Keep supabase/config.toml on its checked-in localhost values: that file owns local Supabase Auth. Do not run bunx supabase config push from this repository as a substitute for the hosted Dashboard step because it would push the local Auth configuration to the linked project.

Sync Production Env

Sync the reviewed .env allowlist to the already-linked Vercel project:

./coremvp connect vercel --environment production

The command syncs the deploy allowlist from .env. It stops if .env does not exist; do not substitute the local .env.local file.

Preview env sync is an advanced path because .env usually contains production Supabase, Stripe, and webhook values. Only opt in after you have decided that Preview should use those same values or after you have replaced them with isolated preview provider resources:

./coremvp connect vercel --environment preview --allow-preview-production-env

Deploy and Confirm the Exact URL

Production is the default:

./coremvp deploy vercel
./coremvp deploy vercel verify https://<project-name>.vercel.app

Preview deployment remains available as an advanced path after preview-specific provider configuration:

./coremvp deploy vercel --preview

If Vercel reports a different Production URL, stop before proof. Update all three owners to that exact URL:

  1. NEXT_PUBLIC_APP_URL in .env
  2. Site URL and Redirect URLs in Supabase URL Configuration
  3. the Stripe test webhook endpoint URL

Copy the signing secret for the corrected Stripe endpoint, update STRIPE_WEBHOOK_SECRET, then run the Production env sync, deploy, and verify commands again. Do not prove billing against a placeholder or stale alias.

Prove Hosted Billing

Keep Stripe in test mode and run the hosted billing suite against the deployed URL:

PROD_E2E_AUTH_MODE=signup ./coremvp e2e billing:hosted

Sign-up mode creates a fresh subscription test user with no previous purchase or active subscription. The lifetime path creates its own fresh guest customer. The lifetime path proves guest Checkout, signed webhook persistence, protected onboarding capture, account setup, and gated access. The subscription path proves authenticated Checkout, the persisted subscription reader, and the same gated access decision.

Add the Custom Domain

Add the final domain in Vercel Project Settings → Domains, then apply the DNS records Vercel provides. Wait until Vercel shows the domain as valid before changing the app URL.

Move the Test Configuration to the Custom Domain

Keep Stripe in test mode while you move every URL owner:

  1. Set NEXT_PUBLIC_APP_URL in .env to https://<your-domain>.

  2. In Supabase URL Configuration, set Site URL to https://<your-domain> and add https://<your-domain>/** to Redirect URLs. Keep the Vercel alias redirect until the custom-domain proof passes.

  3. In Stripe Dashboard test mode, edit the existing hosted test webhook endpoint so its URL is https://<your-domain>/api/webhooks/stripe. Keep these events selected:

    • product.created, product.updated, product.deleted
    • price.created, price.updated, price.deleted
    • checkout.session.completed
    • customer.subscription.created, customer.subscription.updated, customer.subscription.deleted
  4. Copy that test endpoint's signing secret to STRIPE_WEBHOOK_SECRET in .env. Do not create a second test endpoint for the same deployment.

Sync the updated Production env, deploy, and verify the custom domain:

./coremvp connect vercel --environment production
./coremvp deploy vercel
./coremvp deploy vercel verify https://<your-domain>

Prove Auth and Billing on the Custom Domain

Run the hosted Auth and billing checks against the updated NEXT_PUBLIC_APP_URL:

TEST_BASE_URL=https://<your-domain> PROD_E2E_AUTH_MODE=signup ./coremvp e2e prod auth
TEST_BASE_URL=https://<your-domain> PROD_E2E_AUTH_MODE=signup ./coremvp e2e billing:hosted

The route check alone does not prove Auth callbacks or signed Stripe webhook delivery. Keep the Vercel alias in Supabase Redirect URLs until both commands pass. After they pass, remove https://<project-name>.vercel.app/** from Supabase URL Configuration, save, and confirm that the custom domain remains the Site URL and an allowed Redirect URL.

Switch to Customer Production

Only after the custom-domain checks pass, switch Stripe Dashboard to live mode and create one live endpoint at:

https://<your-domain>/api/webhooks/stripe

Select these events once for that live endpoint:

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

Copy that endpoint's live signing secret; do not reuse the test endpoint's secret. Replace the Stripe test values in .env with the matching live products, prices, keys, and signing secret. Update src/config/pricing.config.ts to the matching live product and price IDs. Set EMAIL_DELIVERY_MODE=real, RESEND_API_KEY, and RESEND_FROM_EMAIL, then sync and deploy again:

./coremvp connect vercel --environment production
./coremvp deploy vercel

Delete TEST_EMAIL_CAPTURE_TOKEN from the Vercel Production environment in Project Settings, then send a real transactional email and open its action link before launch. Do not use Stripe test cards after switching to live mode.

Environment Variables

The connect CLI reads .env only and syncs an allowlist of deploy keys into Vercel Production by default. It prints synced and skipped key names, never values. The deploy command then uses Vercel's remote project environment; .env is ignored during source upload and is not the deployed runtime source.

VariablePre-launch hosted proofCustomer launch
NEXT_PUBLIC_APP_URLHosted Vercel URLFinal custom domain
NEXT_PUBLIC_SUPABASE_URLHosted Supabase URLSame hosted Supabase URL
NEXT_PUBLIC_SUPABASE_ANON_KEYHosted anon keySame hosted anon key
SUPABASE_SERVICE_ROLE_KEYServer-only admin keySame server-only admin key
DATABASE_URLHosted Postgres connectionSame hosted Postgres connection
STRIPE_SECRET_KEYsk_test_...sk_live_...
STRIPE_WEBHOOK_SECRETTest-mode endpoint secretLive-mode endpoint secret
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYpk_test_...pk_live_...
EMAIL_DELIVERY_MODEtest_capturereal
TEST_EMAIL_CAPTURE_TOKENRequired, server-onlyRemove from Vercel Production
RESEND_API_KEYNot required for capture proofRequired, server-only
RESEND_FROM_EMAILNot required for capture proofRequired verified sender

Local development still uses .env.local. Deployment setup uses .env so it matches what ./coremvp connect supabase and ./coremvp connect vercel update.

Do not treat Vercel Preview as a safe beginner default when it points at production Supabase, Stripe, or webhook secrets. Keep the default Production sync, or create isolated Preview provider resources before enabling Preview env sync.

Performance Tips

Edge Runtime

For low-latency API routes, use Edge Runtime:

src/app/api/fast/route.ts
export const runtime = 'edge';

export async function GET() {
  return Response.json({ hello: 'world' });
}

Edge Runtime has limitations. Not all Node.js APIs are available.

Caching

Add cache headers to cacheable responses:

export async function GET() {
  return Response.json(data, {
    headers: {
      'Cache-Control': 's-maxage=60, stale-while-revalidate=300',
    },
  });
}

Static Generation

Pages without dynamic data are automatically static. Force static generation:

export const dynamic = 'force-static';

Continuous Deployment

Vercel automatically deploys when you push to main:

git push origin main

For preview deployments, push to feature branches only after Preview has isolated Supabase, Stripe, and webhook configuration.

Troubleshooting

Build Fails

Check build logs in Vercel dashboard. Common issues:

  • Missing environment variables
  • TypeScript errors
  • Dependency issues

API Routes Timeout

Vercel serverless functions have a 10s timeout (Pro: 60s). For long operations:

  • Use background jobs (Vercel Cron, Inngest)
  • Move to Edge Runtime for faster cold starts

Environment Variables Not Working

  • Ensure variables are set for the correct environment (Production/Preview/Development)
  • Redeploy after adding new variables

Was this page helpful?

On this page