Contents

Chapter 1

Architecture Guide

This document explains the architectural decisions behind the Next.js Starter Kit — why things are structured the way they are, and how to extend the architecture as your app grows.

Directory Structure

├── app/                      # Next.js App Router pages and layouts
│   ├── api/                  # API route handlers (serverless functions)
│   │   ├── auth/route.ts     # Authentication endpoints
│   │   ├── posts/route.ts    # Blog/content CRUD
│   │   └── users/route.ts    # User management
│   ├── blog/                 # Public blog with ISR
│   │   ├── page.tsx          # Blog listing (ISR, 60s revalidation)
│   │   └── [slug]/page.tsx   # Individual post (ISR + generateStaticParams)
│   ├── dashboard/            # Protected area
│   │   ├── layout.tsx        # Dashboard shell (sidebar, nav)
│   │   └── page.tsx          # Dashboard home
│   ├── layout.tsx            # Root layout (HTML shell, global providers)
│   ├── page.tsx              # Landing page (Server Component)
│   ├── loading.tsx           # Global loading skeleton
│   ├── error.tsx             # Global error boundary
│   └── not-found.tsx         # 404 page
├── components/               # Shared React components
│   ├── Header.tsx            # Site header with auth-aware navigation
│   ├── Footer.tsx            # Site footer with link columns
│   └── PostCard.tsx          # Blog post card (3 variants)
├── src/
│   ├── lib/                  # Core libraries and utilities
│   │   ├── auth.ts           # JWT auth, password hashing, session management
│   │   ├── prisma.ts         # Prisma client singleton
│   │   └── utils.ts          # Shared utilities (slugify, formatting, etc.)
│   └── types/                # TypeScript type definitions
│       └── index.ts          # Shared types (User, Post, ApiResponse, etc.)
├── prisma/
│   └── schema.prisma         # Database schema
├── middleware.ts              # Edge middleware for route protection
├── next.config.ts             # Next.js configuration
├── vercel.json                # Vercel deployment configuration
└── package.json               # Dependencies and scripts

Key Architecture Decisions

1. App Router Over Pages Router

We use the App Router (introduced in Next.js 13, stable in 14) instead of the legacy Pages Router. Here's why:

FeaturePages RouterApp Router (this starter)
Rendering modelPages default to client-sideComponents default to server-side
Data fetchinggetServerSideProps, getStaticPropsasync Server Components, fetch() with caching
LayoutsCustom _app.tsx wrapperNested layout.tsx files (preserved across navigation)
Loading statesManual implementationBuilt-in loading.tsx per route segment
Error handlingCustom _error.tsxBuilt-in error.tsx per route segment
MetadataManual managementmetadata export or generateMetadata()
StreamingNot supportedBuilt-in with

The App Router is the future of Next.js. All new features ship for App Router first.

2. Server Components by Default

Every component in this starter is a Server Component unless explicitly marked with "use client". This matters for three reasons:

1. Bundle size — Server Components send zero JavaScript to the client. The PostCard, Footer, and most of the app/ pages ship as pure HTML.

2. Data access — Server Components can directly query the database, read files, or call APIs without exposing credentials to the client. No need for an API route as a middleman.

3. Performance — Server Components stream HTML progressively. The user sees content faster because the browser doesn't need to download, parse, and execute JavaScript before rendering.

When to use "use client":

  • Event handlers (onClick, onChange, onSubmit)
  • Browser APIs (useState, useEffect, useRef, usePathname)
  • Third-party libraries that use browser APIs

In this starter, only Header.tsx needs "use client" (for the mobile menu toggle and active link detection).

3. JWT Authentication (Not NextAuth)

We implement authentication from scratch using JWTs instead of using NextAuth.js. This is deliberate:

Why not NextAuth?

  • NextAuth abstracts away the auth flow, making it harder to understand and debug
  • For a SaaS app, you often need custom auth logic (API keys, team invitations, RBAC)
  • NextAuth's session strategy adds complexity that isn't needed for most projects
  • Understanding auth fundamentals makes you a better developer

How our auth works:

Login Request → Validate credentials → Create JWT → Set HTTP-only cookie
                                                           │
Subsequent requests → Middleware reads cookie → Verifies JWT → Allows/denies
                                                                    │
API routes → getCurrentUser() reads cookie → Returns user or null

Key security features:

  • HTTP-only cookies — JavaScript can't access the token (XSS protection)
  • Secure flag — Cookie only sent over HTTPS in production
  • SameSite=Lax — CSRF protection without breaking OAuth redirects
  • Short expiry — Tokens expire after 7 days (configurable)
  • Password hashing — Uses Web Crypto API's PBKDF2 (no bcrypt dependency)

4. Prisma ORM for Database Access

We use Prisma because:

  • Type safety — Prisma generates TypeScript types from your schema. prisma.user.findUnique() returns a fully typed User object.
  • Migrations — prisma migrate dev handles schema changes with version-controlled SQL migrations.
  • Multi-database — Switch between PostgreSQL, MySQL, SQLite, and MongoDB by changing the provider in schema.prisma.
  • Query logging — Enable query logging in development to see the actual SQL.

The singleton pattern (src/lib/prisma.ts):

typescript
// Without singleton: Hot Module Replacement creates a new PrismaClient
// on every file save, eventually exhausting database connections.
//
// With singleton: One PrismaClient instance is stored on `globalThis`
// and reused across hot reloads.

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

5. Edge Middleware for Route Protection

The middleware.ts file runs on Vercel's Edge Runtime (or Node.js locally). It intercepts every request before it reaches your pages.

Why middleware instead of per-page auth checks?

  • Centralized — One file protects all routes, no chance of forgetting a page
  • Fast — Edge middleware runs in <1ms, before the page starts rendering
  • Redirects — Unauthenticated users are redirected to login, not shown a 403

Route matching strategy:

typescript
// Protected routes — require authentication
const PROTECTED_PREFIXES = ["/dashboard", "/settings", "/api/users"];

// Auth routes — redirect to dashboard if already logged in
const AUTH_ROUTES = ["/login", "/signup"];

The middleware checks the JWT cookie, verifies it, and either allows the request or redirects. It does NOT query the database (that would be slow on every request). The JWT contains the user's ID and role, which is enough for authorization decisions.

6. ISR (Incremental Static Regeneration) for Blog

The blog pages use ISR — a hybrid between static and dynamic rendering:

typescript
// app/blog/page.tsx
export const revalidate = 60; // Regenerate at most every 60 seconds

How ISR works:

1. First visitor → Next.js renders the page server-side and caches it

2. Next 60 seconds → All visitors get the cached HTML (instant)

3. After 60 seconds → Next visitor triggers a background regeneration

4. Background regeneration completes → New HTML replaces the cache

5. All subsequent visitors get the fresh page

This gives you the performance of a static site with the freshness of server rendering. Blog posts update within 60 seconds of a database change, without a full rebuild.

generateStaticParams for dynamic routes:

typescript
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await prisma.post.findMany({
    where: { published: true },
    select: { slug: true },
  });
  return posts.map((post) => ({ slug: post.slug }));
}

This pre-generates pages for all existing posts at build time. New posts are rendered on-demand and then cached.

Data Flow Patterns

Pattern 1: Server Component Data Fetching

User visits /blog → Server Component renders
                          │
                    Prisma query (server-side, no API call)
                          │
                    Returns HTML with post data
                          │
                    Browser displays page (zero JS for data fetching)

Pattern 2: Client-Side Mutations via API Routes

User clicks "Create Post" → Client Component form
                                    │
                              fetch("/api/posts", { method: "POST", body: ... })
                                    │
                              API route validates → Prisma insert → Returns JSON
                                    │
                              Client updates UI (optimistic or after response)

Pattern 3: Authenticated API Calls

Client sends request → API route handler
                              │
                        getCurrentUser(request) reads cookie
                              │
                        JWT valid? → Query database with user context
                        JWT invalid? → Return 401

Scaling the Architecture

Adding a New Protected Page

1. Create app/your-page/page.tsx

2. Add the route prefix to PROTECTED_PREFIXES in middleware.ts

3. Use getCurrentUser() in the page for user-specific data

Adding a New API Endpoint

1. Create app/api/your-resource/route.ts

2. Export GET, POST, PUT, or DELETE functions

3. Use getCurrentUser(request) for authenticated endpoints

4. Return NextResponse.json() with appropriate status codes

Adding a New Database Model

1. Add the model to prisma/schema.prisma

2. Run npx prisma migrate dev --name add_your_model

3. Add the TypeScript type to src/types/index.ts

4. Create an API route at app/api/your-model/route.ts

Adding Real-Time Features

For real-time updates (chat, notifications, live dashboards), you have several options:

1. Server-Sent Events (SSE) — Simple, one-way server-to-client. Create a streaming API route.

2. WebSockets — Two-way communication. Requires a separate WebSocket server (not supported on Vercel Serverless).

3. Polling — Simple but less efficient. Use setInterval with fetch() in a client component.

4. Third-party services — Pusher, Ably, or Supabase Realtime for managed WebSockets.

Adding Team/Organization Support

The current schema has individual users. To add teams:

1. Add Team and TeamMember models to Prisma

2. Add teamId to the JWT payload

3. Update middleware to check team membership

4. Add team-scoped queries (where: { teamId })

Performance Considerations

OptimizationImplementationImpact
Server ComponentsDefault — no "use client" unless needed50-80% less client JS
ISR for blogrevalidate = 60 on listing pagesStatic-speed for reads
Image optimizationUse next/image with width/heightAutomatic WebP, lazy loading
Font optimizationUse next/font/google in root layoutNo layout shift, preloaded
Route prefetchingAutomatic for in viewportInstant navigation
Edge middlewareAuth checks at the edge, not in pages<1ms auth overhead
Prisma singletonSingle connection pool across hot reloadsNo connection exhaustion

Security Checklist

  • [x] Passwords hashed with PBKDF2 (100,000 iterations)
  • [x] JWT stored in HTTP-only, Secure, SameSite=Lax cookie
  • [x] CSRF protection via SameSite cookie attribute
  • [x] Route protection in edge middleware (not just client-side)
  • [x] Input validation in API routes before database queries
  • [x] Error responses don't leak internal details
  • [x] Environment variables for all secrets (never hardcoded)
  • [ ] Rate limiting on auth endpoints (add with Upstash or similar)
  • [ ] Content Security Policy headers (add in next.config.ts)
  • [ ] CORS configuration for API routes (add if serving external clients)

Part of Frontend Developer Pro

Chapter 2
🔒 Available in full product

Deployment Guide

Chapter 3
🔒 Available in full product

Architecture Guide

Chapter 4
🔒 Available in full product

Deployment Guide

You’ve reached the end of the free preview

Get the full Next.js Starter Kit and unlock everything.

All Chapters

Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.

Full Tool Suite

Access all interactive tools with complete data, all workload profiles, and the full scenario library.

Source Files

Downloadable source code, configuration files, and working examples from every chapter.

Lifetime Updates

Free updates for life. Every new chapter, tool, and improvement included.

Buy Now — $49 →
📦 Free sample included — download another copy or visit the store for the full product.
Next.js Starter Kit v1.0.0 — Free Preview