0%

Web Development

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.

personWEBOPS Limited Engineering Team
calendar_today
schedule16 min read

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

MetricMVPGrowthScale
**Users**1,00050,000500,000
**Monthly Cost**$100$400$2,000
**Cost per User**$0.10$0.008$0.004
**Deployment**VercelVercel + RDSMulti-region
**DB Size**1 GB50 GB500+ 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