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.
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.
npm install @clerk/nextjs
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
export default function RootLayout({ children }) {
return (
<ClerkProvider>
<html><body>{children}</body></html>
</ClerkProvider>
)
}
// 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
npm install @supabase/supabase-js @supabase/ssr drizzle-orm postgres
npm install -D drizzle-kit
// 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.
// 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
npm install resend @react-email/components
// 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>
)
}
// 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
// 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.