Why Next.js for SaaS
Next.js has become the default choice for modern SaaS because it handles the full stack in one framework β server-side rendering for SEO on marketing pages, API routes for backend logic, and React for interactive UI. You don't need separate frontend and backend codebases.
For Indian startup teams that are often 1β3 people, this unification is practically valuable. One deployment, one codebase, one set of skills required.
Project Structure That Scales
/app
/(marketing) # Public pages (landing, pricing, blog)
/(app) # Authenticated app routes
/dashboard
/settings
/api # API routes
/(components)
/ui # Generic UI components
/app # App-specific components
/lib
/auth.ts # Auth utilities
/db.ts # Database client
/validations.ts # Zod schemas
/hooks # React hooks
/types # TypeScript types
The key decision: route groups (marketing) and (app) let you apply different layouts to public and authenticated pages without affecting URL structure.
Authentication
For most SaaS products, use NextAuth.js (Auth.js v5) or Clerk.
NextAuth: Open source, flexible, more complex to configure. Good if you need custom logic or want to self-host auth.
Clerk: Hosted auth with a generous free tier. Pre-built UI components. Takes 30 minutes to implement vs. a day for NextAuth. Worth the $25/month at scale for the time saved.
Middleware for route protection:
export { auth as middleware } from './lib/auth'
export const config = {
matcher: ['/(app)/:path*']
}
Database Layer
For most SaaS: Supabase (managed PostgreSQL) or Turso (SQLite at the edge, extremely cheap).
Use Drizzle ORM over Prisma for Next.js SaaS β it generates better query code and has better edge runtime support.
// lib/db.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
const client = postgres(process.env.DATABASE_URL!)
export const db = drizzle(client)
Multi-Tenancy Pattern
For B2B SaaS, add organizationId (or tenantId) to every table. Create middleware that validates the current user has access to the requested organization: