Techcookies

Frontend System Design — Complete Interview Guide

Architecture, Design Patterns | Wed Apr 29 2026 | 23 min read

Frontend System Design — Complete Interview Guide

A comprehensive guide covering famous interview questions and in-depth explanations of all core system design topics for mid and senior frontend engineers.


Table of Contents

  1. Famous Interview Questions — Mid Level
  2. Famous Interview Questions — Senior Level
  3. Level Comparison Table
  4. System Design Topics with Solutions

🟡 Mid-Level Questions

UI Components

  • Design an Autocomplete / Typeahead search box
  • Design an Infinite Scroll feed
  • Design a Modal Dialog system (stacking, focus trap, a11y)
  • Design a Toast / Notification system
  • Design a Drag and Drop interface (like Trello cards)
  • Design a File Uploader with progress, retry, multi-file support
  • Design a Rich Text Editor (basic, like a comment box)
  • Design a Date Picker component

Application-Level

  • Design a Twitter/Facebook News Feed UI
  • Design a Google Search Results page
  • Design an E-commerce Product Listing page with filters, sorting, pagination
  • Design a Shopping Cart with persistence
  • Design a YouTube Video Page (player, comments, recommendations)
  • Design a Dashboard with Charts (analytics UI)
  • Design a Multi-step Form / Wizard

Core Concepts Often Asked

  • How would you design a client-side routing system (like React Router)?
  • Design a polling or WebSocket notification system
  • Design a form validation library

🔴 Senior-Level Questions

Collaborative & Real-Time

  • Design Google Docs — real-time collaboration, conflict resolution (OT/CRDT)
  • Design a Figma-like Drawing Tool — canvas, multi-user cursors, vector ops
  • Design a Slack / WhatsApp chat — presence, delivery receipts, message threading
  • Design Google Meet / Zoom UI — WebRTC, participants grid, screen share

Large-Scale Feed & Social

  • Design Facebook News Feed at scale — ranking, pagination, real-time updates, ads
  • Design Instagram / Pinterest photo feed — lazy loading, virtual list, media optimization
  • Design LinkedIn Feed — relevance, infinite scroll, skeleton loading
  • Design Google Autocomplete — debounce, caching, keyboard navigation, network cancellation
  • Design Amazon Search — filters, faceted search, result ranking UI
  • Design a Type-ahead with fuzzy matching at scale

Developer Tools / Platforms

  • Design CodeSandbox / StackBlitz — in-browser code editor, live preview, file system
  • Design a Monitoring Dashboard (like Datadog) — real-time charts, alerting UI
  • Design a Feature Flag Management UI
  • Design an A/B Testing Framework on the frontend

Architecture Questions

  • Design a Micro-Frontend architecture for a large enterprise app
  • Design a Design System & Component Library from scratch (versioning, theming, a11y)
  • Design a PWA / Offline-first app — service workers, sync strategies, IndexedDB
  • Design a Frontend for a Maps app (like Google Maps) — tile rendering, markers, zoom

Performance & Infrastructure

  • Design a CDN + asset delivery strategy for a global frontend app
  • Design a Client-side caching system (HTTP cache, SWR, normalization)
  • Design a Web performance monitoring SDK (tracking CWV, errors, user sessions)
  • Design a Virtual List / Windowing system for 100k+ rows

🎯 Level Comparison

Question Mid Senior
Autocomplete component ✅ (at scale, distributed)
News Feed ✅ (real-time, ranking, ads)
Chat UI ✅ (WebRTC, threading, presence)
Google Docs
Micro-Frontend
Drawing Tool (Figma)
PWA / Offline
Design System

💡 Top 5 Must-Practice Questions

The most commonly asked across Google, Meta, Amazon, Flipkart, and Uber:

  1. News Feed — pagination, real-time updates, state management
  2. Autocomplete — debounce, caching, accessibility, network handling
  3. Chat UI — WebSockets, optimistic updates, presence
  4. Google Docs — CRDT/OT, real-time sync, conflict resolution
  5. E-commerce Listing — filters, performance, SEO, infinite scroll

🧱 System Design Topics


1. Architecture Patterns

Component-Driven Architecture

Break UI into small, reusable, self-contained components. Each component owns its markup, styles, and logic.

Solution:

  • Use Atomic Design methodology: Atoms → Molecules → Organisms → Templates → Pages
  • Components should follow Single Responsibility Principle — one component, one job
  • Use composition over inheritance — build complex UIs by combining simple components
  • Keep components stateless where possible, lift state up only when needed
  • Use Storybook to develop and document components in isolation
atoms/       → Button, Input, Label, Icon
molecules/   → SearchBar (Input + Button), FormField (Label + Input)
organisms/   → Header (Logo + Nav + SearchBar), ProductCard
templates/   → PageLayout, DashboardLayout
pages/       → HomePage, ProductPage

Micro-Frontend Architecture

Split a large frontend monolith into smaller, independently deployable applications owned by different teams.

Solution:

  • Module Federation (Webpack 5) — most popular approach; host app dynamically loads remote apps at runtime
  • iframes — maximum isolation but poor UX, hard to share state
  • Web Components — framework-agnostic custom elements that can be embedded anywhere
  • Single-SPA — meta-framework that orchestrates multiple SPAs on one page

Key concerns to address:

  • Shared dependencies (avoid loading React twice)
  • Cross-app communication (custom events, shared state store, URL params)
  • Consistent design via a shared Design System
  • Independent CI/CD pipelines per micro-frontend
  • Routing ownership — who controls the URL?
Shell App (host)
├── /dashboard  → loaded from Team A's remote
├── /orders     → loaded from Team B's remote
└── /profile    → loaded from Team C's remote

Monorepo vs Polyrepo

Monorepo Polyrepo
Code sharing Easy (shared packages) Harder (publish to npm)
CI/CD Slower (builds all) Faster per repo
Tooling Nx, Turborepo, Lerna Standard per repo
Team autonomy Less More

Solution: Use Nx or Turborepo for monorepos — they enable incremental builds and affected-only testing, making them fast at scale.

State Architecture Patterns (Flux / MVC)

  • Flux/Redux — unidirectional data flow: Action → Dispatcher → Store → View
  • MVC — Model owns data, View renders it, Controller handles input (common in Angular)
  • MVVM — Model + ViewModel (reactive bindings) + View (used in Vue, Knockout)

2. Performance

Core Web Vitals (CWV)

Metric Measures Good Threshold
LCP (Largest Contentful Paint) Loading performance < 2.5s
INP (Interaction to Next Paint) Responsiveness < 200ms
CLS (Cumulative Layout Shift) Visual stability < 0.1

Solutions:

  • LCP — preload hero images (<link rel="preload">), use SSR/SSG, optimize server response time, avoid render-blocking resources
  • INP — break long tasks with scheduler.yield(), defer non-critical JS, use web workers for heavy computation
  • CLS — always set explicit width and height on images/videos, avoid inserting content above existing content, use CSS aspect-ratio

Code Splitting & Lazy Loading

Solution:

  • Split at route level — each page is a separate chunk loaded on navigation
  • Split at component level — heavy components (charts, editors) loaded on demand
  • Use React.lazy() + Suspense for component-level splitting
  • Use dynamic import() for library-level splitting
js
// Route-level splitting
const Dashboard = React.lazy(() => import('./Dashboard'));

// Library-level splitting (load heavy lib only when needed)
button.addEventListener('click', async () => {
  const { default: Chart } = await import('chart.js');
  new Chart(ctx, config);
});

Virtual Lists / Windowing

Rendering 10,000 DOM nodes tanks performance. Windowing renders only visible items.

Solution:

  • Use react-window or react-virtual — only renders items in the viewport
  • Maintain a scroll container with fixed height, calculate which items are visible based on scroll offset
  • Use position: absolute with calculated top offsets for each item
  • For dynamic heights, measure items with a ResizeObserver and cache heights
Viewport (600px visible)
├── Item 50  ← rendered
├── Item 51  ← rendered
├── Item 52  ← rendered  (visible)
└── Item 53  ← rendered
    [Items 0-49 and 54-10000 = NOT in DOM]

Bundle Optimization

  • Tree shaking — remove unused exports (works with ES modules + Rollup/Webpack)
  • Minification — Terser for JS, cssnano for CSS
  • Compression — Brotli > Gzip for text assets; configure on CDN/server
  • Differential serving — serve modern JS (ES2020) to modern browsers, legacy bundle to old ones
  • Preload / Prefetch<link rel="preload"> for critical assets, <link rel="prefetch"> for next-page assets

Critical Rendering Path

Browser steps: HTML parse → DOM → CSSOM → Render Tree → Layout → Paint → Composite

Solution to optimize:

  • Inline critical CSS, defer non-critical CSS
  • Move <script> tags to bottom or use defer / async
  • Minimize DOM size (< 1500 nodes recommended)
  • Avoid forced synchronous layouts (reading layout properties after writes)

3. State Management

When to Use What

State Type Tool Example
Local UI state useState Modal open/close
Shared UI state Context API / Zustand Theme, sidebar
Server/async state React Query / SWR API data, cache
Complex global state Redux Toolkit Large apps, time-travel debug
Form state React Hook Form Form inputs, validation

Server State vs Client State

  • Client state — UI-only data that lives in the browser (modal open, selected tab)
  • Server state — data fetched from an API that needs caching, refetching, synchronization

Solution: Separate them clearly. Don't store server data in Redux — use React Query or SWR which give you caching, background refetch, stale-while-revalidate, optimistic updates out of the box.

Redux Architecture (when needed)

Component dispatches Action
    → Middleware (Thunk/Saga) handles async
    → Reducer updates Store
    → Selector derives data
    → Component re-renders

Use Redux Toolkit (RTK) to avoid boilerplate. Use RTK Query for server state within Redux ecosystem.

Zustand (lightweight alternative)

js
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

No Provider needed, minimal boilerplate, great for mid-sized apps.


4. Networking & APIs

REST vs GraphQL on the Frontend

REST GraphQL
Over-fetching Common Avoided (request only needed fields)
Under-fetching Multiple round trips Single query
Caching Easy (HTTP cache) Complex (normalize by ID)
Type safety Manual Auto-generated (codegen)
Best for Simple CRUD APIs Complex, nested data (social feeds)

Polling vs WebSockets vs SSE

Polling WebSockets Server-Sent Events
Direction Client → Server Bidirectional Server → Client only
Overhead High (repeated requests) Low (persistent connection) Low
Use case Infrequent updates Chat, live collaboration Live feeds, notifications
Reconnection Automatic Manual Automatic

Solution for choosing:

  • Notifications / live feed → SSE (simpler, auto-reconnect, HTTP/2 compatible)
  • Chat / collaborative editing → WebSockets
  • Infrequent status checks → Polling with exponential backoff

Optimistic UI Updates

Show the result of an action immediately without waiting for the server, then reconcile.

Solution:

User clicks "Like"
→ Immediately update UI (likes: 10 → 11)
→ Send API request in background
→ On success: keep the UI as is
→ On failure: roll back to previous state (likes: 11 → 10) + show error toast

React Query and SWR both have built-in onMutate / rollback hooks for this pattern.

Request Deduplication & Cancellation

  • Deduplication — if the same request fires twice (double click), execute only once
  • Cancellation — cancel in-flight requests when component unmounts or user types again (autocomplete)

Solution:

js
// Cancellation with AbortController
const controller = new AbortController();
fetch('/api/search?q=' + query, { signal: controller.signal });

// Cancel on next keystroke
return () => controller.abort();

React Query deduplicates identical queries automatically within the same render cycle.


5. Caching

HTTP Caching

Header Behavior
Cache-Control: max-age=3600 Cache for 1 hour
Cache-Control: no-cache Must revalidate with server each time
Cache-Control: immutable Never revalidate (for hashed assets)
ETag Server sends hash; browser sends it back to check if changed

Solution for static assets: Use content hashing in filenames (main.a3f9c2.js). Set Cache-Control: max-age=31536000, immutable. When file changes, hash changes → new URL → no cache invalidation needed.

Client-Side Caching with SWR / React Query

Both implement stale-while-revalidate: return cached data immediately, then fetch fresh data in background and update UI.

First visit:  fetch from network → store in cache → render
Second visit: render from cache instantly → refetch in background → update if changed

Configuration:

  • staleTime — how long data is considered fresh (no background refetch)
  • cacheTime — how long unused data stays in memory
  • refetchOnWindowFocus — refetch when user returns to tab

Data Normalization

Avoid storing the same entity in multiple places (causes stale data bugs).

Solution: Normalize by ID, like a database:

js
// Instead of this (denormalized):
{ posts: [{ id: 1, author: { id: 99, name: "Alice" } }] }

// Store this (normalized):
{
  posts: { 1: { id: 1, authorId: 99 } },
  users: { 99: { id: 99, name: "Alice" } }
}

Apollo Client and RTK Query do this automatically. For manual normalization, use normalizr.

Service Worker Caching Strategies

Strategy How it works Best for
Cache First Serve from cache, fallback to network Static assets, fonts
Network First Try network, fallback to cache API responses
Stale While Revalidate Serve cache, update in background Non-critical content
Cache Only Only serve from cache Offline-only content
Network Only Always hit network Real-time data

6. Security

XSS (Cross-Site Scripting)

Attacker injects malicious scripts into your page that execute in other users' browsers.

Solution:

  • Never use innerHTML, dangerouslySetInnerHTML with user input
  • Sanitize all user-generated content with DOMPurify before rendering
  • Set Content-Security-Policy header to restrict which scripts can execute
  • Use HttpOnly cookies so JS cannot access auth tokens
js
// Dangerous
element.innerHTML = userInput;

// Safe
element.textContent = userInput;
// Or sanitize if HTML is needed:
element.innerHTML = DOMPurify.sanitize(userInput);

CSRF (Cross-Site Request Forgery)

Attacker tricks a logged-in user into making unintended requests to your server.

Solution:

  • Use SameSite=Strict or SameSite=Lax cookie attribute — prevents cookies from being sent on cross-site requests
  • Use CSRF tokens — server issues a token, client sends it with every state-changing request
  • Verify Origin / Referer headers on the server

Secure Token Storage

Storage XSS Risk CSRF Risk Recommendation
localStorage High (JS accessible) None Avoid for auth tokens
sessionStorage High (JS accessible) None Avoid for auth tokens
HttpOnly Cookie None (JS can't read) Medium Best option
Memory (JS variable) Low None Good but lost on refresh

Best practice: Store access tokens in memory, refresh tokens in HttpOnly + SameSite=Strict cookies.

Content Security Policy (CSP)

HTTP header that tells the browser which sources are trusted.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.trusted.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';

7. Accessibility (a11y)

WCAG 2.1 AA — Key Principles (POUR)

  • Perceivable — content must be presentable in ways users can perceive (alt text, captions)
  • Operable — all functionality available via keyboard
  • Understandable — content and UI must be understandable
  • Robust — content must work with current and future assistive technologies

ARIA Roles & Attributes

Use ARIA only when native HTML semantics aren't sufficient.

html
<!-- Native HTML (always prefer this) -->
<button>Submit</button>

<!-- ARIA when custom component is needed -->
<div role="button" tabindex="0" aria-label="Submit form"
     aria-pressed="false" onkeydown="handleKey(event)">
  Submit
</div>

Common ARIA patterns:

  • aria-live="polite" — announce dynamic content changes to screen readers
  • aria-expanded — for accordions, dropdowns
  • aria-describedby — link an input to its error message
  • role="dialog" + aria-modal="true" — for modals

Focus Management

Critical for modals, drawers, and SPAs.

Solution:

  • Focus trap in modals — Tab key should cycle only within the modal
  • Restore focus — when modal closes, return focus to the element that opened it
  • Skip links<a href="#main">Skip to main content</a> at top of page for keyboard users
  • On route change in SPAs — move focus to page heading or main landmark

Every interactive element must be reachable and operable via keyboard:

  • Tab / Shift+Tab — navigate between focusable elements
  • Enter / Space — activate buttons, links
  • Arrow keys — navigate within components (menus, tabs, listboxes)
  • Escape — close dialogs, dropdowns

8. Internationalization (i18n)

Translation Management

Solution:

  • Use i18next or react-intl — industry standard libraries
  • Store translations in JSON files per locale: en.json, hi.json, ar.json
  • Use translation keys, never hardcode strings: t('nav.home') not "Home"
  • Use a Translation Management System (TMS) like Lokalise or Crowdin for large teams
  • Support pluralization rules (different languages have different plural forms)
json
// en.json
{ "items": "{{count}} item", "items_plural": "{{count}} items" }

// ar.json (Arabic has 6 plural forms)
{ "items_0": "لا عناصر", "items_1": "عنصر واحد", ... }

RTL (Right-to-Left) Support

Arabic, Hebrew, Persian, Urdu are RTL languages.

Solution:

  • Set dir="rtl" on <html> tag
  • Use CSS logical properties instead of physical ones:
    • margin-inline-start instead of margin-left
    • padding-inline-end instead of padding-right
    • border-inline-start instead of border-left
  • Use CSS writing-mode for vertical scripts if needed
  • Flip icons that have directional meaning (back arrow, play button)
  • Test with actual RTL content — don't just mirror the UI

Date, Time & Number Formatting

Never format manually. Use the browser's Intl API:

js
// Date formatting
new Intl.DateTimeFormat('hi-IN', { dateStyle: 'full' }).format(new Date());
// → "शनिवार, 29 अप्रैल 2026"

// Number formatting
new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(100000);
// → "₹1,00,000.00"

// Relative time
new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(-1, 'day');
// → "yesterday"

9. Rendering Strategies

CSR vs SSR vs SSG vs ISR

Strategy Renders at First Load SEO Dynamic Data
CSR (Client-Side) Browser, on request Slow (blank page) Poor Yes
SSR (Server-Side) Server, on request Fast (full HTML) Great Yes
SSG (Static Generation) Build time Fastest (pre-built) Great No (at build time)
ISR (Incremental Static) Build + background regen Fast Great Partially

When to use:

  • CSR → Dashboards, admin panels, apps behind auth (SEO doesn't matter)
  • SSR → News sites, e-commerce PDPs, personalized pages
  • SSG → Blogs, marketing pages, documentation
  • ISR → E-commerce category pages, content that changes every few minutes

Hydration

SSR sends fully rendered HTML → browser loads JS → React "hydrates" by attaching event listeners to existing DOM.

Problems:

  • Hydration mismatch — server HTML doesn't match client render → React errors
  • All-or-nothing hydration — entire page must hydrate before any interaction

Solutions:

  • Partial hydration — only hydrate interactive components (islands architecture — Astro)
  • Streaming SSR (React 18) — stream HTML in chunks, hydrate as chunks arrive
  • Selective hydration — high-priority interactions hydrate first

Edge Rendering

Run server-side rendering at CDN edge nodes (close to user) instead of a central server.

Solution:

  • Use Next.js Edge Runtime, Vercel Edge Functions, or Cloudflare Workers
  • Reduces latency from ~200ms (central server) to ~20ms (edge node)
  • Limitation: Edge runtime is restricted (no Node.js APIs, limited memory)

10. Offline & PWA

Service Workers

A JS file that runs in the background, separate from the page, intercepting network requests.

Browser → Service Worker → Cache / Network

Lifecycle:

  1. install — cache static assets
  2. activate — clean up old caches
  3. fetch — intercept requests and apply caching strategy
js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});

IndexedDB

Browser-based NoSQL database for storing large amounts of structured data offline.

Solution: Use Dexie.js (wrapper around IndexedDB with a clean API):

js
const db = new Dexie('MyApp');
db.version(1).stores({ drafts: '++id, title, content, updatedAt' });

// Save draft offline
await db.drafts.add({ title: 'My Post', content: '...', updatedAt: Date.now() });

// Sync when back online
window.addEventListener('online', () => syncDraftsToServer());

Background Sync

Queue failed requests and replay them when connectivity is restored.

Solution:

js
// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-posts') {
    event.waitUntil(syncPendingPosts());
  }
});

// Register sync from page
navigator.serviceWorker.ready.then((sw) => {
  sw.sync.register('sync-posts');
});

PWA Checklist

  • ✅ HTTPS
  • ✅ Web App Manifest (manifest.json) with icons and display: standalone
  • ✅ Service Worker registered
  • ✅ Offline fallback page
  • ✅ Responsive design
  • ✅ Fast load (LCP < 2.5s)

11. Real-Time Systems

WebSocket Architecture

Client ←——— persistent TCP connection ———→ Server
         (bidirectional, low latency)

Solution for production:

  • Handle reconnection with exponential backoff
  • Implement heartbeat / ping-pong to detect dead connections
  • Use rooms / channels to scope messages (don't broadcast everything to everyone)
  • Scale with Redis Pub/Sub — multiple WebSocket servers subscribe to Redis channels
js
// Client with reconnection
function connect() {
  const ws = new WebSocket('wss://api.example.com/ws');
  ws.onclose = () => setTimeout(connect, Math.min(backoff * 2, 30000));
  return ws;
}

CRDT (Conflict-free Replicated Data Types)

Algorithm for merging concurrent edits without conflicts. Used in Google Docs, Figma, Notion.

How it works:

  • Every operation is designed so that applying it in any order gives the same result
  • No need for a central server to resolve conflicts
  • Types: LWW (Last-Write-Wins), OR-Set, Yjs (tree-based CRDT for text)

Solution: Use Yjs library:

js
const ydoc = new Y.Doc();
const ytext = ydoc.getText('content');

// Connect to WebSocket sync provider
const provider = new WebsocketProvider('wss://sync.example.com', 'room-id', ydoc);

// Bind to editor (e.g., Quill, CodeMirror, TipTap)
const binding = new QuillBinding(ytext, quill, provider.awareness);

Operational Transformation (OT)

Alternative to CRDT. Server transforms operations against each other to resolve conflicts.

How it works:

  • Client A and Client B both edit the same document
  • Both send operations to the server
  • Server transforms B's operation relative to A's (already applied) and vice versa
  • Used by Google Docs (originally)

CRDT vs OT:

CRDT OT
Server coordination Not required Required
Complexity Higher data overhead Complex transformation logic
Offline support Excellent Limited
Adoption Newer (Figma, Notion) Older (Google Docs)

12. Testing Strategy

Testing Pyramid

         [E2E Tests]          ← Few, slow, expensive (Playwright, Cypress)
       [Integration Tests]    ← Some (React Testing Library)
     [Unit Tests]             ← Many, fast, cheap (Jest, Vitest)

Unit Testing

Test individual functions and components in isolation.

js
// Testing a utility function
test('formatCurrency formats INR correctly', () => {
  expect(formatCurrency(100000, 'INR')).toBe('₹1,00,000');
});

// Testing a React component
render(<Button onClick={mockFn}>Click me</Button>);
userEvent.click(screen.getByRole('button'));
expect(mockFn).toHaveBeenCalledOnce();

Integration Testing

Test how components work together. Use React Testing Library — tests from user's perspective.

js
test('user can search and see results', async () => {
  render(<SearchPage />);
  await userEvent.type(screen.getByRole('searchbox'), 'React');
  await waitFor(() => {
    expect(screen.getByText('React Tutorial')).toBeInTheDocument();
  });
});

E2E Testing (Playwright)

Test full user flows in a real browser.

js
test('user can complete checkout', async ({ page }) => {
  await page.goto('/products');
  await page.click('[data-testid="add-to-cart"]');
  await page.click('[data-testid="checkout"]');
  await page.fill('#card-number', '4242424242424242');
  await page.click('[data-testid="pay"]');
  await expect(page.locator('.success-message')).toBeVisible();
});

Visual Regression Testing

Catch unintended UI changes by comparing screenshots.

Solution: Use Chromatic (with Storybook) or Percy — takes screenshots of components, diffs against baseline, flags changes for review.

Performance Testing

  • Lighthouse CI — run Lighthouse on every PR, fail if scores drop below threshold
  • WebPageTest — detailed waterfall analysis, filmstrip view
  • bundlesize — fail CI if bundle exceeds a size limit

Accessibility Testing

  • axe-core — automated a11y linter (catches ~30% of issues)
  • @axe-core/react — run axe in development, log violations to console
  • jest-axe — run axe in unit tests
js
test('should have no accessibility violations', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

13. DevOps & Deployment

CI/CD for Frontend

Code Push → Lint & Type Check → Unit Tests → Build → E2E Tests → Deploy

Solution with GitHub Actions:

  • Run ESLint + TypeScript on every PR
  • Run Jest unit tests with coverage threshold
  • Build and run Playwright E2E tests
  • Deploy preview to Vercel / Netlify per PR
  • Merge to main → deploy to production

Feature Flags

Decouple deployment from release. Ship code to production but enable features only for specific users.

Solution:

  • Use LaunchDarkly, Flagsmith, or Unleash
  • Evaluate flags server-side (SSR) to avoid layout shift
  • Use for: A/B testing, gradual rollouts, kill switches, beta features
js
const isNewDashboardEnabled = await flagsmith.isFeatureEnabled('new_dashboard');

return isNewDashboardEnabled ? <NewDashboard /> : <OldDashboard />;

A/B Testing Infrastructure

Run controlled experiments to measure impact of UI changes.

Solution:

  1. Assign variant — hash user ID to bucket consistently (same user always gets same variant)
  2. Expose variant — render correct UI, avoid layout shift
  3. Track events — log interactions with variant info attached
  4. Analyze — statistical significance testing (p-value < 0.05)
js
const variant = getVariant(userId, 'checkout_button_color'); // 'control' | 'treatment'
const buttonColor = variant === 'treatment' ? 'green' : 'blue';

CDN Strategy & Cache Invalidation

Solution:

  • Serve all static assets (JS, CSS, images) from CDN
  • Use content hashing in filenames → immutable caching (Cache-Control: max-age=31536000, immutable)
  • HTML files → short cache or no-cache (they reference hashed assets)
  • Images → use CDN image transformation (resize, format conversion on the fly)
  • Cache invalidation → deploy new file with new hash; old URL still serves old cached version safely

Monitoring & Error Tracking

Tool Purpose
Sentry JS error tracking, stack traces, user context
Datadog RUM Real User Monitoring, performance metrics
LogRocket Session replay, console logs, network requests
Lighthouse CI Automated performance audits in CI

Solution for error tracking:

js
Sentry.init({ dsn: '...', tracesSampleRate: 0.1 }); // 10% of transactions

// Capture custom errors
try {
  await processPayment();
} catch (error) {
  Sentry.captureException(error, { extra: { userId, cartId } });
}

Last updated: April 2026