Frontend System Design

Reference repo: namastedev/namaste-frontend-system-design

Note: Performance Optimization and Testing are covered in depth in separate domain tables. Sections here focus on system design–level thinking — not implementation details.


Networking — API Paradigms

Topic Core Concepts & Mental Model Tools & Libraries Key Techniques Tradeoffs & Failure Modes Resources
REST Resources identified by URLs, stateless, HTTP verbs map to CRUD. Simplest API contract — universally understood. Cacheable by nature (GET) Postman, Thunder Client, Swagger/OpenAPI, Axios, fetch Proper verb usage (GET/POST/PUT/PATCH/DELETE), status codes (2xx/4xx/5xx), versioning (/api/v1/), pagination, filtering via query params ✅ Simple, universal, CDN-cacheable GETs ❌ Over-fetching (getting fields you don't need), under-fetching (N+1 trips for related data), no strict type contract between client/server RESTful API Design, HTTP Status Codes
GraphQL Single endpoint, client declares exact data shape needed. Schema is a typed contract shared by client and server. Eliminates over/under-fetching Apollo Client, URQL, GraphQL Playground, Apollo Studio Queries (read), mutations (write), subscriptions (real-time), fragments for reuse, DataLoader (server-side) to solve N+1, persisted queries ✅ Client controls shape, great for nested/relational UIs, self-documenting schema ❌ N+1 query problem server-side if not using DataLoader; caching harder than REST (non-GET); complexity overhead for simple CRUD; large queries can be expensive GraphQL.org, Apollo Client docs
gRPC Binary protocol using Protocol Buffers. Strongly typed IDL-defined contracts, streaming support. Not natively browser-compatible — requires gRPC-Web proxy or Envoy sidecar on frontend gRPC-Web, Protobuf, Envoy proxy Unary, server/client/bidirectional streaming, .proto schema as single source of truth for types, code generation for clients ✅ Extremely fast (binary, compressed), strong typing via Protobuf, native streaming ❌ Not usable directly in browser without gRPC-Web + proxy layer; harder to debug than REST (binary not human-readable); limited browser support; overkill for most web apps gRPC-Web, Protobuf docs
tRPC Type-safe RPC — call server functions from client with full TypeScript type inference. No schema, no codegen, no REST URLs. Types flow end-to-end automatically tRPC v11+, Zod (input validation), TanStack Query (transport layer) Routers + procedures (query/mutation), Zod schemas for input validation, context for auth/session, middleware on procedures, Next.js adapter ✅ Zero type duplication, best-in-class DX for TypeScript monorepos, no API contract drift ❌ Tight client-server coupling (same TypeScript repo required); not suitable for public APIs or non-TypeScript consumers; not REST — can't be called from Postman tRPC docs

Communication Patterns

Topic Core Concepts & Mental Model Tools & Libraries Key Techniques Tradeoffs & Failure Modes Resources
Short Polling Client asks server "any updates?" on a fixed interval. Simple to implement. Server always responds immediately (empty or with data) setInterval + fetch, TanStack Query refetchInterval Fixed interval (e.g. 5s), exponential backoff on error, stop polling on component unmount/tab hidden ✅ Works everywhere, simple ❌ Wasteful — sends requests even when nothing changed; server load grows linearly with users; not suitable for low-latency or high-frequency scenarios
Long Polling Client sends request, server holds it open until it has data to return (or timeout). Then client immediately re-requests Custom Node.js endpoint with delayed response Server holds response via async timeout, client loops on response, timeout + reconnect strategy required ✅ Lower latency than short polling, no persistent connection ❌ Server must hold many concurrent open connections; reconnect logic adds complexity; harder to scale behind load balancers
Server-Sent Events (SSE) Persistent one-way HTTP stream: server → client. Uses standard HTTP (not a new protocol). Browser auto-reconnects on disconnect EventSource API (native), Next.js Route Handler returning ReadableStream, @microsoft/fetch-event-source Named event types (event: message), data: field, id: for reconnect cursor, retry: for reconnect interval, route handler: return new Response(stream) ✅ Simple, HTTP-only, auto-reconnect built-in, works through proxies/firewalls ❌ Server → client only; max 6 connections per domain on HTTP/1.1 (use HTTP/2); Vercel/serverless functions time out — use streaming or dedicated SSE server MDN SSE, EventSource API
WebSockets Full-duplex persistent TCP connection. Both sides send messages at any time. Requires HTTP → WS upgrade handshake Socket.io (abstraction + fallback), native WebSocket API, Pusher/Ably/PartyKit (hosted) Event-based messaging, rooms/namespaces (Socket.io), auth via handshake query/cookie, heartbeat/ping-pong for connection health, reconnection with backoff ✅ Lowest latency bidirectional communication ❌ Vercel/AWS Lambda serverless don't support persistent WS natively — use hosted service or dedicated server; reconnect + auth state management is complex; stateful connections don't scale horizontally without Redis pub/sub Socket.io docs, MDN WebSocket
Webhooks Server-to-server HTTP callbacks. External service calls your endpoint when an event occurs. Asynchronous, event-driven ngrok (local testing), Svix (webhook platform), Express/Next.js Route Handler Verify payload signature (HMAC X-Signature header), idempotency (process same event twice safely), respond 200 fast then process async (queue), retry handling ✅ Decoupled, async, no polling ❌ No delivery guarantee without retry logic on sender side; your endpoint must be publicly reachable (problem in dev — use ngrok); replay attacks if signature not verified; timeouts if processing is slow Stripe Webhook Guide

Security

Mental Model: Security is defence in depth — multiple layers. No single technique is sufficient. Frontend security focuses on: (1) protecting the DOM, (2) protecting the network, (3) protecting storage, (4) protecting the user's identity.

Topic Core Concepts & Mental Model Tools & Libraries Key Techniques Tradeoffs & Failure Modes Resources
XSS (Cross-Site Scripting) Attacker injects malicious script via user-generated content. Runs with full origin privileges. Three types: Stored (in DB), Reflected (in URL), DOM-based (client-side JS) DOMPurify (sanitize HTML), Content-Security-Policy header Sanitize before rendering user HTML (DOMPurify.sanitize()), avoid dangerouslySetInnerHTML, set strict CSP header, encode output (React JSX does this by default) ❌ React's JSX escapes by default — only dangerous when you bypass it (dangerouslySetInnerHTML, eval, innerHTML). DOM-based XSS comes from document.write, location.href = userInput, etc. OWASP XSS, DOMPurify
CSRF (Cross-Site Request Forgery) Attacker tricks authenticated user's browser into making requests to your server using stored cookies. Exploits the browser's automatic cookie-sending behavior SameSite=Strict or SameSite=Lax on session cookies, anti-CSRF token in form/header, validate Origin/Referer header on mutations, CAPTCHA for critical actions, avoid state-changing GET requests SameSite=Lax (browser default) doesn't protect against top-level cross-site POST. SameSite=Strict can break OAuth flows. JWT in Authorization header is CSRF-immune (not auto-sent by browser) OWASP CSRF
CORS (Cross-Origin Resource Sharing) Browser enforces same-origin policy — blocks cross-origin requests by default. CORS headers tell the browser which origins, methods, and headers are allowed cors npm package (Node), browser DevTools Network tab Configure Access-Control-Allow-Origin (never * with credentials), Access-Control-Allow-Methods, preflight (OPTIONS) handling, credentials: 'include' only when necessary ❌ Common mistake: setting Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — browsers reject this. CORS is a browser protection, not a server security measure — server-to-server requests ignore it MDN CORS
Content Security Policy (CSP) HTTP response header that tells the browser which sources are trusted for scripts, styles, images, frames, etc. Prevents XSS by refusing to execute untrusted scripts CSP Evaluator (Google), next.config.js headers, helmet (Node) script-src 'self' (no inline scripts), object-src 'none', base-uri 'self', report-only mode for testing (Content-Security-Policy-Report-Only), nonce-based for inline scripts ❌ Too strict CSP breaks third-party integrations (analytics, chat widgets); 'unsafe-inline' defeats XSS protection; start with report-only mode and tighten gradually MDN CSP, CSP Evaluator
Security Headers HTTP response headers that instruct browsers on security policies. One of the highest-ROI security improvements — add in next.config.js or CDN config next.config.js headers(), helmet (Express), securityheaders.com X-Frame-Options: DENY (clickjacking), X-Content-Type-Options: nosniff (MIME sniffing), Strict-Transport-Security (HSTS — force HTTPS), Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy ❌ Missing X-Frame-Options = clickjacking. Missing HSTS = protocol downgrade attack. Wrong Referrer-Policy leaks sensitive URL paths to third parties securityheaders.com, OWASP Secure Headers
Clickjacking & iframe Security Attacker embeds your page in an invisible iframe on their site and tricks user into clicking elements X-Frame-Options: DENY or SAMEORIGIN, CSP frame-ancestors 'none', sandbox attribute on iframes you embed (sandbox="allow-scripts allow-same-origin") X-Frame-Options is deprecated in favor of CSP frame-ancestors — set both for compatibility MDN X-Frame-Options
Subresource Integrity (SRI) Ensures external scripts/styles (from CDN) haven't been tampered with by verifying a cryptographic hash. If CDN is compromised, the browser refuses the resource srihash.org Add integrity="sha384-<hash>" + crossorigin="anonymous" on external <script> and <link> tags ❌ Hash must be updated whenever the external resource version changes; doesn't protect dynamically loaded scripts (only static URLs); not needed if you self-host all assets MDN SRI
Permissions Policy HTTP response header that controls which browser features (camera, microphone, geolocation) a page or its iframes can use. Replaced Feature Policy res.setHeader("Permissions-Policy", ...), DevTools → Application → Frames Permissions-Policy: geolocation=(self), camera=(), microphone=() — empty () = denied for everyone including self. (self) = allowed only for your origin ❌ Permissions Policy only restricts browser APIs, not server-side operations. Not supported in all browsers MDN Permissions Policy
Input Validation & Sanitization Validate on both client (UX feedback) and server (security). Client-side validation is for convenience, never for trust Zod, Yup, React Hook Form, DOMPurify Type (string, email, number), format (regex), range (min/max), required fields. Sanitize HTML before display, never before storage (store raw, sanitize on output) ❌ Sanitizing before storing and then sanitizing again on display = double-sanitization corruption. Client-side validation only = bypassable with DevTools/curl. Never trust Content-Type header alone Zod docs
Dependency Security Third-party npm packages are a supply chain attack vector. Regular auditing is a production hygiene practice npm audit, pnpm audit, Snyk, Socket.dev, Renovate (auto-updates) Run npm audit --audit-level=high in CI, use package-lock.json / pnpm-lock.yaml (lockfiles prevent version drift), pin major versions of critical packages npm install without lockfile = gets latest (possibly compromised) version; node_modules is 3rd-party code running with full privileges; typosquatting (e.g. reakt instead of react) Socket.dev, npm audit docs
HTTPS & Transport Security All production traffic must be HTTPS. Mixed content (HTTP resource on HTTPS page) is blocked by browsers. HSTS prevents downgrade attacks Vercel (auto HTTPS), Let's Encrypt, Cloudflare Enable HSTS (Strict-Transport-Security: max-age=31536000; includeSubDomains), redirect HTTP→HTTPS at CDN/server level, fix mixed content warnings, Secure flag on cookies ❌ Including subdomains in HSTS is permanent — misconfigured subdomain bricks it. Never preload HSTS without testing thoroughly MDN HSTS
SSRF Awareness (System Design) Server-Side Request Forgery — attacker causes your backend to fetch an unintended internal/external URL (e.g., AWS metadata endpoint 169.254.169.254). Frontend devs need to understand this for system design reviews Burp Suite, OWASP ZAP Frontend design responsibility: never send raw user-provided URLs to server-side fetch without validation; backend must whitelist allowed domains/protocols ❌ A frontend input field that sends a user-provided URL to a server-side fetch (e.g., "paste any URL to preview") is a classic SSRF vector OWASP SSRF
Storage Security Different storage mechanisms have different security characteristics. Choosing the wrong one exposes tokens to XSS or CSRF Chrome DevTools → Application tab HttpOnly + Secure cookies (not readable by JS — XSS-safe) for session tokens. Avoid storing JWTs in localStorage (readable by JS — XSS-vulnerable). sessionStorage for ephemeral state ❌ Most common mistake: storing JWT access tokens in localStorage for "convenience" — any XSS steals all tokens permanently. Use HttpOnly cookie + short-lived access token instead OWASP Session Management

Browser Storage & Caching

Topic Core Concepts & Mental Model Tools & Libraries Key Techniques Tradeoffs & Failure Modes Resources
localStorage Key-value string storage. Persists across browser sessions and tabs. Synchronous (blocks main thread for large data) localStorage API Store non-sensitive user preferences (theme, language). Always serialize with JSON.stringify/parse. Check storage quota (~5MB) ❌ Synchronous reads block main thread; accessible to all JS on the page (XSS risk); no expiry mechanism — you must implement TTL manually; not suitable for sensitive data MDN localStorage
sessionStorage Same API as localStorage but data is cleared when the tab closes. Isolated per tab sessionStorage API Ephemeral state that should not persist: wizard form progress, temp search filters, one-time tokens ❌ Not shared across tabs — cannot use for cross-tab communication; lost on refresh in some cases; same XSS risk as localStorage MDN sessionStorage
Cookies Small key-value strings sent with every HTTP request automatically by the browser. The only storage type that participates in HTTP document.cookie (avoid), js-cookie library, server-set Set-Cookie header HttpOnly (not readable by JS), Secure (HTTPS only), SameSite=Strict/Lax, Expires/Max-Age, Path scoping. Set from server for sensitive cookies ❌ Sent with every request (bandwidth cost); 4KB size limit; document.cookie API is painful to use raw; third-party cookies being phased out MDN Set-Cookie
IndexedDB Asynchronous NoSQL database in the browser. No size limit (beyond disk). Supports indexes, transactions, and structured data (not just strings) IndexedDB API (raw), Dexie.js (Promise-based wrapper) Use for large datasets (offline data, cached API responses, file blobs). Dexie simplifies schema migration, versioning, and querying ❌ Raw API is verbose and complex — always use Dexie or similar wrapper; no automatic sync with server; schema migrations require version bumps MDN IndexedDB, Dexie.js
HTTP Caching Browser and CDN caching controlled by response headers. Multi-level: memory cache → disk cache → service worker → CDN → origin Chrome DevTools Network tab (check from cache) Cache-Control (controls who caches and how long), ETag + If-None-Match (conditional validation requests — 304 Not Modified), Last-Modified + If-Modified-Since, s-maxage for CDN vs browser ❌ Priority order: Cache-Control > Expires > heuristic. Stale CDN cache is the #1 "works in dev, broken in prod" bug. no-cache ≠ "don't cache" — it means "revalidate before using cache" MDN Cache-Control, web.dev HTTP cache
Service Worker A JavaScript proxy that runs in a background thread, intercepts all network requests, and decides: serve from cache or go to network. Required for PWA and offline Workbox (abstracts SW caching strategies), sw.js (custom) Lifecycle: install (precache) → activate (clean old caches) → fetch (intercept). Strategies: Cache-first, Network-first, Stale-while-revalidate, Network-only, Cache-only ❌ Requires HTTPS (except localhost); bugs in SW can hard-brick your app (bad cache = stuck forever); tricky to update (old SW serves old cache until all tabs close); test with DevTools → Application → Service Workers MDN Service Worker, Workbox docs
API Caching (Client-side) Cache server responses in memory on the client to avoid redundant network requests. Different from HTTP caching — this is application-level TanStack Query, SWR, Apollo Client staleTime (how long data is considered fresh), cacheTime/gcTime (how long to keep in memory), refetchOnWindowFocus, invalidateQueries on mutation, Apollo fetch-policy ❌ Stale cache after mutation = UI shows old data; cache key collisions from non-unique query keys; memory leak if cache is never garbage-collected TanStack Query caching
Data Normalization Store entities flat (like a relational DB) — indexed by ID, no nesting. Prevents duplication and sync bugs across components Redux Toolkit createEntityAdapter, custom normalized store entities: { users: {1: {...}, 2: {...}}, posts: {...} } + ids: [1, 2]. Reference by ID, not by embedding ❌ Requires upfront schema design; denormalization needed at read time (selectors); unfamiliar to devs used to raw API response shapes Redux normalization guide

Logging & Monitoring