A modern, production-ready Micro SaaS starter built with Next.js, tRPC, and Drizzle ORM. Get your SaaS up and running in minutes with authentication, payments, and a fully-configured monorepo architecture.
- Modern Stack - Next.js 16, React 19, TypeScript, TailwindCSS v4
- Authentication Built-in - Better Auth with email/password, email verification, password reset
- Payment Integration - Stripe subscriptions with free trials and customer portal
- Type-Safe APIs - End-to-end type safety with tRPC v11
- Database Ready - Drizzle ORM with PostgreSQL, automatic schema migrations
- Beautiful UI - 40+ shadcn/ui components with Radix UI primitives
- Monorepo Architecture - Turborepo for optimal build caching and task running
- Docker Support - Local PostgreSQL + pgAdmin setup included
- Developer Experience - Hot reload, type checking, linting, formatting
- Production Ready - Optimized for Vercel deployment with automatic migrations
- Email & password authentication
- Email verification on signup
- Password reset flow
- Session management (7-day sessions)
- Type-safe auth client and server APIs
- Protected routes and middleware ready
- Stripe integration via Better Auth plugin
- Multiple pricing tiers (Free, Starter, Pro)
- Monthly and annual billing
- 14-day free trials
- Stripe Customer Portal
- Subscription lifecycle webhooks
- Feature gating components and hooks
- Trial abuse prevention
- Complete monorepo setup with Turborepo
- Type-safe database queries with Drizzle ORM
- Drizzle Studio for database management
- Docker Compose for local development
- ESLint and Prettier configured
- TypeScript strict mode
- AI coding assistant ready (CLAUDE.md)
Accordion, Alert Dialog, Avatar, Badge, Button, Card, Checkbox, Collapsible, Command, Context Menu, Dialog, Dropdown Menu, Form, Input, Label, Menubar, Navigation Menu, Popover, Progress, Radio Group, Scroll Area, Select, Separator, Sheet, Skeleton, Slider, Switch, Table, Tabs, Textarea, Toast, Toggle, Tooltip, and more!
| Category | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript 5.9 |
| Styling | TailwindCSS v4 |
| UI Components | shadcn/ui + Radix UI |
| API Layer | tRPC v11 |
| Database | PostgreSQL (Drizzle ORM) |
| Authentication | Better Auth |
| Payments | Stripe |
| Resend | |
| Monorepo | Turborepo |
| Package Manager | pnpm |
| Containerization | Docker Compose |
micro-saas-boilerplate/
├── apps/
│ └── web/ # Next.js application
│ ├── app/ # App router pages
│ │ ├── (auth)/ # Auth pages (sign-in, sign-up, reset-password)
│ │ ├── dashboard/ # Dashboard with subscription management
│ │ ├── pricing/ # Pricing page
│ │ ├── payment/ # Payment success pages
│ │ └── api/ # API routes (tRPC, Better Auth)
│ ├── components/ # App-specific components
│ └── lib/ # App utilities (auth, stripe, trpc)
├── packages/
│ ├── api/ # tRPC API layer (@repo/api)
│ │ └── src/routers/ # API routers (post, stripe)
│ ├── db/ # Drizzle ORM + schemas (@repo/db)
│ │ ├── src/schema/ # Database schemas (auth, stripe, posts)
│ │ └── migrations/ # SQL migrations
│ ├── ui/ # Shared UI components (@repo/ui)
│ │ └── src/components/ # shadcn/ui components
│ ├── eslint-config/ # Shared ESLint configs
│ └── typescript-config/ # Shared TypeScript configs
├── docker-compose.yml # PostgreSQL + pgAdmin
├── CLAUDE.md # AI coding assistant guide
├── STRIPE_SETUP_GUIDE.md # Detailed Stripe setup
└── package.json # Root workspace config
- Node.js 18 or higher
- pnpm 9.0.0 or higher
- Docker (optional, for local database)
git clone https://github.com/washingtonserip/micro-saas-boilerplate.git
cd micro-saas-boilerplatepnpm install# Copy root env
cp .env.example .env
# Copy web app env
cp .env apps/web/.env.localnpx @better-auth/cli secretCopy the generated secret and add it to apps/web/.env.local:
BETTER_AUTH_SECRET=your_generated_secret_heredocker compose up -dThis starts:
- PostgreSQL on
localhost:5432 - pgAdmin on
localhost:5050(login:admin@admin.com/admin)
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/micro_saas_boilerplate" pnpm --filter @repo/db db:migratepnpm devThe app will be running at http://localhost:3000
- Visit http://localhost:3000
- Click "Sign Up" to create an account
- Check console logs for verification email (Resend not configured yet)
- Explore the dashboard at http://localhost:3000/dashboard
Note: Stripe and Resend are optional for initial development. See setup guides below to enable payments and emails.
This boilerplate uses Better Auth for authentication, providing:
- Email & password authentication
- Email verification on signup
- Password reset functionality
- Session management
- Type-safe auth APIs
- Automatic database table management
/sign-up- User registration/sign-in- User login/reset-password- Password reset
Client-side:
import { authClient } from "@/lib/auth-client";
// Sign up
await authClient.signUp.email({
email: "user@example.com",
password: "SecurePass123",
name: "John Doe",
});
// Sign in
await authClient.signIn.email({
email: "user@example.com",
password: "SecurePass123",
});
// Get current session
const session = await authClient.getSession();Server-side:
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers(),
});To enable email verification and password reset emails:
- Sign up for Resend (free tier available)
- Get your API key
- Add to
apps/web/.env.local:RESEND_API_KEY=re_xxxxxxxxxxxxx
Full Stripe subscription management is included via the Better Auth Stripe plugin.
- Subscription checkout sessions
- Customer portal for managing subscriptions
- Free trial support (14 days)
- Multiple pricing tiers
- Webhook handling (automatic)
- Feature gating components
- Subscription status tracking
See STRIPE_SETUP_GUIDE.md for detailed instructions.
Quick version:
- Create Stripe account and products
- Get API keys from Stripe Dashboard
- Add to
apps/web/.env.local:STRIPE_SECRET_KEY=sk_test_xxxxx STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx STRIPE_STARTER_MONTHLY_PRICE_ID=price_xxxxx STRIPE_PRO_MONTHLY_PRICE_ID=price_xxxxx
- Start Stripe webhook forwarding (for local dev):
stripe listen --forward-to http://localhost:3000/api/auth/stripe/webhook
import {
useHasAccess,
RequireSubscription,
} from "@/lib/stripe/subscription-guards";
// Guard a component by subscription tier
<RequireSubscription requiredPlan="starter">
<PremiumFeature />
</RequireSubscription>;
// Or check access programmatically
const { hasAccess, plan } = useHasAccess("pro");
if (hasAccess) {
// Show pro features
}This boilerplate includes example pages to demonstrate the full stack:
| Route | Description |
|---|---|
/ |
Home/landing page with navigation |
/landing-page |
Full marketing landing page example |
/sign-up |
User registration |
/sign-in |
User login |
/reset-password |
Password reset flow |
/dashboard |
Protected dashboard layout |
/dashboard/subscription |
Subscription management page |
/pricing |
Pricing tiers with Stripe checkout |
/payment/success |
Payment confirmation page |
/trpc-demo |
tRPC example with CRUD operations |
pnpm dev # Start all apps in development mode
pnpm build # Build all apps and packages
pnpm lint # Lint all packages
pnpm format # Format code with Prettier
pnpm check-types # Run TypeScript type checking# Generate migrations from schema changes
DATABASE_URL="<your_db_url>" pnpm --filter @repo/db db:generate
# Run pending migrations (production workflow)
DATABASE_URL="<your_db_url>" pnpm --filter @repo/db db:migrate
# Push schema directly to DB (dev only)
DATABASE_URL="<your_db_url>" pnpm --filter @repo/db db:push
# Open Drizzle Studio GUI (localhost:4983)
DATABASE_URL="<your_db_url>" pnpm --filter @repo/db db:studio
# Drop migrations
DATABASE_URL="<your_db_url>" pnpm --filter @repo/db db:droppnpm --filter web dev # Start Next.js dev server
pnpm --filter web build # Build for production
pnpm --filter web start # Start production servertRPC router and procedures with full type safety. Located in packages/api/.
Key Features:
- Type-safe API routes
- React Query integration
- Zod validation
- Database integration via Drizzle
Available Routers:
post- Example CRUD operationsstripe- Subscription and billing management
Database layer using Drizzle ORM with PostgreSQL.
Key Features:
- Type-safe database queries
- Automatic schema migrations
- Drizzle Studio support
- Connection pooling
- Lazy initialization (Proxy pattern)
Schemas:
auth.ts- Better Auth tables (managed automatically)stripe.ts- Stripe customer and subscription tables (managed automatically)posts.ts- Example application table
Shared component library based on shadcn/ui with 40+ components.
Import Pattern:
import { Button } from "@repo/ui/button";
import { Card, CardHeader, CardContent } from "@repo/ui/card";
import { Dialog } from "@repo/ui/dialog";All components are fully customizable and built with Radix UI primitives.
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/micro_saas_boilerplate" pnpm --filter @repo/db db:studioOpens a web interface at localhost:4983 for managing your database visually.
If using Docker Compose, pgAdmin is available at localhost:5050:
- Email:
admin@admin.com - Password:
admin
To connect to PostgreSQL in pgAdmin:
- Right-click "Servers" → "Register" → "Server"
- Name: Micro SaaS
- Host:
host.docker.internal(orpostgresif inside Docker network) - Port:
5432 - Username:
postgres - Password:
postgres
-
Database Schema - Define or modify in
packages/db/src/schema/// packages/db/src/schema/tasks.ts import { pgTable, serial, text, boolean } from "drizzle-orm/pg-core"; export const tasks = pgTable("tasks", { id: serial("id").primaryKey(), title: text("title").notNull(), completed: boolean("completed").default(false), });
-
Export Schema - Add to
packages/db/src/schema/index.tsexport * from "./tasks";
-
Generate Migration
DATABASE_URL="<url>" pnpm --filter @repo/db db:generate -
Apply Migration
DATABASE_URL="<url>" pnpm --filter @repo/db db:migrate -
Create tRPC Router - Add procedures in
packages/api/src/routers/task.tsimport { router, publicProcedure } from "../trpc"; import { tasks } from "@repo/db"; export const taskRouter = router({ getAll: publicProcedure.query(async ({ ctx }) => { return ctx.db.select().from(tasks); }), });
-
Register Router - Add to
packages/api/src/routers/index.tsimport { taskRouter } from "./task"; export const appRouter = router({ task: taskRouter, // ... other routers });
-
Use in Frontend - Call from your components
const { data: tasks } = trpc.task.getAll.useQuery();
This boilerplate is optimized for Vercel with automatic database migrations.
A PostgreSQL database from one of:
- Vercel Postgres
- Neon (recommended for free tier)
- Supabase
- Railway
-
Push code to GitHub
git push origin main
-
Import in Vercel
- Go to vercel.com
- Click "Add New Project"
- Import your GitHub repository
-
Configure build settings
- Root Directory:
apps/web - Framework Preset: Next.js (auto-detected)
- Build Command:
pnpm build(default) - Install Command:
pnpm install(default)
- Root Directory:
-
Add environment variables
Required:
DATABASE_URL=postgresql://user:pass@host/db?sslmode=require BETTER_AUTH_SECRET=<generate with: npx @better-auth/cli secret> BETTER_AUTH_URL=https://yourdomain.com NEXT_PUBLIC_BETTER_AUTH_URL=https://yourdomain.com NEXT_PUBLIC_APP_URL=https://yourdomain.com
Optional (for emails):
RESEND_API_KEY=re_xxxxx
Optional (for payments):
STRIPE_SECRET_KEY=sk_live_xxxxx STRIPE_PUBLISHABLE_KEY=pk_live_xxxxx STRIPE_WEBHOOK_SECRET=whsec_xxxxx STRIPE_STARTER_MONTHLY_PRICE_ID=price_xxxxx STRIPE_STARTER_YEARLY_PRICE_ID=price_xxxxx STRIPE_PRO_MONTHLY_PRICE_ID=price_xxxxx STRIPE_PRO_YEARLY_PRICE_ID=price_xxxxx
-
Deploy
Click "Deploy" - migrations will run automatically during build.
-
Configure Stripe Webhooks (if using Stripe)
In Stripe Dashboard:
- Go to Developers → Webhooks
- Click "Add endpoint"
- URL:
https://yourdomain.com/api/auth/stripe/webhook - Events: Select all
customer.*andcheckout.*events - Copy the webhook signing secret to
STRIPE_WEBHOOK_SECRET
The packages/db/package.json includes a postinstall script:
"postinstall": "[ -n \"$DATABASE_URL\" ] && drizzle-kit migrate || echo 'Skipping migrations'"This automatically runs migrations when DATABASE_URL is present (production), ensuring your schema is always up-to-date.
Build and run the production container:
pnpm build
docker build -t micro-saas-boilerplate .
docker run -p 3000:3000 micro-saas-boilerplateTailwindCSS v4 is configured in packages/ui/. Customize your theme:
- Global styles:
packages/ui/src/styles/ - Tailwind config:
packages/ui/tailwind.config.js - Theme tokens: Modify CSS variables in the styles
Update the following for your brand:
- App name:
apps/web/lib/auth.ts(Better Auth config) - Logo:
apps/web/public/and components - Colors:
packages/ui/src/styles/(CSS variables) - Favicon:
apps/web/app/favicon.ico - Metadata:
apps/web/app/layout.tsx
Edit plans in apps/web/lib/auth.ts:
subscription: {
plans: [
{
name: "starter",
priceId: process.env.STRIPE_STARTER_MONTHLY_PRICE_ID,
freeTrial: { days: 14 },
},
// Add more plans...
];
}This project includes CLAUDE.md for AI coding assistants like Claude Code. This file contains:
- Project architecture overview
- Common commands and workflows
- Development best practices
- Key technical decisions
If you're using an AI coding assistant, point it to CLAUDE.md for context-aware assistance.
Problem: Cannot connect to PostgreSQL
Solution:
# Check if Docker is running
docker ps
# Restart database
docker compose down
docker compose up -d
# Verify connection
psql postgresql://postgres:postgres@localhost:5432/micro_saas_boilerplateProblem: Migration fails with "relation already exists"
Solution:
# Reset database (WARNING: deletes all data)
docker compose down -v
docker compose up -d
# Re-run migrations
DATABASE_URL="<url>" pnpm --filter @repo/db db:migrateProblem: TypeScript errors after modifying database schema
Solution:
# Regenerate types
pnpm check-types
# Or restart Next.js dev server
# (press Ctrl+C and run pnpm dev again)Problem: Stripe webhooks not received during development
Solution:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks (keep this running)
stripe listen --forward-to http://localhost:3000/api/auth/stripe/webhook
# Copy the webhook secret (whsec_...) to .env.localProblem: Verification or reset emails not being sent
Solution:
- Check if
RESEND_API_KEYis set inapps/web/.env.local - Verify your API key in Resend Dashboard
- Check console logs for email content during development:
# Emails are logged to console when Resend is not configured
Problem: Vercel build fails with migration errors
Solution:
- Check that
DATABASE_URLis set in Vercel environment variables - Ensure SSL is enabled:
?sslmode=requirein connection string - Check build logs for specific error
- Test migrations locally first:
DATABASE_URL="<your_vercel_db_url>" pnpm --filter @repo/db db:migrate
Contributions are welcome! Please feel free to submit a Pull Request.
- Follow the existing code style
- Write meaningful commit messages
- Add tests for new features
- Update documentation as needed
- Run
pnpm lintandpnpm check-typesbefore committing
MIT License - feel free to use this for your own projects.
Created by Washington Serip
- Next.js Documentation
- tRPC Documentation
- Drizzle ORM Documentation
- Better Auth Documentation
- Stripe Documentation
- shadcn/ui Documentation
- Turborepo Documentation
- TailwindCSS Documentation
If you find this helpful, please give it a star on GitHub!
Happy Building!
