Taking a Next.js + Supabase App to Production: The Complete Checklist
Your app works on localhost. Nice. Now comes the part between "works on my machine" and "works for paying customers," written so nothing surprises you at 11pm on launch night.
01The accounts audit
Before touching code, confirm who owns what. Production incidents love apps deployed from a developer's personal account.
- The Vercel project, the Supabase project, and the domain registrar should live in accounts the owner controls, not the freelancer's.
- Every third-party service (Stripe, Resend, OpenAI) needs its own production account with billing attached, test keys will quietly stop being enough.
- Know your Supabase plan limits before launch day. The free tier pauses inactive projects, which is a fun thing to discover from a customer.
02Environment variables
Every key lives in an encrypted store, never in the repo, or even in a chat message. The typical Next.js + Supabase set looks like this:
# Browser-safe (still mark Sensitive in Vercel) NEXT_PUBLIC_SUPABASE_URL=https://<ref>.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_... # Server-only. Leaks here are incidents, not oopsies. SUPABASE_SERVICE_ROLE_KEY=... STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... RESEND_API_KEY=re_...
NEXT_PUBLIC_ ships to the browser. That's by design for the URL and anon key, but if a "secret" ever gets that prefix to fix a build error, you have published it. Check twice.Set every variable for all three Vercel environments (Production, Preview, Development), and mark all of them Sensitive. Yes, even the public ones. It costs nothing and builds the right habit.
03Database: migrations in order, then RLS
Migrations are numbered for a reason. Run them in sequence against the production project, and resist the urge to "just paste the schema" from your dev database.
# Link the production project, then push in order
supabase link --project-ref <prod-ref>
supabase db push
Then verify Row Level Security before a single real user signs up:
- RLS is enabled on every table that holds user data. A table without RLS behind an anon key is a public API.
- Policies reference
auth.uid(), not values the client sends. Clients lie. - Try to read another user's row with a fresh test account. If you can, so can everyone.
04Auth redirect URLs
The classic launch bug: login works locally and fails silently in production, because Supabase only redirects to URLs it has been told about.
- In Supabase, set the Site URL to your production domain (
https://app.example.com, exact protocol, no trailing slash surprises). - Add every domain that logs in to Redirect URLs: production, the
*.vercel.apppreview pattern, and localhost for your own sanity. - If you use magic links, send yourself one from production before customers do. Read it on your phone, not your dev machine.
05Webhooks
Webhooks are configured per environment, and pointing production Stripe at a preview URL is a rite of passage nobody enjoys. Using Stripe as the example:
# The endpoint lives at your real domain https://app.example.com/api/webhooks/stripe # Test it fires end to end before launch stripe trigger payment_intent.succeeded
- Create the endpoint in live mode, then copy the live
whsec_signing secret into your env vars. The test-mode secret will fail signature checks and look like a mystery bug. - Verify the signature on every event, and return a 200 fast. Do slow work after you respond.
- Confirm each price ID in the code matches the live product catalog, test and live IDs are different, always.
06The deploy itself
# From the project root, once everything above is green
vercel --prod
Then connect the custom domain in Vercel, let it issue the certificate, and update the Site URL from step 04 if the final domain changed. Redeploy after any env var edit, variables are baked in at build time, not read live.
07The ten-minute smoke test
On the production URL, on a real phone and a laptop, as a brand-new user. Tap each item as you go, they check off.
- ✓Sign up with a fresh email, receive the verification email, and click it from the inbox it landed in.
- ✓Log out, log back in, and refresh mid-session. Still logged in? Good.
- ✓Create, edit, and delete a record. Confirm it actually changed in the database, not just on screen.
- ✓Run one real payment end to end, then check the webhook log and the customer record agree with each other.
- ✓Try to access another user's data while logged in. Failure here is the only passing grade.
- ✓Check the browser console and server logs for anything red you've been ignoring.
08Handoff and key rotation
If someone else built this for you, the last step is making it fully yours: transfer the Vercel and Supabase projects to your accounts, then rotate every key the builder ever touched, service role, Stripe secrets, email API keys, and update the env vars with the new values. A good developer will walk you through this without being asked. It's how you know you hired one.
This checklist is the written version of a service I actually offer, taking finished apps live, tested, and handed over clean. If yours is sitting at ninety percent, I'm easy to find.