I've tried custom auth with JWT tokens - spent 3 months and ended up with more security holes than Swiss cheese. I've wrestled with NextAuth.js configs that randomly break when you update anything. I built payment processing from scratch once and learned why you don't fucking do that.
Supabase + Next.js + Stripe still sucks sometimes, but it's the first combo that didn't make me want to quit programming and become a farmer.
The Parts That Actually Work
Supabase Auth: Less Painful Than Rolling Your Own
Supabase Auth handles user registration, login, and all the tedious password reset stuff. The real win is Row Level Security (RLS) - your database automatically filters data by user without you having to remember to add WHERE user_id = ?
to every goddamn query.
The JWT tokens contain user info that works across your Next.js app and Stripe. When auth works, you get:
- Sessions that don't randomly expire (mostly)
- User data that stays in sync
- Database queries that can't leak other users' data
- Social logins that actually work without fighting OAuth
But tokens die after exactly 1 hour like clockwork, and if your refresh logic is broken, users get kicked to the login screen right in the middle of using your app. Discovered this delightful feature during a client demo while 8 board members watched our "seamless user experience" crash and burn.
Next.js App Router: Server Components Are Tricky
The Next.js App Router wants everything server-side, which is great until you realize auth state is complicated. The `@supabase/ssr` package tries to solve this, but you need two different Supabase clients.
One for server (reads cookies), one for client (handles real-time updates). Mix them up and you'll debug for 4 hours trying to figure out why users show up as authenticated on page load but immediately get booted. Error message? "TypeError: Cannot read property 'user' of undefined". Thanks, Next.js 14, super helpful.
Stripe: The Part That Usually Works
Stripe's API is solid, but connecting it to Supabase users is where things get interesting. You need to:
Create Stripe customers after someone signs up. Simple, right? Wrong. Hit a race condition where users would sign up but never get a Stripe customer ID. Failed a ton - maybe 30%? Could've been more, hard to track because the error logging was also broken. Spent a whole Saturday debugging this until I found a Stack Overflow comment buried at the bottom: the auth.users
trigger fires before the profiles
table insert finishes. Async hell.
Handle subscriptions through Stripe's billing system. The subscription data lives in Stripe, but you need to sync status to your database. Webhooks do this, but they arrive out of order and sometimes fail. Build idempotency or suffer.
Process payments with Stripe Checkout. This part actually works reliably - Stripe handles the credit card headaches, you just redirect users and wait for webhook confirmations. The Stripe Elements integration offers more customization if needed, while Payment Intents API gives you full control over the payment flow.
What Performance Actually Looks Like
Here's what you can realistically expect:
Security is decent - RLS policies protect your database, Stripe handles PCI compliance, and you don't store credit card data. But you're still responsible for proper session management and webhook signature verification. Mess up either one and you're in trouble.
Performance is a shitshow. Auth is usually fast, except when it isn't. Supabase can be slow as molasses if you're far from their data centers. Stripe is reliable until you hit traffic spikes, then it can take 2+ seconds to respond. Cold starts are the real killer - first page load after deploy can take forever. Sometimes 2+ seconds. Users think your site died.
Scaling works up to maybe 50k users before you need to think harder. After that, you'll hit database connection limits, webhook processing bottlenecks, and realize Supabase's connection pooling isn't magic. You might need PgBouncer or Prisma connection pooling for better performance. Database optimization becomes critical at scale.
The Data Sync Nightmare You Signed Up For
You now have data scattered across three systems that need to stay in sync:
- Supabase: User profiles, app data, subscription status
- Stripe: Payment methods, billing history, actual money stuff
- Your Next.js app: Whatever cached state you're trying to keep consistent
Stripe webhooks are supposed to keep everything in sync. When payments succeed or fail, Stripe tells your webhook endpoint, which updates Supabase. Simple, right?
Nope. Webhooks show up out of order, fail without telling you, and sometimes arrive 6 hours late for no fucking reason. Had a customer pay for a subscription, webhook didn't show up for like 6 hours or something. They couldn't access the app and support was getting angry emails. Could've been more, hard to track because the error logging was also broken at the time.
"Eventually consistent" means "your data is fucked but maybe it'll fix itself later".
Security: Better Than Rolling Your Own
The security model is actually pretty solid when you don't screw it up. Understanding PostgreSQL RLS and JWT token validation is essential:
RLS policies in Supabase automatically filter queries by user ID from the JWT token. No more forgetting WHERE user_id = ?
and accidentally leaking someone else's data. Just don't use the service role key in client code - that bypasses RLS and exposes everything.
Next.js middleware runs on every request to check auth status. It's supposed to refresh expired tokens automatically, but sometimes it doesn't and users get bounced to the login page mid-session. Fun times.
Stripe webhook signatures prevent people from faking payment events. Always verify these or someone will post fake "payment succeeded" events to your webhook and get free subscriptions. I've seen it happen.
Why Not Build It Yourself?
I've tried the DIY route. Here's why I don't anymore:
- Time: This stack took me 1-2 weeks to get working well vs 2-3 months building auth and payments from scratch
- Security: Supabase and Stripe handle PCI compliance and SOC 2 requirements I don't want to think about. DIY means expensive audits and sleepless nights
- Maintenance: Vendor updates happen automatically. With custom code, every security patch is your problem
- Features: Getting 2FA, social logins, and subscription management working properly takes forever
Yeah, you're locked into these vendors. But the alternative is maintaining authentication and payment infrastructure forever. Pick your poison.
The Gotchas That'll Bite You
Here's what breaks in production:
Webhook chaos: Stripe sends webhooks out of order and retries them when they fail. Build idempotency handling or watch your subscription statuses flip-flop randomly. I spent a whole weekend debugging this after launch.
Auth state mysteries: Server components render with one user state, client hydrates with another. Users see flickers, get logged out randomly, or worse - see other people's data for a split second. Loading states are your friend.
Database connections: Every API route opens database connections. Hit any decent traffic and you'll exhaust Supabase's connection pool. Enable connection pooling or your app dies under load.
Token expiration: Tokens expire after an hour. If your refresh logic sucks, users get bounced to login mid-session. Always happens during the most important user flows.