Ship your SaaS this weekend. Production-ready Next.js starter with auth, Stripe billing, dashboard, admin panel, and transactional email.
- Auth: Email/password + Google OAuth via Better Auth. Session management, middleware protection.
- Stripe billing: Subscriptions, checkout, customer portal, webhook handler with idempotency, dunning flow.
- Dashboard: Dark-mode sidebar layout, responsive, ready for your product.
- Admin panel: User management, stats (total users, paying, past_due, trial).
- Billing settings: Current plan, manage billing button, invoice history with PDF links.
- Dunning: In-app banner for failed payments, dunning email templates.
- Landing page: Hero, features grid, pricing table, FAQ, footer.
- Email: Resend integration with welcome, trial-ending, payment-failed, and recovery templates. Falls back to console logging if no API key set.
- Feature gating:
can(plan, feature)andisWithinLimit(plan, key, current)helpers.
| Layer | Tech |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| Auth | Better Auth |
| Database | Prisma + SQLite (dev) / Postgres (prod) |
| Payments | Stripe |
| Resend | |
| Deploy | Vercel (one-click) |
# 1. Clone
git clone <your-repo-url> my-saas
cd my-saas
# 2. Install
npm install
# 3. Set up environment
cp .env.example .env.local
# Fill in your keys (see .env.example for details)
# 4. Set up database
npx prisma db push
npx prisma generate
# 5. Run
npm run dev
# Open http://localhost:3000- Create a free account at https://stripe.com
- Copy your test secret key from Dashboard → Developers → API keys
- Create Products and Prices in Dashboard (or use the seed script):
npx tsx scripts/stripe-seed.ts
- Copy the printed price IDs to
.env.local - For webhooks, install Stripe CLI:
stripe listen --forward-to localhost:3000/api/stripe/webhook # Copy the whsec_... to .env.local as STRIPE_WEBHOOK_SECRET
src/
├── app/
│ ├── page.tsx # Landing page
│ ├── login/page.tsx # Login
│ ├── signup/page.tsx # Signup
│ ├── dashboard/
│ │ ├── layout.tsx # Sidebar layout
│ │ └── page.tsx # Dashboard
│ ├── settings/
│ │ ├── page.tsx # Profile settings
│ │ └── billing/page.tsx # Billing + invoices
│ ├── admin/page.tsx # Admin panel
│ ├── pricing/page.tsx # Pricing page (post-login)
│ ├── billing/success/page.tsx # Post-checkout confirmation
│ └── api/
│ ├── auth/[...all]/ # Better Auth handler
│ └── stripe/
│ ├── webhook/route.ts # Webhook handler
│ ├── checkout/route.ts# Create checkout session
│ └── portal/route.ts # Customer portal redirect
├── components/
│ ├── sidebar.tsx # Dashboard sidebar
│ └── dunning-banner.tsx # Failed payment banner
└── lib/
├── auth.ts # Better Auth config
├── auth-client.ts # Client-side auth hooks
├── auth-server.ts # Server-side getSession()
├── db.ts # Prisma client
├── email.ts # Resend + email templates
└── stripe/
├── plans.ts # Pricing config (single source of truth)
├── can.ts # Feature gating helpers
└── checkout.ts # Checkout session creation
- Edit
ShipKitinsrc/components/sidebar.tsxandsrc/app/page.tsx - Update
metadatainsrc/app/layout.tsx
- Edit
src/lib/stripe/plans.ts— all UI reads from this config - Run
scripts/stripe-seed.tsto create matching Products/Prices in Stripe
- Create files in
src/app/dashboard/— they inherit the sidebar layout - Add links to
src/components/sidebar.tsx
- Change
DATABASE_URLin.env.localto a Postgres connection string - Change
provider = "sqlite"toprovider = "postgresql"inprisma/schema.prisma - Run
npx prisma db push
- Push to GitHub
- Import in Vercel
- Add all env vars from
.env.example - Deploy
For the webhook, add the production URL in Stripe Dashboard → Developers → Webhooks → Add endpoint:
- URL:
https://yourdomain.com/api/stripe/webhook - Events:
checkout.session.completed,customer.subscription.*,invoice.*,charge.dispute.created,charge.refunded
Single-developer license. See LICENSE.md.