Building a Scalable SaaS Architecture with Next.js: Complete Guide
Learn how to architect a production-grade SaaS product using Next.js 16, API routes, databases, and deployment. From MVP to 100k users.
What is SaaS Architecture?
SaaS (Software-as-a-Service) architecture is the technical blueprint for building multi-tenant web applications that serve thousands of users simultaneously. It prioritizes scalability, security, and cost efficiency.
The WEBOPS Limited SaaS Stack
**Frontend:** Next.js 16 (React + App Router) **Backend:** Next.js API Routes + external services **Database:** PostgreSQL + Redis cache **Deployment:** Vercel + AWS RDS **Auth:** NextAuth.js **Monitoring:** Sentry + DataDog
### Layer 1: Frontend Architecture
// src/app/dashboard/[team]/layout.tsx - Multi-tenant layout
export async function generateStaticParams() {
const teams = await db.teams.findAll();
return teams.map(t => ({ team: t.slug }));
}export default function DashboardLayout({ children, params }) { const team = useTeamContext(params.team); // Verify user has access to this team if (!team.members.includes(currentUser.id)) { notFound(); } return ( <div> <Sidebar teams={team} /> {children} </div> ); } ```
**Key principles:** - Implement team/workspace isolation at component level - Use URL params to scope data queries - Cache frequently accessed data in React Query - Lazy load dashboard components
### Layer 2: Backend Scalability
// src/app/api/teams/[teamId]/projects/route.ts
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { db } from '@/lib/db';export async function GET(req, { params }) { const session = await getServerSession(); // Check permission (this hits cache, not DB) const hasAccess = await cache(() => db.teams.checkUserAccess(params.teamId, session.user.id), 'team-' + params.teamId + '-' + session.user.id, 3600 // 1 hour ); if (!hasAccess) { return NextResponse.json({ error: 'Unauthorized' }, { status: 403 }); } // Query only what's needed const projects = await db.projects.findByTeam(params.teamId, { select: ['id', 'name', 'createdAt'], // Not all columns take: 50, // Pagination }); return NextResponse.json(projects); } ```
**Performance rules:** - Always paginate (max 50 items per request) - Use database indexes on frequently queried columns - Implement caching layer (Redis) for user permissions - Cache responses with revalidateTag() for ISR
### Layer 3: Database Design for Multi-Tenant
-- Core schema
CREATE TABLE teams (
id UUID PRIMARY KEY,
slug VARCHAR(100) UNIQUE,
name VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
);CREATE TABLE team_members ( team_id UUID REFERENCES teams(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE CASCADE, role ENUM('owner', 'admin', 'member', 'viewer'), PRIMARY KEY (team_id, user_id) );
CREATE TABLE projects ( id UUID PRIMARY KEY, team_id UUID REFERENCES teams(id) ON DELETE CASCADE, name VARCHAR(255), created_at TIMESTAMP DEFAULT NOW() );
-- Indexes for performance CREATE INDEX idx_projects_team_id ON projects(team_id); CREATE INDEX idx_team_members_user_id ON team_members(user_id); ```
**Multi-tenancy isolation:** - Every query includes WHERE team_id = X - Use row-level security (RLS) in PostgreSQL as backup - Never query across teams in same request
### Layer 4: Authentication & Authorization
// lib/auth.ts
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
// Load user's teams into token
const teams = await db.teams.findByUser(user.id);
token.teams = teams;
}
return token;
},
async session({ session, token }) {
session.user.teams = token.teams;
return session;
},
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
};
Scaling Patterns: MVP → 10k → 100k Users
### Stage 1: MVP (0-1,000 users) - **Tech:** Single Vercel deployment + shared PostgreSQL DB - **Cost:** ~$50-100/month - **Code strategy:** Monolithic API routes (all endpoints in src/app/api) - **Database:** Standard PostgreSQL on AWS RDS (db.t3.micro)
**When to scale:** Hitting database CPU >80% or API latency >500ms
### Stage 2: Growth (1k-50k users) - **Tech:** Vercel Functions + separate database service (AWS RDS db.t3.small) - **Cost:** ~$200-500/month - **Code strategy:** Separate API route folders by feature (/projects /analytics /billing) - **Caching:** Add Redis for session/permission caching - **Database:** Implement read replicas for heavy queries
**When to scale:** Multiple Vercel deployments needed or API response time >1s
### Stage 3: Scale (50k-500k users) - **Tech:** Vercel Edge Runtime for APIs + Postgres replicas + CDN caching - **Cost:** ~$1,000-3,000/month - **Code strategy:** Separate backend service (Node.js on AWS ECS) if Vercel limits hit - **Queue system:** Add job queue (Bull/RabbitMQ) for async work - **Monitoring:** Full observability (error tracking, performance profiling)
**When to scale:** Building custom infrastructure (self-hosted backend)
Cost Projections by Stage
| Metric | MVP | Growth | Scale |
| **Users** | 1,000 | 50,000 | 500,000 |
| **Monthly Cost** | $100 | $400 | $2,000 |
| **Cost per User** | $0.10 | $0.008 | $0.004 |
| **Deployment** | Vercel | Vercel + RDS | Multi-region |
| **DB Size** | 1 GB | 50 GB | 500+ GB |
Case Study: WEBOPS Limited Client Success
**Client:** E-Commerce SaaS Platform - **Launched:** 500 users on Vercel + shared DB - **6 months:** Scaled to 25,000 users - needed read replicas - **12 months:** Hit 100,000 users - moved to Edge Runtime - **Architecture investment:** $15,000 (engineering time) - saved $50,000+ vs AWS/GCP bills
Common Mistakes to Avoid
❌ **Querying all data instead of paginating** — Returns 100,000 rows when user needs 10 ❌ **No team_id check in API routes** — User A sees Team B's data ❌ **Storing user session data in database per request** — Kills performance at scale ❌ **Single database write source for all users** — Causes lock contention ❌ **No monitoring until it breaks** — Can't debug production issues
✅ **Always add team_id filter** ✅ **Paginate from the start** (even for MVP) ✅ **Use caching for slow queries** (permissions, user preferences) ✅ **Monitor from day 1** (even free tier is better than nothing)
Ready to Build Your SaaS?
[Talk to our SaaS architects →](/booking) to validate your architecture before you build. We've scaled 15+ SaaS products to 100k+ users.
Ready to Implement This?
WEBOPS Limited specializes in web development. Get a custom 48-hour proposal for your project.
Schedule a Consultation