Skip to main content

How to Deploy Your SaaS Boilerplate to Production 2026

·StarterPick Team
deploymentvercelproductionguide2026

TL;DR

Vercel + Neon + Resend + Stripe (live mode) is the default production stack for Next.js SaaS in 2026. Deployment itself takes 1-2 hours. The checklist below covers the steps most developers forget — especially environment variables, database migrations, domain setup, and Stripe webhook registration.


The Production Stack

LayerServiceCost
HostingVercel Pro$20/mo
DatabaseNeon (Serverless Postgres)$19/mo
EmailResend$0 → $20/mo
PaymentsStripe2.9% + 30¢
AuthNextAuth (self-hosted)Free
Error trackingSentryFree tier
AnalyticsPostHogFree tier

Step 1: Database (Neon)

# 1. Create production database at neon.tech
# 2. Copy connection string

# 3. Run migrations against production
DATABASE_URL="postgresql://..." npx prisma migrate deploy

# 4. Verify migrations
DATABASE_URL="postgresql://..." npx prisma db push --preview-feature

Neon branching for zero-downtime migrations:

# Before deploying a breaking migration:
# 1. Create a branch: main → migration-branch
# 2. Apply migration to branch
# 3. Deploy app pointing to branch
# 4. Verify, then promote branch to main

Step 2: Vercel Project Setup

# Install Vercel CLI
npm i -g vercel

# Link and deploy
vercel --prod

# Or connect via GitHub:
# vercel.com → New Project → Import from GitHub

Build settings for Next.js:

// vercel.json (optional — usually auto-detected)
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "installCommand": "npm ci"
}

If using Prisma, generate client during build:

// package.json
{
  "scripts": {
    "build": "prisma generate && next build",
    "postinstall": "prisma generate"
  }
}

Step 3: Environment Variables

Set in Vercel dashboard → Project Settings → Environment Variables. Select Production environment.

# Core
NEXTAUTH_URL=https://yoursaas.com
NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>

# Database
DATABASE_URL=postgresql://...

# Stripe (LIVE keys, not test)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=  # Set after step 4

# Email
RESEND_API_KEY=re_...
EMAIL_FROM=YourSaaS <noreply@yoursaas.com>

# OAuth (if using)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Error tracking
SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=

Critical: Never use test Stripe keys in production. sk_test_ keys will silently fail on real payments.


Step 4: Stripe Webhook Registration

# Register your production webhook endpoint in Stripe dashboard:
# Stripe Dashboard → Developers → Webhooks → Add endpoint
# URL: https://yoursaas.com/api/stripe/webhook

# Or via CLI:
stripe listen --forward-to https://yoursaas.com/api/stripe/webhook

Events to subscribe to (minimum):

checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
invoice.payment_succeeded
invoice.payment_failed

Verify your webhook handler checks the signature:

// app/api/stripe/webhook/route.ts
const sig = req.headers.get('stripe-signature')!;
const body = await req.text();

let event: Stripe.Event;
try {
  event = stripe.webhooks.constructEvent(
    body,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET!
  );
} catch (err) {
  return new Response('Webhook signature verification failed', { status: 400 });
}

Step 5: Custom Domain

# In Vercel dashboard → Project → Domains → Add
# Add: yoursaas.com and www.yoursaas.com

# DNS records to add at your registrar:
# A record:     @ → 76.76.21.21 (Vercel IP)
# CNAME record: www → cname.vercel-dns.com

Update environment variables after domain is live:

NEXTAUTH_URL=https://yoursaas.com  # Was localhost:3000

Step 6: Email Domain Verification (Resend)

# In Resend dashboard → Domains → Add Domain
# Add DNS records:
# TXT record: resend._domainkey.yoursaas.com → (DKIM key from Resend)
# TXT record: yoursaas.com → v=spf1 include:amazonses.com ~all

# Verify in Resend dashboard (takes 10-30 min to propagate)

Pre-Launch Verification Checklist

# Auth
[ ] Sign up with email works → welcome email received
[ ] Google OAuth login works
[ ] Sign out works
[ ] Password reset email received

# Payments
[ ] Checkout page loads with correct prices
[ ] Test purchase with: 4242 4242 4242 4242 (Stripe test card)
[ ] Switch to LIVE mode and verify a real $1 test charge
[ ] Webhook fires and subscription created in DB
[ ] Billing portal loads via Manage Billing button

# Core Features
[ ] Dashboard loads after login
[ ] Feature gating works (Pro features blocked for free users)
[ ] Settings page saves

# Error Handling
[ ] 404 page renders (visit /does-not-exist)
[ ] Error boundary works (no blank screens)
[ ] Sentry receives test event: Sentry.captureException(new Error("test"))

# Performance
[ ] Lighthouse score ≥ 90 on /
[ ] Core Web Vitals: LCP < 2.5s
[ ] No console errors on load

Common Deployment Failures

ErrorCauseFix
NEXTAUTH_URL mismatchEnv var not updated for production domainSet to https://yoursaas.com
Prisma migration errorsUsing migrate dev instead of migrate deployUse prisma migrate deploy in build
OAuth redirect mismatchGoogle/GitHub OAuth not updatedAdd production URL to OAuth app
Stripe webhooks 401Wrong webhook secretRegenerate in Stripe and update env var
Email from Resend sandboxDomain not verifiedComplete Resend DNS verification

Compare boilerplates and their deployment complexity on StarterPick.

Check out this boilerplate

View ShipFast on StarterPick →

Comments