Building a SaaS application locally feels great until you deploy it and everything breaks in spectacular ways. I've watched this stack fail at 2am on Black Friday, hit Supabase connection limits during a Product Hunt launch, and lose $12k in payments because webhooks were silently failing.
Here's what actually happens when you deploy Next.js + Supabase + Stripe to production, and the fixes that work when you're getting paged at 3am.
The Production Stack Components
Next.js 15 is solid until you deploy and realize the App Router caching aggressively caches things you don't want cached. Like user authentication state. Or API responses that should be dynamic. The caching works great locally but breaks in production because of CDN interactions you didn't expect.
Pro tip: Add export const dynamic = 'force-dynamic'
to API routes that handle payments or user-specific data, or you'll spend hours debugging why users see each other's data.
Supabase works beautifully in development with unlimited local connections. Production? Welcome to connection limit hell. Supabase Pro gives you 60 connections and each Next.js serverless function grabs a new one. Do the math - you'll hit the limit around 400-800 concurrent users. The fun part? Supabase doesn't warn you until you're already down.
I learned this the hard way when our signup page went viral and Supabase started returning FATAL: remaining connection slots are reserved for non-replication superuser connections
. The fix: enable connection pooling and prepare to upgrade plans sooner than expected.
Stripe webhooks are reliable... until they're not. Webhooks will randomly fail during high traffic, timeout because Vercel functions are slow, or get blocked by your firewall. The worst part? They fail silently.
You won't know payments are failing until customers email asking why their subscription isn't working. Always log webhook failures and set up alerts. And yes, implement idempotency - I've seen users charged 8 times for the same subscription because of webhook retries.
The Shit That Actually Breaks
Environment Variables Will Ruin Your Week: You'll accidentally use dev Stripe keys in production (this charges your card instead of test cards). Or forget to set NODE_ENV=production
and wonder why Next.js is slow. Or mix up Supabase project URLs and users can see each other's data.
Copy this checklist and check it twice:
## Production environment must have:
NEXT_PUBLIC_SUPABASE_URL=your-prod-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-prod-service-role-key
STRIPE_SECRET_KEY=sk_live_... # NOT sk_test_
STRIPE_WEBHOOK_SECRET=whsec_...
Connection Limits Hit During Success: Nothing kills your viral moment like hitting database connection limits. Supabase gives you 60 connections on Pro, but one Next.js API route can use 3-5 connections simultaneously. The math sucks:
- 20 users hitting your API = potential for 100+ connections
- Connection pool exhausted = your app dies while you frantically Google solutions
- Users see "Database unavailable" = they bounce forever
Enable connection pooling and monitor connection usage in the Supabase dashboard obsessively.
Webhooks Timeout and Lose Money: Stripe gives your webhook endpoint 20-30 seconds to respond (not 10 like I thought). Vercel functions timeout after 10 seconds on Hobby tier. You do the math.
Lost webhook = lost subscription update = angry customer = refund requests. Upgrade to Pro for 60-second timeouts or optimize your webhook processing.
Production-Ready Architecture Patterns
Don't dump everything in one API route: Split your routes by function - auth goes in /api/auth/*
, payments in /api/payments/*
, subscription management in /api/subscriptions/*
, and webhooks get their own /api/webhooks/*
space. Makes debugging way less painful when something breaks. I've spent hours tracking down payment issues buried in a 500-line catch-all API route.
Index your damn database or it'll be slow as hell: Your Supabase queries will timeout without proper indexes. I watched a startup's signup flow take 45 seconds because they didn't index user_id
on their profiles table. Composite indexes on columns you query together (like user_id, created_at
), partial indexes for status filtering (WHERE status = 'active'
), and database functions for complex logic that would otherwise require 5 API calls.
Real-time updates are great until they break: Supabase's real-time features work well for live subscription updates and payment confirmations, but you need fallback logic when WebSocket connections drop. I've seen payment confirmations lost because developers assumed real-time would always work. Set up proper channel management, presence tracking, and always have a polling fallback for critical updates.
Set up automated deployments or enjoy fixing broken production every Friday
Vercel's Git integration automatically deploys when you push to main, which is great until you push broken code at 4:59 PM on Friday. Use preview deployments to test features in a production-like environment, set up database migration strategies that won't destroy your data, and have rollback capabilities ready because you WILL need them.
Test the critical path or users will find your bugs: Test Stripe webhook handling with actual events, verify your Supabase RLS policies don't leak data between users, and test Next.js API routes under error conditions. I've seen apps go live where sign-up worked but payment confirmation emails never sent because webhook testing was "on the TODO list."
Monitor everything or you'll find out from angry users: Vercel Analytics shows you basic performance, Supabase logs show database issues, and Stripe Dashboard tracks payments. But you need Sentry for error tracking and custom metrics for critical flows. Set up alerts that wake you up at 3am - better than waking up to support tickets.
Get this foundation right and you might actually sleep through the night after deployment. Ignore it and you'll be debugging payment failures while your users are trying to give you money.