Skip to content

Repository files navigation

Next SaaS Starter

A production-ready SaaS boilerplate built with Next.js 16, React 19, TypeScript, Prisma, Stripe, and NextAuth.js. Launch your SaaS product in days, not months.

Next SaaS Starter Banner

Next SaaS Starter Banner

Next SaaS Starter Banner


Table of Contents


Features

Category What's Included
Auth Email/password login, Google OAuth, GitHub OAuth, forgot password, session management
Payments Stripe subscriptions, plan upgrades/downgrades, proration, invoices, promo codes
Dashboard Stats overview, revenue chart, activity feed, sidebar navigation
Billing Plan management, usage meter, payment method, billing email, invoice history with PDF
Settings Profile update, password change, account deletion
API Keys Generate, copy, delete API keys (max 10 per user)
Marketing Landing page, pricing, blog, changelog, FAQ, testimonials
UI Dark/light theme, responsive design, Radix UI + Tailwind CSS
Email Welcome emails, password reset via Resend
DevOps CI/CD with GitHub Actions, CodeQL security scanning
Type Safety End-to-end TypeScript, Zod validation, Prisma types

Screenshots

Login / Register

Login Register
Login Register

Dashboard

Dashboard

Billing

Billing

Settings

Settings

API Keys

API Keys

Pricing

Pricing

Dark Mode

Light Dark
Light Mode Dark Mode

Tech Stack

Category Technology Version
Framework Next.js (App Router) 16.2
Language TypeScript 5.x
UI Components Radix UI + shadcn/ui Latest
Styling Tailwind CSS v4
Database PostgreSQL + Prisma ORM 6.19
Auth NextAuth.js (Auth.js) v5 beta
Payments Stripe 17.x
Email Resend 6.x
Forms React Hook Form + Zod Latest
Icons Lucide React Latest
Toasts Sonner Latest
Theme next-themes Latest

Project Structure

next-saas-starter/
├── .github/                        # CI/CD & community templates
│   ├── workflows/
│   │   ├── ci.yml              # Lint, type-check, build
│   │   └── codeql.yml          # Security scanning
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── FUNDING.yml
├── docs/                           # Per-page deep documentation
│   ├── auth-guide.md               # Login, Register, OAuth, sessions
│   ├── billing-guide.md            # Stripe, plans, invoices, usage
│   ├── dashboard-guide.md          # Stats, charts, activity, navigation
│   ├── landing-guide.md            # Hero, features, pricing, blog, footer
│   └── settings-api-keys-guide.md  # Profile, password, API keys
├── prisma/
│   ├── schema.prisma               # Database models (User, Subscription, ApiKey, etc.)
│   └── seed.ts                     # Demo users seeder (admin + user)
├── screenshots/                    # README screenshots (add your own)
├── public/                         # Static assets (favicon, og-image, SVGs)
├── src/
│   ├── app/
│   │   ├── (auth)/                 # Auth pages (no navbar/sidebar)
│   │   │   ├── login/page.tsx      # Sign in + demo autofill buttons
│   │   │   ├── register/page.tsx   # Create account
│   │   │   └── forgot-password/page.tsx
│   │   ├── (dashboard)/            # Dashboard pages (sidebar + topbar)
│   │   │   ├── layout.tsx          # Shared dashboard layout
│   │   │   ├── dashboard/page.tsx  # Overview: stats, chart, activity
│   │   │   ├── billing/page.tsx    # Plans, invoices, usage, payment, coupons
│   │   │   ├── settings/page.tsx   # Profile, password, connected accounts, delete
│   │   │   └── api-keys/page.tsx   # Create, copy, revoke API keys
│   │   ├── (marketing)/            # Public pages (navbar + footer)
│   │   │   ├── layout.tsx          # Shared marketing layout
│   │   │   ├── page.tsx            # Landing (hero, features, pricing, CTA)
│   │   │   ├── pricing/page.tsx    # Pricing page
│   │   │   ├── blog/page.tsx       # Blog listing
│   │   │   ├── blog/[slug]/page.tsx # Blog post detail
│   │   │   └── changelog/page.tsx  # Version changelog
│   │   ├── api/
│   │   │   ├── auth/
│   │   │   │   ├── [...nextauth]/route.ts  # NextAuth handler
│   │   │   │   └── register/route.ts       # User registration
│   │   │   ├── stripe/
│   │   │   │   └── webhook/route.ts        # Stripe webhook handler
│   │   │   └── v1/                         # REST API endpoints
│   │   │       ├── api-keys/route.ts       # GET list, POST create
│   │   │       ├── api-keys/[id]/route.ts  # DELETE revoke
│   │   │       ├── billing-email/route.ts  # GET/PUT billing email
│   │   │       ├── checkout/route.ts       # POST create/switch plan
│   │   │       ├── coupon/route.ts         # POST validate promo code
│   │   │       ├── invoices/route.ts       # GET invoice history
│   │   │       ├── payment-method/route.ts # GET card / POST update
│   │   │       ├── portal/route.ts         # POST Stripe portal session
│   │   │       ├── subscription/route.ts   # GET current subscription
│   │   │       ├── subscription/cancel/route.ts # POST cancel at period end
│   │   │       ├── usage/route.ts          # GET API usage stats
│   │   │       └── user/
│   │   │           ├── route.ts            # DELETE account
│   │   │           ├── profile/route.ts    # PATCH update name/email
│   │   │           └── password/route.ts   # PATCH change password
│   │   ├── layout.tsx              # Root layout (providers, metadata, fonts)
│   │   ├── globals.css             # Tailwind + CSS variables (themes)
│   │   ├── error.tsx               # Global error page
│   │   ├── not-found.tsx           # 404 page
│   │   ├── robots.ts               # SEO robots.txt
│   │   └── sitemap.ts              # SEO sitemap
│   ├── components/
│   │   ├── auth/
│   │   │   ├── login-form.tsx      # Login form + demo autofill
│   │   │   ├── register-form.tsx   # Register form with validation
│   │   │   └── social-buttons.tsx  # Google + GitHub OAuth buttons
│   │   ├── dashboard/
│   │   │   ├── sidebar.tsx         # Desktop sidebar navigation
│   │   │   ├── topbar.tsx          # Header + avatar dropdown + mobile nav
│   │   │   ├── stats-card.tsx      # Metric card with trend indicator
│   │   │   └── recent-activity.tsx # Activity feed with color-coded icons
│   │   ├── marketing/
│   │   │   ├── navbar.tsx          # Auth-aware marketing navbar
│   │   │   ├── hero.tsx            # Hero section with mockup
│   │   │   ├── features.tsx        # 8-feature grid
│   │   │   ├── pricing-cards.tsx   # Plan cards with monthly/yearly toggle
│   │   │   ├── testimonials.tsx    # 6 testimonial cards
│   │   │   ├── faq.tsx             # Accordion FAQ section
│   │   │   ├── cta.tsx             # Call-to-action section
│   │   │   └── footer.tsx          # Footer with links + credit
│   │   ├── shared/
│   │   │   ├── theme-toggle.tsx    # Dark/light mode switch
│   │   │   ├── loading.tsx         # Loading spinner
│   │   │   └── error-boundary.tsx  # Error boundary wrapper
│   │   ├── ui/                     # shadcn/ui primitives
│   │   │   ├── accordion.tsx
│   │   │   ├── avatar.tsx
│   │   │   ├── badge.tsx
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── separator.tsx
│   │   │   ├── skeleton.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── table.tsx
│   │   │   └── tabs.tsx
│   │   └── providers.tsx           # Session + Theme providers
│   ├── config/
│   │   ├── site.ts                 # App name, description, URLs, creator
│   │   ├── plans.ts                # Pricing plans (Free, Pro, Enterprise)
│   │   └── nav.ts                  # Marketing + dashboard nav items
│   ├── hooks/
│   │   ├── use-current-user.ts     # Auth state hook
│   │   └── use-subscription.ts     # Subscription state hook
│   ├── lib/
│   │   ├── auth.ts                 # NextAuth v5 config + providers
│   │   ├── db.ts                   # Prisma client singleton
│   │   ├── stripe.ts               # Stripe helpers (checkout, portal)
│   │   ├── email.ts                # Resend email templates
│   │   ├── blog.ts                 # Blog posts content
│   │   ├── utils.ts                # Utility functions (cn, formatDate)
│   │   └── validations.ts          # Zod schemas (login, register, etc.)
│   ├── types/
│   │   ├── index.ts                # Shared TypeScript types
│   │   └── next-auth.d.ts          # NextAuth type augmentation
│   └── proxy.ts                    # Route protection (auth redirect)
├── .env.example                    # Environment variable template
├── CONTRIBUTING.md                 # Contribution guidelines
├── CODE_OF_CONDUCT.md              # Community code of conduct
├── SECURITY.md                     # Security policy
├── CHANGELOG.md                    # Version history
└── LICENSE                         # MIT License

Quick Start (5 Minutes)

# 1. Clone
git clone https://github.com/ali-raza-arain/next-saas-starter.git
cd next-saas-starter

# 2. Install
npm install

# 3. Environment
cp .env.example .env.local
# Fill in your keys (see Environment Variables section below)

# 4. Database
npm run db:push     # Create tables
npm run db:seed     # Create demo users

# 5. Run
npm run dev         # http://localhost:3000

Demo accounts (after seeding):

Email Password Role
demo@example.com password123 User
admin@example.com password123 Admin

Environment Variables

Create .env.local in the project root:

# ============================================
# REQUIRED
# ============================================

# Database (PostgreSQL)
DATABASE_URL="postgresql://user:password@host:5432/dbname?sslmode=require"

# Auth (generate: openssl rand -base64 32)
AUTH_SECRET="your-random-secret-here"
NEXT_PUBLIC_APP_URL="http://localhost:3000"

# ============================================
# STRIPE (Required for payments)
# ============================================

# Get from: https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

# Get from: stripe listen (CLI) or Webhook settings
STRIPE_WEBHOOK_SECRET="whsec_..."

# Get from: Stripe > Products > [Plan] > Pricing > Price ID
# IMPORTANT: Must start with "price_" (NOT "prod_")
NEXT_PUBLIC_STRIPE_PRO_MONTHLY_PRICE_ID="price_..."
NEXT_PUBLIC_STRIPE_PRO_YEARLY_PRICE_ID="price_..."
NEXT_PUBLIC_STRIPE_ENTERPRISE_MONTHLY_PRICE_ID="price_..."
NEXT_PUBLIC_STRIPE_ENTERPRISE_YEARLY_PRICE_ID="price_..."

# ============================================
# OAUTH (Optional)
# ============================================

# Google: https://console.cloud.google.com > APIs & Services > Credentials
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""

# GitHub: https://github.com/settings/developers > OAuth Apps
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""

# ============================================
# EMAIL (Optional)
# ============================================

# Resend: https://resend.com > API Keys
RESEND_API_KEY="re_..."
EMAIL_FROM="onboarding@yourdomain.com"

Database Setup (Neon / PostgreSQL)

Option A: Neon (Free Cloud — Recommended)

  1. Go to https://neon.tech and sign up (GitHub login works)
  2. Click "New Project"
  3. Set project name (e.g. saas-starter), choose nearest region
  4. Click Create Project
  5. Copy the connection string:
    postgresql://neondb_owner:abc123@ep-xxx.region.aws.neon.tech/neondb?sslmode=require
    
  6. Paste into .env.local as DATABASE_URL

Option B: Local PostgreSQL

sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql
sudo -u postgres psql -c "CREATE USER myuser WITH PASSWORD 'mypass';"
sudo -u postgres psql -c "CREATE DATABASE saas_starter OWNER myuser;"

Then set: DATABASE_URL=postgresql://myuser:mypass@localhost:5432/saas_starter

Initialize Database

npm run db:push       # Create all tables from schema
npm run db:seed       # Insert demo users
npm run db:studio     # Visual database browser (localhost:5555)

Useful Database Commands

Command Purpose
npm run db:push Sync schema to database
npm run db:generate Regenerate Prisma client (after schema changes)
npm run db:seed Seed demo data
npm run db:studio Open Prisma Studio GUI

Stripe Integration (Payments)

Step 1: Create Account

  1. Go to https://dashboard.stripe.com/register
  2. Sign up with email
  3. Toggle "Test Mode" ON (top-right)

Step 2: Get API Keys

  1. Go to Developers > API Keys
  2. Copy Publishable key (pk_test_...) and Secret key (sk_test_...)

Stripe API Keys

Step 3: Create Products

  1. Go to Products > Add Product
  2. Pro Plan:
    • Name: Pro Plan
    • Add price: $12.00 > Recurring > Monthly
    • (Optional) Add price: $120.00 > Recurring > Yearly
    • Save > Copy the Price ID (starts with price_)
  3. Enterprise Plan:
    • Name: Enterprise Plan
    • Add price: $49.00 > Recurring > Monthly
    • (Optional) Add price: $490.00 > Recurring > Yearly
    • Save > Copy the Price ID

Stripe Price ID

Common Mistake: Copy the Price ID (price_xxx), NOT the Product ID (prod_xxx). The Price ID is found under the product's Pricing section.

Step 4: Webhook (Local Dev)

# Install Stripe CLI
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg
echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list
sudo apt update && sudo apt install stripe

# Login & forward webhooks
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook

Copy the whsec_... output to STRIPE_WEBHOOK_SECRET in .env.local.

Step 5: Create Promo Code (Optional)

  1. Products > Coupons > Create Coupon
  2. Set: 20% off, duration: 3 months
  3. Click coupon > Promotion Codes > Create
  4. Set code: LAUNCH20
  5. Users can enter this on the billing page

Test Cards

Card Number Result
4242 4242 4242 4242 Success
4000 0000 0000 0002 Declined
4000 0000 0000 9995 Insufficient funds
4000 0025 0000 3155 3D Secure

Use any future expiry (e.g. 12/30), any CVC (123), any ZIP (12345).


OAuth Setup (Google & GitHub)

Google OAuth

  1. Go to https://console.cloud.google.com
  2. Create a new project
  3. APIs & Services > Credentials > Create OAuth Client ID
  4. Application type: Web application
  5. Authorized redirect URI: http://localhost:3000/api/auth/callback/google
  6. Copy Client ID and Client Secret

GitHub OAuth

  1. Go to https://github.com/settings/developers
  2. Click New OAuth App
  3. Homepage URL: http://localhost:3000
  4. Callback URL: http://localhost:3000/api/auth/callback/github
  5. Copy Client ID and Client Secret

Email Setup (Resend)

  1. Go to https://resend.com and sign up
  2. API Keys > Create API Key > copy the key
  3. Add to .env.local:
    RESEND_API_KEY=re_xxxxx
    EMAIL_FROM=onboarding@yourdomain.com
  4. (Optional) Add & verify your domain in Resend for production

Email templates included:

  • Welcome email (on registration)
  • Password reset email (on forgot password)

Page-by-Page Guide

Landing Page

URL: /

Landing Page

Sections (top to bottom):

Section Description File
Navbar Logo, nav links, auth buttons (Sign In/Get Started or Dashboard if logged in) src/components/marketing/navbar.tsx
Hero Headline, description, 2 CTA buttons, animated dashboard mockup src/components/marketing/hero.tsx
Features 8 feature cards in grid src/components/marketing/features.tsx
Pricing 3-tier plan comparison with monthly/yearly toggle src/components/marketing/pricing-cards.tsx
Testimonials Customer quotes src/components/marketing/testimonials.tsx
FAQ 8 common questions in accordion src/components/marketing/faq.tsx
CTA Final call-to-action section src/components/marketing/cta.tsx
Footer Links, social icons, copyright src/components/marketing/footer.tsx

Buttons:

Button Logged Out Logged In
Navbar "Sign In" Visible > goes to /login Hidden
Navbar "Get Started" Visible > goes to /register Hidden
Navbar "Dashboard" Hidden Visible > goes to /dashboard
Hero "Get Started" Goes to /register Goes to /register
Hero "Star on GitHub" Opens GitHub repo Opens GitHub repo
Pricing "Upgrade to Pro" Goes to /register Goes to /register

Customization:

  • Edit site name/description: src/config/site.ts
  • Edit features: src/components/marketing/features.tsx
  • Edit FAQ: src/components/marketing/faq.tsx
  • Edit testimonials: src/components/marketing/testimonials.tsx

Pricing Page

URL: /pricing

Pricing Page

Elements:

Element Description
Monthly/Yearly toggle Switch between billing intervals, "Save 17%" badge
Free card $0, 4 included features, "Get Started" button
Pro card (highlighted) $12/mo or $120/yr, 6 features, "Upgrade to Pro" button, "Most Popular" badge
Enterprise card $49/mo or $490/yr, 8 features, "Contact Sales" button
FAQ section Same accordion as landing page

Customization: Edit src/config/plans.ts to change plan names, prices, features.


Blog & Changelog

Blog URL: /blog Blog Post URL: /blog/[slug] Changelog URL: /changelog

Blog Page

Blog posts included:

  1. "Introducing Next SaaS Starter"
  2. "Setting Up Stripe Subscriptions"
  3. "Migrating to NextAuth.js v5"

Blog Next Page

Customization: Edit src/lib/blog.ts to add/modify blog posts.

Changelog Page


Auth Pages

Login Page

URL: /login

Login Page

Element Description
Google button OAuth login via Google
GitHub button OAuth login via GitHub
Email input Email address field
Password input Password field
"Forgot password?" link Goes to /forgot-password
"Sign In" button Submits credentials
"Create an account" link Goes to /register

Error handling:

  • Wrong credentials: "Invalid email or password"
  • Empty fields: Zod validation messages

Register Page

URL: /register

Register Page

Element Description
Name input Min 2, max 50 characters
Email input Must be valid email
Password input Min 8 chars, 1 uppercase, 1 lowercase, 1 number
Confirm password Must match password
"Create Account" button Submits, redirects to login

Forgot Password

URL: /forgot-password

Forgot Password

Element Description
Email input Enter registered email
"Send Reset Link" button Sends reset email via Resend
Success state Shows confirmation message

Dashboard

URL: /dashboard (Protected — requires login)

Dashboard Page

Layout: Sidebar (left) + Topbar (top) + Content area

Element Description
Sidebar Logo, 4 nav links (Dashboard, Billing, API Keys, Settings), Sign Out button
Topbar Page title, theme toggle, user avatar dropdown
Stats Cards (4) Monthly Revenue, Total Users, Active Subscriptions, Churn Rate — with trend indicators
Revenue Chart 12-month bar chart
Recent Activity Activity feed with icons and relative timestamps

Sidebar Navigation:

Item Icon URL
Dashboard LayoutDashboard /dashboard
Billing CreditCard /billing
API Keys Key /api-keys
Settings Settings /settings

Topbar User Dropdown:

Item Action
Dashboard Go to /dashboard
Billing Go to /billing
Settings Go to /settings
Sign Out Confirmation dialog > signs out > redirects to /

Sign Out: Both sidebar and topbar show a confirmation dialog: "Are you sure you want to sign out?"


Billing Page

URL: /billing (Protected)

Billing Page Full

This is the most feature-rich page. For deep documentation, see docs/billing-guide.md.

Sections (top to bottom):

# Section Description
1 Current Plan Shows plan name, badge, cancel status, "Manage Billing" button
2 API Usage Meter Progress bar showing API requests used/limit, color-coded
3 Payment Method Card on file (brand, last 4, expiry), "Update Card" button
4 Billing Email Input to change invoice email (separate from account email)
5 Promo Code Input to apply coupon, shows discount details
6 Monthly/Yearly Toggle Switch billing interval, "Save 17%" badge
7 Plan Cards (3) Free, Pro, Enterprise with upgrade/downgrade/switch buttons
8 Invoice History Table with invoice #, date, description, period, amount, payment method, status, view/download

Plan Switching Logic:

From To What Happens
Free Pro/Enterprise Stripe Checkout opens (new payment)
Pro Enterprise Instant switch, Stripe prorates charge
Enterprise Pro Instant switch, Stripe gives credit
Any paid Free Cancel at period end (keeps access until billing period ends)

Buttons on each plan card:

User's Current Plan Free Card Pro Card Enterprise Card
Free "Current Plan" (disabled) "Upgrade to Pro" "Upgrade to Enterprise"
Pro "Downgrade to Free" "Current Plan" (disabled) "Switch to Enterprise"
Enterprise "Downgrade to Free" "Switch to Pro" "Current Plan" (disabled)
Canceling "Switching to Free on [date]" "Switch to Pro" "Switch to Enterprise"

Settings Page

URL: /settings (Protected)

Settings Page

Sections:

Section Fields Description
Profile Name, Email Update profile info
Password Current password, New password, Confirm Change password (requires current)
Danger Zone Delete Account button Permanently delete account (with confirmation dialog)

Password Requirements:

  • Minimum 8 characters
  • At least 1 uppercase letter
  • At least 1 lowercase letter
  • At least 1 number

API Keys Page

URL: /api-keys (Protected)

API Keys Page

Element Description
"Create API Key" button Opens dialog with name input
Key table Name, Key prefix (sk_...), Last used, Created date
Copy button Copies full key to clipboard (only shown once on creation)
Delete button Removes key with confirmation

Limits: Maximum 10 API keys per user.

Key Format: sk_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX


API Routes Reference

Authentication

Route Method Purpose
/api/auth/[...nextauth] GET/POST NextAuth handler (login, session, providers)
/api/auth/register POST Create new account (name, email, password, confirmPassword)

Subscription & Billing

Route Method Request Body Response
/api/v1/subscription GET { planId, status, currentPeriodEnd, cancelAtPeriodEnd }
/api/v1/checkout POST { priceId, promotionCodeId? } { url } or { switched: true, planId }
/api/v1/subscription/cancel POST { message, periodEnd }
/api/v1/portal POST { url } (Stripe customer portal)
/api/v1/invoices GET Invoice array with amounts, dates, PDF URLs
/api/v1/usage GET { used, limit, unlimited, percentage }
/api/v1/payment-method GET { paymentMethod: { brand, last4, expMonth, expYear } }
/api/v1/payment-method POST { url } (Stripe setup session)
/api/v1/billing-email GET { email }
/api/v1/billing-email PUT { email } { message, email }
/api/v1/coupon POST { code } { valid, promotionCodeId, discount, duration }

User Management

Route Method Request Body Response
/api/v1/user DELETE Deletes account
/api/v1/user/profile PATCH { name, email } Updated user
/api/v1/user/password PATCH { currentPassword, newPassword, confirmPassword } Success message

API Keys

Route Method Request Body Response
/api/v1/api-keys GET Array of keys (prefix only)
/api/v1/api-keys POST { name } { key } (full key, shown once)

Webhook

Route Method Purpose
/api/stripe/webhook POST Handles Stripe events (signature verified)

Database Schema

┌──────────────┐     ┌──────────────┐     ┌──────────────────┐
│    User      │────>│ Subscription │     │      ApiKey      │
│──────────────│     │──────────────│     │──────────────────│
│ id           │     │ id           │     │ id               │
│ name         │     │ userId (FK)  │     │ userId (FK)      │
│ email        │     │ stripeCustom │     │ name             │
│ password     │     │ stripeSub    │     │ keyHash          │
│ role         │     │ stripePriceId│     │ keyPrefix        │
│ image        │     │ planId       │     │ lastUsedAt       │
│ emailVerified│     │ status       │     │ createdAt        │
│ createdAt    │     │ cancelAt...  │     └──────────────────┘
│ updatedAt    │     │ apiRequests* │
└──────┬───────┘     │ createdAt    │
       │             │ updatedAt    │
       │             └──────────────┘
       │
  ┌────┴─────┐     ┌──────────────────┐
  │ Account  │     │ VerificationToken│
  │──────────│     │──────────────────│
  │ provider │     │ identifier       │
  │ provAccId│     │ token            │
  │ tokens...│     │ expires          │
  └──────────┘     └──────────────────┘

Models: User, Account (OAuth), Session, VerificationToken, Subscription, ApiKey

Subscription statuses: inactive, active, past_due, canceled

Plan IDs: free, pro, enterprise


Webhook Setup

The app listens for these Stripe webhook events:

Event Handler
checkout.session.completed Creates/updates subscription in DB
customer.subscription.updated Syncs plan changes, renewals
customer.subscription.deleted Sets plan to "free"
invoice.payment_succeeded Updates period end, status
invoice.payment_failed Sets status to "past_due"

Local: Run stripe listen --forward-to localhost:3000/api/stripe/webhook

Production: Add webhook endpoint in Stripe Dashboard with URL https://yourdomain.com/api/stripe/webhook


Authentication & Route Protection

Proxy (src/proxy.ts) protects routes:

Route Auth Required Behavior
/dashboard Yes Redirects to /login if not authenticated
/billing Yes Redirects to /login if not authenticated
/settings Yes Redirects to /login if not authenticated
/api-keys Yes Redirects to /login if not authenticated
/login No (redirect if authed) Redirects to /dashboard if already logged in
/register No (redirect if authed) Redirects to /dashboard if already logged in
/ No Public (navbar changes based on auth state)

Providers configured:

Provider Type Config
Credentials Email + Password bcrypt hashed, Zod validated
Google OAuth Requires GOOGLE_CLIENT_ID + SECRET
GitHub OAuth Requires GITHUB_CLIENT_ID + SECRET

Customization Guide

Change App Name & Branding

// src/config/site.ts
export const siteConfig = {
  name: "Your App Name",
  description: "Your app description",
  creator: "Your Name",
  links: {
    github: "https://github.com/you/your-repo",
    twitter: "https://twitter.com/you",
  },
};

Change Plans & Pricing

// src/config/plans.ts
{
  id: "pro",
  name: "Pro",
  price: { monthly: 19, yearly: 190 },
  stripePriceId: {
    monthly: process.env.NEXT_PUBLIC_STRIPE_PRO_MONTHLY_PRICE_ID ?? "",
    yearly: process.env.NEXT_PUBLIC_STRIPE_PRO_YEARLY_PRICE_ID ?? "",
  },
  features: [
    { text: "Your feature here", included: true },
  ],
}

Change Navigation

// src/config/nav.ts
export const dashboardNav = [
  { title: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
  { title: "Your Page", href: "/your-page", icon: YourIcon },
];

Add a New Dashboard Page

  1. Create src/app/(dashboard)/your-page/page.tsx
  2. Add nav item in src/config/nav.ts
  3. Page automatically gets sidebar + topbar + auth protection

Add a New Database Model

  1. Edit prisma/schema.prisma
  2. Run npm run db:generate then npm run db:push
  3. Use import { db } from "@/lib/db" to query

Add a New API Route

  1. Create src/app/api/v1/your-route/route.ts
  2. Use import { auth } from "@/lib/auth" for protection

Deployment

Deploy to Vercel

  1. Push code to GitHub
  2. Go to vercel.com > Import repo
  3. Add all environment variables from .env.local
  4. Change NEXT_PUBLIC_APP_URL to your domain
  5. Deploy

Deploy with Vercel

Post-Deployment Checklist

  • All env vars set in Vercel dashboard
  • NEXT_PUBLIC_APP_URL = your production domain
  • Stripe switched to Live Mode with live keys
  • Stripe webhook URL: https://yourdomain.com/api/stripe/webhook
  • OAuth callbacks updated: https://yourdomain.com/api/auth/callback/google etc.
  • public/robots.txt sitemap URL updated
  • Resend domain verified
  • Remove demo/seed users from production database
  • Test full flow: register > subscribe > dashboard

Testing Checklist

Code Quality

npm run lint          # 0 errors
npm run type-check    # 0 errors
npm run build         # Compiles successfully

Full User Flow

# Test URL Expected
1 Landing page loads / Hero, features, pricing visible
2 Register new account /register Account created, redirect to login
3 Login /login Redirect to dashboard
4 Dashboard loads /dashboard Stats, chart, activity visible
5 Update profile /settings Name/email saved
6 Change password /settings Password updated
7 Create API key /api-keys Key generated, shown once
8 Upgrade to Pro /billing Stripe checkout > payment > plan updated
9 Switch to Enterprise /billing Instant switch, no new payment
10 Apply promo code /billing Discount shown
11 Update card /billing Stripe setup page > card saved
12 Change billing email /billing Email updated
13 Downgrade to Free /billing Cancel at period end, access retained
14 View invoices /billing Real invoices with PDF download
15 Toggle dark/light mode Any page Theme switches
16 Sign out Dashboard Confirmation > redirected to home
17 Visit dashboard logged out /dashboard Redirected to login
18 Mobile responsive All pages Layout adapts, hamburger menu works

Troubleshooting

Problem Solution
"Can't reach database server" Check DATABASE_URL. For Neon, ensure ?sslmode=require is included
"Failed to create checkout session" Check STRIPE_SECRET_KEY and Price IDs (must start with price_, not prod_)
Plan shows "Free" after payment Webhook not running. Start stripe listen --forward-to localhost:3000/api/stripe/webhook
"Coming Soon" on plan button That plan's Price ID is empty in .env. Create price in Stripe and add it
OAuth not working Check Client ID/Secret. Verify callback URL: http://localhost:3000/api/auth/callback/google
Coupon "Invalid or expired" Must be a Promotion Code (not just a Coupon). Create in Stripe: Coupon > Promotion Codes
Hydration errors in console Expected with next-themes. suppressHydrationWarning is set — safe to ignore
Email not sending Check RESEND_API_KEY. Verify domain in Resend dashboard for production

All Scripts

Command Purpose
npm run dev Start dev server
npm run build Production build
npm run start Start production server
npm run lint Run ESLint
npm run lint:fix Auto-fix lint issues
npm run type-check TypeScript check
npm run format Prettier format
npm run db:generate Regenerate Prisma client
npm run db:push Push schema to database
npm run db:seed Seed demo data
npm run db:studio Visual database browser

Contributing

See CONTRIBUTING.md for development setup and PR guidelines.

License

MIT


Built with Next SaaS Starterhttps://github.com/ali-raza-arain/next-saas-starter

About

The free, open-source SaaS starter kit for Next.js. Authentication, payments, dashboard — everything you need to launch your SaaS.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages