All guides
Engineering14 min read

Building a Next.js SaaS Boilerplate: What to Include and What to Skip

A practical guide to building or choosing a Next.js SaaS boilerplate — covering auth, payments, multi-tenancy, email, and the features that waste your time.

NC

Nextcraft Agency

The Boilerplate Paradox

Every SaaS developer has started a new project, stared at an empty Next.js app, and thought: "I should build a solid boilerplate so I never have to do this setup again."

Then spent three weeks building the boilerplate instead of the product.

This guide covers what a Next.js SaaS boilerplate actually needs, what to skip, and when to buy one instead of building it.

What Every SaaS Needs Before Building the Product

There's a core set of infrastructure every SaaS requires before you can build the thing that makes money. The goal is to get through this as fast as possible:

Authentication: Users need to sign up, log in, reset passwords, and verify email. Expect OAuth (Google, GitHub at minimum).

Organisations/Teams: Most B2B SaaS needs multi-user organisations with roles (owner, admin, member).

Billing: Subscription management, Stripe integration, plan gating.

Onboarding: The flow from signup to "aha moment" — the number one lever on activation rate.

Email: Transactional email for auth events, billing events, and product notifications.

Basic settings: Profile, password change, notification preferences, org management.

None of this is your product. It's all overhead. Minimise the time spent on it.

The Core Boilerplate Stack

Authentication: Clerk

Building auth from scratch takes 2–4 weeks. Clerk takes 2–4 hours.

code
npm install @clerk/nextjs
code
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }) {
  return (
    <ClerkProvider>
      <html><body>{children}</body></html>
    </ClerkProvider>
  )
}
code
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)'])

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect()
})

Clerk handles email verification, OAuth, MFA, organisation management, and a pre-built user profile UI. Cost: free up to 10,000 MAU, then $25/month. Worth every cent at MVP stage.

Database: Supabase + Drizzle

code
npm install @supabase/supabase-js @supabase/ssr drizzle-orm postgres
npm install -D drizzle-kit
code
// lib/db.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'

const client = postgres(process.env.DATABASE_URL!)
export const db = drizzle(client, { schema })

Supabase provides managed Postgres with a dashboard, connection pooling, and Row Level Security. Drizzle provides type-safe queries and migrations.

Payments: Stripe

The only decision to make: Stripe Checkout (hosted page) or Stripe Elements (embedded UI).

Use Checkout for MVP — it's a single stripe.checkout.sessions.create() call and Stripe handles the UI, validation, and localization.

code
// app/api/checkout/route.ts
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  customer_email: user.email,
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: `${env.APP_URL}/dashboard?upgraded=true`,
  cancel_url: `${env.APP_URL}/pricing`,
  metadata: { userId: user.id },
})

Store stripeCustomerId on your user record. Listen to webhooks for checkout.session.completed and customer.subscription.deleted to update plan status.

Email: Resend + React Email

code
npm install resend @react-email/components
code
// emails/WelcomeEmail.tsx
import { Html, Button, Text } from '@react-email/components'

export function WelcomeEmail({ name, ctaUrl }: { name: string; ctaUrl: string }) {
  return (
    <Html>
      <Text>Hi {name}, welcome to the product.</Text>
      <Button href={ctaUrl}>Get started →</Button>
    </Html>
  )
}
code
// lib/email.ts
import { Resend } from 'resend'
import { WelcomeEmail } from '@/emails/WelcomeEmail'

const resend = new Resend(process.env.RESEND_API_KEY)

export async function sendWelcomeEmail(to: string, name: string) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to,
    subject: 'Welcome to the product',
    react: <WelcomeEmail name={name} ctaUrl="https://yourapp.com/dashboard" />,
  })
}

Schema

code
// lib/db/schema.ts
import { pgTable, text, uuid, timestamp, boolean } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id: text('id').primaryKey(), // Clerk user ID
  email: text('email').notNull().unique(),
  name: text('name'),
  stripeCustomerId: text('stripe_customer_id'),
  plan: text('plan').default('free'),
  createdAt: timestamp('created_at').defaultNow(),
})

export const organizations = pgTable('organizations', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: text('name').notNull(),
  slug: text('slug').notNull().unique(),
  stripeSubscriptionId: text('stripe_subscription_id'),
  plan: text('plan').default('trial'),
  createdAt: timestamp('created_at').defaultNow(),
})

export const memberships = pgTable('memberships', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: text('user_id').references(() => users.id),
  organizationId: uuid('organization_id').references(() => organizations.id),
  role: text('role').default('member'), // owner | admin | member
  createdAt: timestamp('created_at').defaultNow(),
})

What to Skip in Your Boilerplate

Admin dashboard: Log into your database directly at MVP stage. Build an admin UI when you have more than 50 customers and real operational needs.

Feature flags: Hardcode the flags. Implement a feature flag system when you have a QA environment and a release cadence that requires it.

Multi-region support: Deploy to one region. Add more when latency data tells you to.

Advanced logging pipeline: Structured logs to stdout + Vercel's log drain is sufficient. Build a proper observability stack after your first production incident reveals the gaps.

Automated onboarding email sequence: Send one email manually for the first 20 customers. Write the automation when you know which message converts.

When to Buy Instead of Build

Boilerplates worth buying (2026 picks):

  • Supastarter — Next.js, Supabase, Stripe, Clerk, i18n. Well-maintained, actively updated.
  • Shipfast — Opinionated, fast, includes blog and landing page. Good for solo founders.
  • LaunchFast — Solid multi-tenancy architecture.

Buy a boilerplate when: you've shipped production SaaS before and you know exactly what you need. Skip buying and build from scratch when: it's your first SaaS and you need to understand each piece.

The 2-Day Setup Checklist

Day 1:

  • Next.js project initialised
  • Clerk installed and auth middleware configured
  • Supabase project created, Drizzle connected
  • Core schema migrated to production database
  • Environment variables set up and validated
  • Deployed to Vercel with production env vars

Day 2:

  • Stripe products and prices created in dashboard
  • Checkout session Route Handler
  • Webhook handler for subscription events
  • Resend connected, welcome email sending
  • Basic dashboard page behind auth
  • Plan gating middleware or server-side check

After day 2, you have a working SaaS skeleton. Everything from day 3 onwards is the product.

Deepen your knowledge

Master your stack.

Explore more technical guides or start a direct conversation with our team.