What you're looking at

This is a free preview of the Startup Boilerplate Bundle—a production-ready SaaS starter kit for technical founders. Below you'll find excerpts from the README, the project directory tree, and a real NextAuth.js configuration snippet.

The full bundle includes 9 deliverables covering everything you need to ship your first SaaS product:

  • Authentication system (OAuth + email)
  • Stripe payments & subscriptions
  • 60+ polished UI components
  • Database schema & migrations
  • API route patterns & middleware
  • Email templates (transactional)
  • Admin dashboard scaffolding
  • Testing setup (unit + E2E)
  • Deploy configs (Vercel & Docker)
1

README

README.md

Project Overview

The Startup Boilerplate Bundle is a full-stack SaaS starter built with Next.js 14 (App Router). It gives technical founders a production-grade codebase so you can skip months of scaffolding and start building your actual product on day one.

Authentication, payments, database models, UI components, and deployment pipelines are pre-wired and tested. Every file is documented with inline comments explaining the why behind each decision.

Tech Stack

Next.js 14 NextAuth.js v5 Prisma ORM Stripe SDK Tailwind CSS TypeScript PostgreSQL Zod

Quick Start

  1. Clone the repo and install dependencies with pnpm install
  2. Copy .env.example to .env.local and fill in your API keys
  3. Run pnpm db:push to initialize the database schema
  4. Run pnpm dev to start the development server on localhost:3000
  5. Visit /api/auth/signin to test OAuth sign-in
terminal
git clone https://github.com/your-org/saas-boilerplate.git
cd saas-boilerplate
pnpm install
cp .env.example .env.local
pnpm db:push
pnpm dev
2

Project Structure

directory tree

A clean, modular layout designed for scalability. Every directory has a single responsibility.

saas-boilerplate/
├── app/
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   ├── register/page.tsx
│   │   └── layout.tsx
│   ├── (dashboard)/
│   │   ├── overview/page.tsx
│   │   ├── settings/page.tsx
│   │   ├── billing/page.tsx
│   │   └── layout.tsx
│   ├── api/
│   │   ├── auth/[...nextauth]/route.ts
│   │   ├── webhooks/stripe/route.ts
│   │   └── trpc/[trpc]/route.ts
│   ├── layout.tsx
│   ├── page.tsx
│   └── globals.css
├── components/
│   ├── ui/              # 60+ reusable UI components
│   ├── forms/           # Form components with Zod validation
│   ├── layouts/         # Page layout wrappers
│   └── providers/       # Context providers (theme, auth, etc.)
├── lib/
│   ├── auth.ts          # NextAuth config (shown below)
│   ├── prisma.ts        # Prisma client singleton
│   ├── stripe.ts        # Stripe client + helpers
│   └── utils.ts         # Shared utility functions
├── prisma/
│   ├── schema.prisma    # Database models
│   └── migrations/      # SQL migration history
├── emails/              # React Email templates
├── tests/
│   ├── unit/            # Vitest unit tests
│   └── e2e/             # Playwright E2E tests
├── .env.example
├── docker-compose.yml
├── tailwind.config.ts
├── tsconfig.json
└── package.json
3

Auth Setup

lib/auth.ts

NextAuth.js v5 configuration with Google and GitHub OAuth providers, Prisma session adapter, and JWT callbacks for role-based access control.

import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import Google from "next-auth/providers/google"
import GitHub from "next-auth/providers/github"
import { prisma } from "@/lib/prisma"

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },

  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      allowDangerousEmailAccountLinking: true,
    }),
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],

  callbacks: {
    // Attach user ID and role to the JWT token
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
        // Fetch role from database on first sign-in
        const dbUser = await prisma.user.findUnique({
          where: { id: user.id },
          select: { role: true },
        })
        token.role = dbUser?.role ?? "user"
      }
      return token
    },

    // Expose user ID and role in client-side session
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string
        session.user.role = token.role as string
      }
      return session
    },

    // Redirect to dashboard after successful sign-in
    async redirect({ url, baseUrl }) {
      if (url.startsWith(baseUrl)) return url
      if (url.startsWith("/")) return `${baseUrl}${url}`
      return `${baseUrl}/dashboard/overview`
    },
  },

  pages: {
    signIn: "/login",
    error: "/login?error=true",
  },
})

Ready to skip the scaffolding?

Get the full Startup Boilerplate Bundle with all 9 deliverables—auth, payments, 60+ UI components, database models, email templates, tests, and deploy configs.

Get the full Startup Boilerplate Bundle — $79 One-time purchase · Lifetime updates · MIT license