https://docs.google.com/document/d/1yCTJsQWdybf9-l-oXxV54MY83xkF64r6OSinkqlnJvk/edit?tab=t.0#heading=h.qoug6dmsv1z8

https://docs.google.com/document/d/1qiJNL7-ylU4Y12VXJ7OAU5FtnhyOdLSONjO396UXq2M/edit?tab=t.0#heading=h.gyvr19djgqq5

Frontend System Design in Next.js

Mental Model: Frontend system design is about making intentional architectural decisions — where code runs (server/client/edge), when data is fetched (build/request/client), how state flows, and what fails gracefully. Every choice has a cost: performance vs freshness, simplicity vs scalability, speed vs consistency.


Rendering Architecture

Topic Core Concepts & Mental Model Key Techniques & Tools Tradeoffs & Failure Modes Real-world Examples
SSG – Static Site Generation HTML generated once at build time, served from CDN. Fastest possible TTFB — no server involved per request fetch(url, { cache: 'force-cache' }), generateStaticParams for dynamic paths ✅ Fastest load, CDN-friendly, cheap ❌ Stale data between deploys, long build times at scale (1000s of pages), not suitable for personalized content Marketing pages, documentation, blogs, landing pages
SSR – Server-Side Rendering HTML generated fresh on every request. Data is always current. Higher TTFB than SSG fetch(url, { cache: 'no-store' }), getServerSideProps (Pages Router) ✅ Fresh data, personalized, good SEO ❌ Higher TTFB, server load scales with traffic, no CDN caching for HTML Personalized dashboards, news homepages, auth-gated pages
ISR – Incremental Static Regeneration Static at build time, revalidated in background after N seconds or on-demand event. Stale-while-revalidate for pages next: { revalidate: 60 }, revalidatePath(), revalidateTag() ✅ CDN speed + data freshness, reduced server load ❌ Users may see stale data until revalidation completes; on-demand revalidation requires a webhook or server action trigger E-commerce product pages, news sites, event listings
CSR – Client-Side Rendering Minimal HTML shell, data fetched in browser after JS loads. Server never touches the data 'use client' + TanStack Query / SWR in component ✅ Rich interactivity, real-time feel, private data never touches CDN ❌ Blank screen until JS runs (bad SEO, bad slow-connection UX), hydration cost Admin panels, post-login dashboards, user-specific feeds
Streaming Server sends HTML in chunks as components resolve. Fast shell, slow parts fill in progressively — no single slow query blocks the page <Suspense fallback={<Skeleton />}>, loading.tsx (auto-wraps segment) ✅ Eliminates waterfall blocking, improves perceived performance ❌ Harder to reason about render order; skeleton abuse causes layout shift; requires careful Suspense boundary placement Product pages (fast shell + slow reviews), AI chat responses
Partial Prerendering (PPR) (experimental) Static shell served instantly from CDN, dynamic holes stream in — single request combines SSG speed with SSR freshness experimental: { ppr: true }, <Suspense> marks dynamic boundaries ✅ Best of SSG + SSR in one request ❌ Experimental — API may change; requires careful Suspense boundary design; not yet production-stable Product pages with static layout + dynamic pricing/stock
Server Components Components that run only on the server — zero JS sent to client, can access DB/secrets directly, cannot use hooks or browser APIs Default in App Router; 'use client' opts into Client Components ✅ Smaller JS bundle, secure, no hydration cost ❌ No interactivity, no event handlers; Server Components cannot be imported by Client Components (only passed as children) Blog layouts, product listings, auth-aware nav shells
Edge Computing Run code at CDN edge nodes, closest to user. Web APIs only (no Node.js, no Prisma) — ultra-low latency for lightweight logic export const runtime = 'edge', Vercel Edge Network, Cloudflare Workers ✅ Lowest latency globally, scales automatically ❌ No Node.js APIs, no ORM, cold starts still possible, harder to debug, limited compute time Geo-based redirects, auth token validation, A/B testing, rate limiting
Serverless Functions On-demand server execution — no persistent server process. Each Route Handler / API Route is a function Next.js Route Handlers, Vercel Functions, AWS Lambda ✅ Auto-scaling, pay-per-use, no server management ❌ Cold starts add latency; avoid heavy initialization per request; not suited for long-running tasks (use queues instead) Contact forms, webhooks, auth callbacks, scheduled cron jobs

State Management Architecture

Topic Core Concepts & Mental Model Key Techniques & Tools Tradeoffs & Failure Modes Real-world Examples
Flux / Unidirectional Flow Data flows in one direction: Action → Dispatch → Store → View. No two-way binding. Predictable, auditable state changes Redux Toolkit (createSlice, dispatch), useReducer + useContext ✅ Predictable, debuggable, time-travel ❌ Boilerplate overhead; overkill for small apps; easy to abuse (putting server state in Redux) Global UI state, auth session, shopping cart, multi-step wizards
Normalized Stores Store relational data flat (like a DB) — entities indexed by ID, no duplication. posts[id], users[id] instead of nested objects Redux Toolkit createEntityAdapter, custom selectors ✅ Single source of truth per entity, efficient updates, no sync bugs ❌ More upfront design; manual denormalization for reads; unfamiliar pattern to juniors Posts + comments + users, products + categories, messages + senders
Server State vs Client State Most "state" in apps is server data — treat it differently. Server state is async, stale, shared. Client state is synchronous, local, owned TanStack Query / SWR (server state), Zustand / Jotai (client state) ✅ Correct mental model eliminates "why is my data stale?" bugs ❌ Anti-pattern: storing server responses in Redux/Zustand manually instead of using a cache library Server: posts, users, products. Client: sidebar open/closed, modal state, form step
URL as State Put shareable/bookmarkable UI state in the URL — filters, pagination, tabs, search queries. Survives refresh; shareable useSearchParams, useRouter, shallow routing ✅ Free persistence, shareable links, back-button works ❌ URL can get cluttered; encoding complex state is awkward; syncing with component state needs care Search filters, pagination page, selected tab, sort order

API Design & Data Fetching Patterns

Topic Core Concepts & Mental Model Key Techniques & Tools Tradeoffs & Failure Modes Real-world Examples
REST Resource-based URLs, stateless, HTTP verbs map to CRUD. Simple mental model, universal support Next.js Route Handlers (GET, POST, PATCH, DELETE), Axios, fetch ✅ Simple, universal, cacheable GETs ❌ Over-fetching (getting fields you don't need), under-fetching (multiple roundtrips for related data), no strict contract /api/users, /api/posts/[id], /api/auth
GraphQL Single endpoint, client specifies exact shape of data needed. Schema is the contract Apollo Client, URQL, tRPC (type-safe alternative) ✅ No over/under-fetch, great for complex relational UIs, self-documenting schema ❌ Complexity overhead for simple apps; N+1 query problem server-side (need DataLoader); caching harder than REST GitHub-style dashboards, nested feeds (post + author + comments + likes)
tRPC Type-safe RPC from client to server — no REST, no GraphQL, no codegen. TypeScript types are shared end-to-end tRPC + Zod, Next.js adapter ✅ End-to-end type safety, zero schema duplication, great DX ❌ Tight client-server coupling (only works within the same TypeScript monorepo); not suitable for public APIs Internal full-stack Next.js apps, team tools, admin panels
Server Actions Call server functions directly from forms or Client Components — no API route needed for mutations 'use server' directive, useFormState / useActionState, revalidatePath ✅ Eliminates boilerplate API routes for mutations, progressive enhancement for forms ❌ Not a replacement for public APIs; harder to reuse across services; error handling requires care Form submissions, CRUD mutations, cart updates, profile edits
Pagination — Offset Fetch page N with skip/limit. Simple to implement and understand ?page=2&limit=20, OFFSET in SQL ✅ Simple, supports jumping to arbitrary page ❌ Inconsistent with real-time data (inserts/deletes shift pages), performance degrades on high offsets (DB must scan all skipped rows) E-commerce product grids, search results
Pagination — Cursor Fetch next N items after a stable cursor (ID or timestamp). Consistent for real-time feeds ?cursor=<lastId>, Prisma cursor, TanStack Query useInfiniteQuery ✅ Consistent, performant at scale, works with real-time inserts ❌ Cannot jump to arbitrary page; cursor must be stable (use unique ID, not offset) Twitter/Instagram feeds, chat history, infinite scroll
Batching Combine multiple data requests into one round trip. Reduces waterfall and connection overhead React's automatic RSC batching, DataLoader (server-side), Promise.all for parallel fetches ✅ Fewer round trips, lower latency ❌ Added complexity; over-batching can delay fast responses waiting for slow ones Profile + settings + activity feed in one SSR render
Optimistic Updates Update UI instantly before server confirms. Rollback on failure TanStack Query onMutate + onError rollback, SWR mutate ✅ Instant-feeling UX ❌ Rollback UX can be jarring; must handle conflict when server disagrees (e.g., someone else deleted the item); not suitable for critical transactions Like/unlike, follow/unfollow, cart add/remove, drag-and-drop reorder

Real-time & Async Patterns

Topic Core Concepts & Mental Model Key Techniques & Tools Tradeoffs & Failure Modes Real-world Examples
Short Polling Client repeatedly asks server "any updates?" on a fixed interval setInterval + fetch, TanStack Query refetchInterval ✅ Simple, works everywhere ❌ Wasteful — constant requests even when nothing changed; high server load at scale; not suitable for >100 concurrent users needing low latency Low-frequency status checks, build progress indicators
Long Polling Client sends request, server holds it open until there's data to return (or timeout) Custom server endpoint, held HTTP response ✅ Lower latency than short polling, no persistent connection needed ❌ Server must hold many open connections; reconnect logic required; more complex than short polling Order status updates, ticket queue notifications
SSE (Server-Sent Events) Persistent one-way stream from server to client over HTTP. Built-in reconnection EventSource API, Next.js Route Handler returning ReadableStream ✅ Simple, HTTP-only (no upgrade), auto-reconnect, works through proxies ❌ Server → client only (no client → server on same connection); max 6 connections per domain in HTTP/1.1 Live logs, streaming AI responses (ChatGPT-style), live sports scores
WebSockets Full-duplex persistent TCP connection. Both sides can send at any time Socket.io, native WebSocket API, Pusher, Ably (hosted) ✅ Lowest latency bidirectional communication ❌ Custom Next.js server or external service required (Vercel serverless doesn't support persistent WebSocket connections natively); reconnect and auth logic complexity Chat apps, collaborative editors, live trading, multiplayer games
Debounce & Throttle Control rate of expensive operations triggered by user input. Debounce: wait for pause. Throttle: cap at N calls/sec lodash.debounce, lodash.throttle, custom useDebounce hook ❌ Common mistake: debouncing the wrong thing (debounce the API call, not the input handler); too-long debounce feels laggy; too-short defeats the purpose Search-as-you-type (debounce), scroll/resize handlers (throttle), button spam prevention (throttle)
Retries with Backoff + Jitter Retry failed requests with exponentially increasing delays + random jitter. Prevents thundering herd on recovery TanStack Query retry + retryDelay, custom exponential backoff ✅ Handles transient failures gracefully ❌ Without jitter, all clients retry simultaneously (thundering herd); retrying non-idempotent operations (POST) can cause duplicates — use idempotency keys Rate-limited API calls, flaky third-party services, payment webhooks
Race Conditions Multiple async operations in flight — stale response overwrites fresh one AbortController, TanStack Query (handles internally), request ID tracking ❌ Classic bug: type fast in search box, slow request from 3 keystrokes ago resolves after fast request from 5 keystrokes → shows wrong results Search filters, fast navigation, tab switching with async data
Long-running Tasks Never block an HTTP request with heavy work. Offload to a queue or background worker BullMQ + Redis, Upstash QStash, Vercel Cron, Inngest, Temporal ✅ Request returns immediately, work happens async ❌ Need observability (job status, failure visibility); user needs a way to know when it's done (SSE/polling for status) Email sending, PDF generation, video processing, AI batch jobs, report generation
Offline & PWA Cache critical assets and data for offline use. Sync changes when reconnection happens Service Worker, Workbox, next-pwa, IndexedDB, Background Sync API ✅ Works without connectivity, app-like install experience ❌ Cache invalidation is hard; IndexedDB API is low-level (use Dexie.js); conflict resolution on sync is complex Field service apps, note-taking apps, delivery tracking

Performance Architecture