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 + StripeAll 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 loginThese 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 verifyPrepare 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 --activeCopy the returned test IDs into src/config/pricing.config.ts:
lifetimeTiers:productIdandpriceIdsubscriptionTiers:productId,priceIdMonthly, andpriceIdYearly
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.
Link the Vercel Project
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 statusThe 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/stripeSelect these events:
product.created,product.updated,product.deletedprice.created,price.updated,price.deletedcheckout.session.completedcustomer.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-tokenKeep 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 productionThe 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-envDeploy and Confirm the Exact URL
Production is the default:
./coremvp deploy vercel
./coremvp deploy vercel verify https://<project-name>.vercel.appPreview deployment remains available as an advanced path after preview-specific provider configuration:
./coremvp deploy vercel --previewIf Vercel reports a different Production URL, stop before proof. Update all three owners to that exact URL:
NEXT_PUBLIC_APP_URLin.env- Site URL and Redirect URLs in Supabase URL Configuration
- 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:hostedSign-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:
-
Set
NEXT_PUBLIC_APP_URLin.envtohttps://<your-domain>. -
In Supabase URL Configuration, set Site URL to
https://<your-domain>and addhttps://<your-domain>/**to Redirect URLs. Keep the Vercel alias redirect until the custom-domain proof passes. -
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.deletedprice.created,price.updated,price.deletedcheckout.session.completedcustomer.subscription.created,customer.subscription.updated,customer.subscription.deleted
-
Copy that test endpoint's signing secret to
STRIPE_WEBHOOK_SECRETin.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:hostedThe 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/stripeSelect these events once for that live endpoint:
product.created,product.updated,product.deletedprice.created,price.updated,price.deletedcheckout.session.completedcustomer.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 vercelDelete 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.
| Variable | Pre-launch hosted proof | Customer launch |
|---|---|---|
NEXT_PUBLIC_APP_URL | Hosted Vercel URL | Final custom domain |
NEXT_PUBLIC_SUPABASE_URL | Hosted Supabase URL | Same hosted Supabase URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Hosted anon key | Same hosted anon key |
SUPABASE_SERVICE_ROLE_KEY | Server-only admin key | Same server-only admin key |
DATABASE_URL | Hosted Postgres connection | Same hosted Postgres connection |
STRIPE_SECRET_KEY | sk_test_... | sk_live_... |
STRIPE_WEBHOOK_SECRET | Test-mode endpoint secret | Live-mode endpoint secret |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_test_... | pk_live_... |
EMAIL_DELIVERY_MODE | test_capture | real |
TEST_EMAIL_CAPTURE_TOKEN | Required, server-only | Remove from Vercel Production |
RESEND_API_KEY | Not required for capture proof | Required, server-only |
RESEND_FROM_EMAIL | Not required for capture proof | Required 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:
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 mainFor 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
Related
- Docker - Alternative deployment
- Environment Variables - Configuration reference
Was this page helpful?