Deploy a Next.js 16 + Supabase SaaS on Vercel (Auth, Billing & PWA)
Step-by-step guide to ship a production SaaS with Next.js 16 App Router, Supabase Auth SSR, Lemon Squeezy checkout, and a PWA-ready frontend on Vercel.

Shipping a SaaS in 2026 means more than a landing page and a Stripe button. Indie builders need auth that survives SSR, billing that handles digital delivery, and a stack that deploys cleanly on Vercel without a week of glue code.
This guide walks through the architecture we use in the Vibe PWA Starter-kit — a production Next.js 16 boilerplate with Supabase, Lemon Squeezy, and installable PWA defaults. You can follow it manually or start from the live demo and the packaged ZIP.
Why Next.js 16 + Supabase for indie SaaS
Next.js 16 (App Router) gives you server components, route handlers, and edge-friendly middleware (proxy.ts in this kit) for session-aware redirects. Supabase covers Postgres, Row Level Security (RLS), and auth with cookie-based SSR via @supabase/ssr.
Together they solve the hardest early-stage problems:
- Protected routes without leaking client-only session state
- Per-user data isolation via RLS policies
- Server-side checkout and webhook handlers in the same repo
Step 1 — Create the Supabase project
- Create a project at supabase.com.
- Copy Project URL and the publishable (anon) key from Settings → API.
- Apply SQL migrations for
profiles,billing_subscriptions, and RLS (included in the starter-kitsupabase/migrations/folder). - Enable email auth (or OAuth providers) under Authentication → Providers.
Set these in .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...
SUPABASE_SERVICE_ROLE_KEY=eyJ... # server-only, never expose to client
Step 2 — Wire Supabase Auth SSR in Next.js
The critical pattern: one browser client (createBrowserClient) and one server client (createServerClient) that both read/write cookies through Next's async cookies() API.
On the edge, proxy.ts (Next.js 16 middleware convention) should:
- Refresh the Supabase session on every navigation
- Redirect unauthenticated users away from
/dashboard,/billing, etc. - Redirect authenticated users away from
/loginand/register
Without this, you get "logged in on the client but blocked on the server" bugs — the #1 support ticket for Supabase + Next.js starters.
Step 3 — Add Lemon Squeezy for digital products
For downloadable products (ZIPs, templates, kits), Lemon Squeezy handles tax, checkout UI, and webhooks without building a cart from scratch.
Minimum setup:
- Create a store and product variants in Lemon Squeezy
- Map each catalog product to
LEMON_SQUEEZY_VARIANT_*env vars - Point webhooks to
https://your-domain.com/api/webhooks/lemon-squeezy - On
order_created, upsertbilling_subscriptionswithaccess_granted: true - Serve signed download URLs from
/api/delivery/link(HMAC withDELIVERY_SIGNING_SECRET)
Test with card 4242 4242 4242 4242 in Lemon Test mode before going live.
Step 4 — Deploy on Vercel
- Push your repo to GitHub and import in Vercel
- Set Node.js 20 or 22 (matches
enginesinpackage.json) - Add all env vars from
.env.example— especiallyNEXT_PUBLIC_SITE_URL(canonical prod URL, no trailing slash) - Deploy and verify:
/api/healthreturns OK- Login → dashboard works in a private window
- Test purchase →
/billingshows active access → ZIP download works
Optional: set NEXT_PUBLIC_CATALOG_STARTER_ONLY=true for an English storefront aimed at international indie hackers (how vibe-pwa-starter.vercel.app runs in production).
Step 5 — PWA basics (credibility, not vanity)
A installable PWA won't replace SEO, but it signals polish:
manifest.jsonwith icons and theme colors- Service worker with offline fallback (
/offline) - Lighthouse installability checks
The starter-kit ships these files pre-wired — customize icons in public/icons/ before launch.
Common pitfalls (and fixes)
| Symptom | Likely cause |
| ------------------------------- | ------------------------------------------------------------------------- |
| Stuck on /login after sign-in | router.refresh() racing with router.push() — use soft navigation only |
| Webhook 401 | Wrong LEMON_SQUEEZY_WEBHOOK_SECRET |
| Download 403 after payment | plan_code mismatch between webhook and catalog product id |
| Legal pages show orange warning | Missing NEXT_PUBLIC_LEGAL_* vars on Vercel |
Skip the boilerplate?
If you want the full stack pre-integrated — auth, protected routes, billing UI, delivery API, PWA, EN marketing mode — the Vibe PWA Starter-kit packages everything above into a ZIP you can deploy in an evening.
- Live demo: /demo
- Product page: /products/vibe-pwa-starter-kit
- Stack: Next.js 16 · Supabase · Lemon Squeezy · Tailwind · shadcn/ui
No fake metrics, no inflated feature lists — just the codebase that powers the marketplace itself.
Passer du papier au système
Ce guide pose le diagnostic ; le pack correspondant livre les fichiers, prompts et checklists pour exécuter sans friction.