git push to live URL — the bulk of the
time is pasting secrets and creating the database.
Prerequisites
- A Postgres database with a pooled connection string. Neon, Supabase, Railway,
Crunchy — any of them. Serverless functions don’t keep connections warm, so the
pooler URL (port 6543 on Supabase, the
-poolerhost on Neon) is required. - A
BETTER_AUTH_SECRETof 32+ random characters:openssl rand -base64 32. - Your active payment provider’s keys. Whichever provider you set in
siteConfig.payment.providerneeds its*_SECRET_KEY,*_WEBHOOK_SECRETand the relevant*_PRICE_*ids populated. - Optional but recommended: a
RESEND_API_KEYfor transactional email. Without one, the mail facade no-ops gracefully and signups skip the verification step. - A GitHub repo Vercel can read. SSO, monorepo, fork — all fine.
pnpm typecheck && pnpm lint && pnpm build locally before pushing. CI runs the
same three; Vercel will refuse a broken build at the same gate.
Step-by-step
1. Push to GitHub
2. Import on Vercel
In the Vercel dashboard: Add New → Project → pick the repo. The Framework Preset auto-resolves to Next.js. Leave the build command blank (Vercel readspnpm build from package.json). Output directory is .next (default).
3. Set env vars
Settings → Environment Variables. The required baseline is:RESEND_API_KEY, GOOGLE_CLIENT_ID/SECRET,
TURNSTILE_SECRET_KEY + NEXT_PUBLIC_TURNSTILE_SITE_KEY. See
Env reference for the full list.
4. Migrate the production database
Before the first deploy, sync your schema. You have two options:db:push against production again — it
can drop columns silently. Switch to Option B and run db:migrate from a deploy
hook or a one-shot script, not from your running app.
5. Configure payment webhooks
In your payment provider’s dashboard, add a webhook endpoint pointing at:checkout.session.completed, invoice.paid, customer.subscription.updated,
customer.subscription.deleted (Stripe naming — adapt for others). Copy the
signing secret into STRIPE_WEBHOOK_SECRET (or equivalent).
6. Deploy
Push or click Deploy. The first build takes 2-3 minutes. Vercel’s logs surface env-var errors loudly —src/env.ts throws at startup if anything required is
missing.
Verify it works
Hit each URL once. Anything red means something’s misconfigured.https://your-domain.com/— home renders, hero copy is your localehttps://your-domain.com/zh/— Chinese herohttps://your-domain.com/api/ping— returns{ ok: true }https://your-domain.com/sitemap.xml— every public route listedhttps://your-domain.com/robots.txt— disallows/admin,/api/,/settingshttps://your-domain.com/register— signup works, you receive the verification email
payment and credit_transaction rows should both appear within ~5 seconds
of completing checkout. If they don’t, check the webhook logs in your payment
dashboard for 4xx responses.
Common pitfalls
BETTER_AUTH_URLmismatch. It must be the full prod URL with scheme, no trailing slash. Auth callbacks silently fail when this drifts from the actual domain.- Missing env vars in prod. Vercel separates Production / Preview / Development scopes. Set vars to “All Environments” unless you know you need per-env values.
- Webhook signing-secret mismatch. A stale
STRIPE_WEBHOOK_SECRETreturns 400 on every event. Rotate the secret in the dashboard, paste it into Vercel, redeploy. - Postgres connection limits. Serverless functions burn connections fast.
Use the pooler URL (port 6543 on Supabase,
-pooler.regionhost on Neon). Hit “max connections” errors? You’re using the direct URL. - Forgotten
NEXT_PUBLIC_APP_URL. Used by sitemap, OG images, OAuth redirects, payment success URLs. Must matchBETTER_AUTH_URLin production. - Skipping
db:pushbefore first deploy. App boots, then 500s on the first query because the tables don’t exist.
Rollback
Vercel keeps every deployment forever. Promote any previous build from the Deployments list — instant revert. If a migration is the culprit, restore from your DB provider’s point-in-time snapshot (Neon and Supabase both ship this on every plan).Official docs
- Vercel Next.js guide: vercel.com/docs/frameworks/nextjs
- Env vars: vercel.com/docs/projects/environment-variables
- Cron jobs (for credit-expiry sweeps): vercel.com/docs/cron-jobs
- Drizzle migrations: orm.drizzle.team/docs/migrations