1. Modern Frontend Architecture Patterns

1.1 Micro-Frontend Module Federation

Concept Technology Description Use Case
Module Federation NEW @module-federation/runtime Webpack 5 feature enabling dynamic runtime module sharing across independently deployed applications Large-scale applications with multiple teams, independent deployments
Remote Entry remoteEntry.js Manifest file exposing modules from host application to consumers Sharing components/utilities between micro-frontends
Shared Dependencies shared: {react, react-dom} Single instance of common libraries shared across federated modules to reduce bundle size Preventing duplicate React instances, reducing memory footprint
Host Application ModuleFederationPlugin Container application that consumes remote modules from other micro-frontends Main shell application orchestrating multiple features
Container Strategy webpack.config.js Configuration pattern for exposing and consuming federated modules Cross-team component sharing, versioning strategy

Example: Module Federation Configuration

// webpack.config.js - Host Application
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
        app2: 'app2@http://localhost:3002/remoteEntry.js'
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

// webpack.config.js - Remote Application
new ModuleFederationPlugin({
  name: 'app1',
  filename: 'remoteEntry.js',
  exposes: {
    './Button': './src/components/Button',
    './Header': './src/components/Header'
  },
  shared: ['react', 'react-dom']
});
Benefits: Independent deployments, team autonomy, technology agnostic, runtime composition, incremental upgrades, shared dependencies

1.2 Component-Based React Vue Angular

Framework Component Model Key Features Architecture Pattern
React function Component() JSX, Virtual DOM, Hooks, Unidirectional data flow, Component composition UI = f(state) - Functional reactive paradigm
Vue 3 <template><script> Composition API, Reactivity system, Single-file components, Template syntax Progressive framework with reactive data binding
Angular @Component TypeScript-first, Dependency injection, RxJS, Decorators, Two-way binding Full-featured framework with opinionated structure
Component Lifecycle mount/update/unmount Predictable lifecycle hooks for initialization, updates, cleanup Consistent patterns across frameworks
Props/Events Pattern props down, events up Parent-to-child data via props, child-to-parent communication via events Unidirectional data flow maintaining predictability

React Pattern

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Vue Pattern

<template>
  <button @click="$emit('click')">
    <slot />
  </button>
</template>

<script setup>
defineEmits(['click']);
</script>

Angular Pattern

@Component({
  selector: 'app-button',
  template: `
    <button (click)="onClick.emit()">
      <ng-content></ng-content>
    </button>
  `
})
class ButtonComponent {
  @Output() onClick = new EventEmitter();
}

1.3 Jamstack Static Site Generation

Concept Implementation Description Benefits
Pre-rendering getStaticProps() Generate static HTML at build time from data sources (CMS, APIs, files) Instant page loads, optimal SEO, reduced server costs
Static Site Generator Next.js, Gatsby, Astro Build-time HTML generation with client-side hydration for interactivity Fast initial load, CDN distribution, security by obscurity
Incremental Static Regeneration NEW revalidate: 60 Update static pages after deployment without full rebuild (Next.js) Fresh content without sacrificing static benefits
API Routes /api/endpoint Serverless functions handling dynamic operations (forms, auth, webhooks) Static frontend with dynamic backend capabilities
Headless CMS Contentful, Strapi Content decoupled from presentation, consumed via APIs at build time Content flexibility, multi-channel distribution

Example: Next.js Static Site Generation

// pages/blog/[slug].js
export async function getStaticPaths() {
  const posts = await fetchAllPosts();
  return {
    paths: posts.map(post => ({ params: { slug: post.slug } })),
    fallback: 'blocking' // ISR for new posts
  };
}

export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);
  return {
    props: { post },
    revalidate: 3600 // Regenerate every hour
  };
}

export default function BlogPost({ post }) {
  return <article>{post.content}</article>;
}

Jamstack Architecture Benefits

  • Performance: Pre-rendered HTML served from CDN edge locations
  • Security: No database or server vulnerabilities, reduced attack surface
  • Scalability: Static files scale infinitely via CDN
  • Developer Experience: Modern tooling, Git-based workflows, preview deployments

1.4 Island Architecture Astro Qwik

Concept Framework Description Performance Impact
Islands Architecture NEW Astro, Fresh Static HTML with interactive "islands" of components that hydrate independently Minimal JavaScript, selective hydration, faster initial load
Partial Hydration client:load Only hydrate components that require interactivity, leave static content as HTML 90% less JavaScript vs traditional SPA
Resumability BETA Qwik Framework Serialize application state on server, resume on client without hydration Zero JavaScript for static content, instant interactivity
Progressive Enhancement client:visible Hydrate components when they enter viewport (lazy hydration) Faster Time to Interactive (TTI), reduced main thread work
Framework Agnostic React, Vue, Svelte Mix different frameworks in same project, each island independent Use best tool per component, incremental migration

Example: Astro Islands Architecture

---
// src/pages/index.astro
import Header from '../components/Header.astro';
import ReactCounter from '../components/Counter.jsx';
import VueCarousel from '../components/Carousel.vue';
---

<html>
  <body>
    <!-- Static HTML, no JS -->
    <Header />
    
    <!-- Interactive island, hydrates on load -->
    <ReactCounter client:load />
    
    <!-- Lazy hydration when visible -->
    <VueCarousel client:visible />
    
    <!-- No hydration, static HTML -->
    <footer>Static Footer</footer>
  </body>
</html>

Astro Directives

Directive Behavior
client:load Hydrate on page load
client:idle Hydrate when main thread idle
client:visible Hydrate when in viewport
client:media Hydrate on media query match
client:only Skip SSR, client-side only

Qwik Resumability

// Qwik Component
export const Counter = component$(() => {
  const count = useSignal(0);
  
  return (
    <button onClick$={() => count.value++}>
      {count.value}
    </button>
  );
});

// Serialized state resumes without hydration
// onClick$ creates lazy-loaded event listener

1.5 Server Components Next.js 13

Concept Syntax Description Benefits
React Server Components NEW export default async function Components that render on server, send HTML to client, never hydrate Zero JavaScript bundle, direct backend access, streaming
Client Components 'use client' Traditional React components with interactivity, hooks, browser APIs Explicit boundary for client-side JavaScript
Async Components await fetch() Server components can be async, directly fetch data without useEffect Cleaner code, automatic request deduplication
Streaming SSR <Suspense> Send HTML progressively as components render, show fallback for slow parts Faster Time to First Byte (TTFB), progressive enhancement
Server Actions BETA 'use server' Functions that run on server, called from client components like RPC Type-safe server mutations, no API routes needed

Example: Server Components with Streaming

// app/page.js - Server Component (default)
export default async function Page() {
  // Direct database/API access, no client bundle impact
  const data = await db.query('SELECT * FROM posts');
  
  return (
    <main>
      <h1>Posts</h1>
      <Suspense fallback={<Loading />}>
        <PostList data={data} />
      </Suspense>
    </main>
  );
}

// components/InteractiveButton.js - Client Component
'use client';
import { useState } from 'react';

export default function InteractiveButton() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// Server Action
async function createPost(formData) {
  'use server';
  const title = formData.get('title');
  await db.insert({ title });
  revalidatePath('/posts');
}
Important: Server Components cannot use browser APIs, hooks (useState, useEffect), or event handlers. Use 'use client' directive for interactive components.

1.6 Edge-First Architecture Vercel

Concept Platform Description Use Case
Edge Functions Vercel, Cloudflare Workers Lightweight serverless functions running at CDN edge locations globally Low-latency API responses, personalization, A/B testing
Edge Middleware middleware.js Intercept requests before they reach application, run logic at edge Authentication, redirects, geolocation, feature flags
Edge Runtime export const runtime = 'edge' Subset of Node.js APIs, optimized for fast cold starts (<10ms) Dynamic rendering near users, streaming responses
Incremental Static Regeneration revalidate Static pages regenerate at edge on-demand, cached globally E-commerce, content sites with frequent updates
Edge Config @vercel/edge-config Ultra-fast key-value store accessible from edge functions (<1ms reads) Feature flags, A/B test configs, redirects

Example: Next.js Edge Middleware

// middleware.js - Runs at edge for all routes
import { NextResponse } from 'next/server';

export function middleware(request) {
  // Geolocation-based redirect
  const country = request.geo?.country;
  if (country === 'US') {
    return NextResponse.redirect(new URL('/us', request.url));
  }
  
  // A/B testing
  const bucket = Math.random() < 0.5 ? 'a' : 'b';
  const response = NextResponse.next();
  response.cookies.set('bucket', bucket);
  
  // Authentication check
  const token = request.cookies.get('session');
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return response;
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
};

Edge vs Serverless

Metric Edge Serverless
Cold Start <10ms 100-500ms
Location Global CDN Regional
Runtime V8 Isolate Node.js
APIs Web Standard Full Node.js

Edge Use Cases

  • Authentication & authorization
  • Bot detection & rate limiting
  • A/B testing & feature flags
  • Geolocation redirects
  • Personalized content
  • SEO optimizations
  • Request/response transformation
  • Cache control headers
Performance Benefit: Edge-first architecture reduces latency by 50-90% compared to traditional origin servers by serving content from locations close to users.

2. State Management Architecture Implementation

2.1 Redux Toolkit RTK Query

Concept API Description Use Case
Redux Toolkit NEW @reduxjs/toolkit Official opinionated Redux toolset with simplified API, built-in Immer, thunks Complex state logic, large applications, time-travel debugging
createSlice createSlice() Generate action creators and reducers automatically with less boilerplate Feature-based state management with auto-generated actions
RTK Query createApi() Data fetching and caching layer built on Redux, auto-generates hooks REST/GraphQL APIs with automatic cache invalidation
Immer Integration state.value++ Write mutable code that produces immutable updates automatically Simplified reducer logic, no spread operators needed
DevTools Extension Redux DevTools Time-travel debugging, action replay, state inspection Debugging complex state transitions, bug reproduction

Example: Redux Toolkit with RTK Query

// store/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value++; }, // Immer allows mutation
    decrement: (state) => { state.value--; },
    incrementByAmount: (state, action) => { state.value += action.payload; }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// store/api/postsApi.js - RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Post'],
  endpoints: (builder) => ({
    getPosts: builder.query({
      query: () => '/posts',
      providesTags: ['Post']
    }),
    addPost: builder.mutation({
      query: (post) => ({ url: '/posts', method: 'POST', body: post }),
      invalidatesTags: ['Post'] // Auto-refetch on mutation
    })
  })
});

export const { useGetPostsQuery, useAddPostMutation } = postsApi;

// Usage in component
function PostList() {
  const { data, isLoading, error } = useGetPostsQuery();
  const [addPost] = useAddPostMutation();
  
  if (isLoading) return <div>Loading...</div>;
  return <div>{data.map(post => <Post key={post.id} {...post} />)}</div>;
}
Key Benefits: 95% less boilerplate vs classic Redux, automatic cache invalidation, optimistic updates, request deduplication, polling support

2.2 Zustand Lightweight State Management

Feature Syntax Description Benefit
Minimal API create() Simple hook-based API without providers, actions, or reducers 3KB bundle, zero boilerplate, easy learning curve
No Provider Wrapper useStore() Direct store access without Context Provider hierarchy Cleaner component tree, no context re-render issues
Transient Updates subscribe() Subscribe to store changes without causing re-renders Performance optimization for animations, frequent updates
Selector Functions useStore(state => state.x) Fine-grained subscriptions to specific state slices Prevents unnecessary re-renders, optimized performance
Middleware Support persist, devtools Optional plugins for persistence, debugging, async actions Flexible enhancement without core complexity

Example: Zustand Store Implementation

import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

// Simple store
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}));

// With middleware (persistence + devtools)
const useBearStore = create(
  devtools(
    persist(
      (set) => ({
        bears: 0,
        addBear: () => set((state) => ({ bears: state.bears + 1 })),
        removeAllBears: () => set({ bears: 0 })
      }),
      { name: 'bear-storage' } // localStorage key
    )
  )
);

// Usage - selector prevents re-render unless count changes
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  return <button onClick={increment}>{count}</button>;
}

// Transient updates (no re-render)
useStore.subscribe(
  (state) => state.count,
  (count) => console.log('Count changed:', count)
);

Zustand vs Redux

  • Bundle Size: Zustand 3KB vs Redux Toolkit 12KB
  • Boilerplate: Single create() call vs slice/store/provider setup
  • Performance: Selective subscriptions prevent unnecessary renders
  • Use Case: Small-to-medium apps, simple state, rapid prototyping

2.3 Context API React State

Concept API Description Best Practice
createContext React.createContext() Create context object for passing data through component tree Use for infrequently changing data (theme, auth, locale)
Context Provider <Context.Provider> Wrap components to provide context value to descendants Split contexts by update frequency to minimize re-renders
useContext Hook useContext(Context) Subscribe to context value in functional components Combine with useMemo to prevent unnecessary calculations
Re-render Issue Every consumer re-renders All context consumers re-render when provider value changes Use composition, memo, or external library for optimization
Prop Drilling Solution Context + Custom Hook Avoid passing props through multiple component layers Create custom useAuth(), useTheme() hooks for clean API

Example: Optimized Context Pattern

// contexts/AuthContext.js
import { createContext, useContext, useState, useMemo } from 'react';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);
  
  // Memoize value to prevent re-renders when provider re-renders
  const value = useMemo(() => ({
    user,
    token,
    login: (userData, authToken) => {
      setUser(userData);
      setToken(authToken);
    },
    logout: () => {
      setUser(null);
      setToken(null);
    }
  }), [user, token]);
  
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// Custom hook with error handling
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

// Usage
function App() {
  return (
    <AuthProvider>
      <Dashboard />
    </AuthProvider>
  );
}

function Dashboard() {
  const { user, logout } = useAuth();
  return <button onClick={logout}>{user.name}</button>;
}
Warning: Context API is not optimized for frequent updates. For high-frequency state changes (form inputs, animations), use local state or specialized libraries like Zustand/Jotai.

2.4 MobX Observable State Trees

Concept Decorator/API Description Advantage
Observable State makeObservable() Automatically track state changes and trigger component re-renders Minimal boilerplate, reactive programming model
Computed Values computed Derive values from observables, automatically cache and update Performance optimization, declarative derived state
Actions action Modify observable state, batch updates for performance Transactional updates, better debugging
Reactions autorun, reaction Side effects that run automatically when observables change Automatic synchronization, no manual subscriptions
MobX-State-Tree types.model() Runtime type system with snapshots, patches, time-travel Type safety, undo/redo, JSON serialization

Example: MobX Store with Computed Values

import { makeObservable, observable, computed, action } from 'mobx';
import { observer } from 'mobx-react-lite';

class TodoStore {
  todos = [];
  
  constructor() {
    makeObservable(this, {
      todos: observable,
      completedCount: computed,
      addTodo: action,
      toggleTodo: action
    });
  }
  
  get completedCount() {
    // Automatically recomputed when todos change
    return this.todos.filter(todo => todo.completed).length;
  }
  
  addTodo(text) {
    this.todos.push({ id: Date.now(), text, completed: false });
  }
  
  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.completed = !todo.completed;
  }
}

const todoStore = new TodoStore();

// Observer component - re-renders only when used observables change
const TodoList = observer(() => {
  return (
    <div>
      <p>Completed: {todoStore.completedCount}/{todoStore.todos.length}</p>
      {todoStore.todos.map(todo => (
        <div key={todo.id} onClick={() => todoStore.toggleTodo(todo.id)}>
          {todo.text}
        </div>
      ))}
    </div>
  );
});
MobX Philosophy: Anything that can be derived from application state should be derived automatically. Focus on what to track, not how to track it.

2.5 SWR Recoil Data Fetching

Library Core Hook Key Feature Use Case
SWR NEW useSWR(key, fetcher) Stale-While-Revalidate strategy, automatic revalidation on focus/network Data fetching with smart caching, real-time updates
Recoil atom(), selector() Atomic state management with derived state, asynchronous queries Complex state graphs, React-centric state management
SWR Mutations useSWRMutation() Trigger remote mutations with automatic cache updates POST/PUT/DELETE with optimistic UI updates
Recoil Atoms useRecoilState() Independent state units, minimal re-renders, shared across components Fine-grained state subscriptions, component isolation
Recoil Selectors selector({ get }) Pure functions deriving state from atoms, async data fetching Computed values, GraphQL queries, API integration

SWR Example

import useSWR from 'swr';

const fetcher = url => fetch(url).then(r => r.json());

function Profile() {
  const { data, error, isLoading, mutate } = useSWR(
    '/api/user',
    fetcher,
    {
      revalidateOnFocus: true,
      revalidateOnReconnect: true,
      refreshInterval: 30000 // Poll every 30s
    }
  );
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  
  return (
    <div>
      <h1>{data.name}</h1>
      <button onClick={() => mutate()}>
        Refresh
      </button>
    </div>
  );
}

// Optimistic update
import { useSWRConfig } from 'swr';

function UpdateButton() {
  const { mutate } = useSWRConfig();
  
  const updateUser = async () => {
    // Optimistic update
    mutate('/api/user', { name: 'New' }, false);
    // Remote update
    await fetch('/api/user', { method: 'PATCH' });
    // Revalidate
    mutate('/api/user');
  };
}

Recoil Example

import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

// Atom - independent state unit
const textState = atom({
  key: 'textState',
  default: ''
});

// Selector - derived state
const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  }
});

// Async selector
const userState = selector({
  key: 'userState',
  get: async ({ get }) => {
    const userId = get(currentUserIdState);
    const response = await fetch(`/api/user/${userId}`);
    return response.json();
  }
});

function TextInput() {
  const [text, setText] = useRecoilState(textState);
  const count = useRecoilValue(charCountState);
  
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Character count: {count}</p>
    </div>
  );
}

SWR vs Recoil Comparison

  • SWR: Focused on data fetching, automatic revalidation, 5KB bundle
  • Recoil: General state management, atom-based architecture, React-centric
  • When to use SWR: API-driven apps, real-time data, caching strategy
  • When to use Recoil: Complex state graphs, derived state, async queries

2.6 TanStack Query React Query

Feature API Description Benefit
useQuery NEW useQuery({ queryKey, queryFn }) Declarative data fetching with automatic caching, background updates Zero-config caching, deduplication, stale-while-revalidate
useMutation useMutation({ mutationFn }) Perform create/update/delete operations with optimistic updates Automatic invalidation, rollback on error, loading states
Query Invalidation queryClient.invalidateQueries() Mark queries as stale to trigger automatic refetch Keep UI in sync after mutations, smart cache management
Parallel Queries useQueries() Execute multiple queries simultaneously, independent loading states Optimize data fetching, reduce waterfall requests
Infinite Queries useInfiniteQuery() Pagination with "load more" pattern, automatic page management Infinite scroll, cursor-based pagination
DevTools ReactQueryDevtools Inspect queries, cache state, refetch manually, time-travel Debugging cache behavior, performance optimization

Example: TanStack Query Complete Implementation

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Query
function TodoList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const res = await fetch('/api/todos');
      return res.json();
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
    refetchOnWindowFocus: true,
    retry: 3
  });
  
  if (isLoading) return <Skeleton />;
  if (error) return <Error message={error.message} />;
  
  return <ul>{data.map(todo => <li>{todo.title}</li>)}</ul>;
}

// Mutation with optimistic update
function AddTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: (newTodo) => fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(newTodo)
    }),
    onMutate: async (newTodo) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['todos'] });
      
      // Snapshot previous value
      const previousTodos = queryClient.getQueryData(['todos']);
      
      // Optimistically update
      queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
      
      return { previousTodos };
    },
    onError: (err, newTodo, context) => {
      // Rollback on error
      queryClient.setQueryData(['todos'], context.previousTodos);
    },
    onSettled: () => {
      // Refetch after mutation
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    }
  });
  
  return <button onClick={() => mutation.mutate({ title: 'New' })}>Add</button>;
}

// Infinite Query
function InfiniteTodos() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['todos'],
    queryFn: ({ pageParam = 0 }) => fetch(`/api/todos?page=${pageParam}`),
    getNextPageParam: (lastPage) => lastPage.nextCursor
  });
  
  return (
    <div>
      {data.pages.map(page => page.todos.map(todo => <div>{todo.title}</div>))}
      {hasNextPage && (
        <button onClick={fetchNextPage} disabled={isFetchingNextPage}>
          Load More
        </button>
      )}
    </div>
  );
}

Query States

State Description
isLoading First fetch, no cached data
isFetching Fetching (initial or background)
isSuccess Query successful, data available
isError Query failed, error available
isStale Data outdated, refetch pending

Cache Configuration

Option Purpose
staleTime How long data stays fresh
cacheTime Unused data retention time
refetchOnWindowFocus Refetch when window regains focus
refetchInterval Polling interval in milliseconds
retry Number of retry attempts
Why TanStack Query: Eliminates 90% of server state boilerplate, automatic background updates, built-in loading/error states, request deduplication, optimistic updates, and infinite queries out-of-the-box.

3. Data Flow Communication Patterns

3.1 Unidirectional Flux Redux Architecture

Concept Pattern Description Benefit
Flux Architecture Action → Dispatcher → Store → View One-way data flow where actions trigger state changes, views subscribe to updates Predictable state changes, easier debugging, time-travel capability
Actions { type, payload } Plain objects describing what happened, dispatched to modify state Serializable events, action log for debugging, middleware intercept
Reducers (state, action) => newState Pure functions calculating next state based on current state and action Testable, predictable, composable state transitions
Store createStore(reducer) Single source of truth holding entire application state tree Centralized state, subscription mechanism, middleware pipeline
Middleware store => next => action Pipeline for side effects, logging, async actions (thunk, saga) Separates business logic from UI, reusable effects

Example: Flux Pattern with Redux

// Action Types
const INCREMENT = 'counter/increment';
const FETCH_USER = 'user/fetch';

// Action Creators
const increment = () => ({ type: INCREMENT });
const fetchUserAsync = (id) => async (dispatch) => {
  dispatch({ type: FETCH_USER + '/pending' });
  try {
    const user = await api.getUser(id);
    dispatch({ type: FETCH_USER + '/fulfilled', payload: user });
  } catch (error) {
    dispatch({ type: FETCH_USER + '/rejected', payload: error });
  }
};

// Reducer (Pure Function)
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, value: state.value + 1 };
    default:
      return state;
  }
}

// Store with Middleware
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(counterReducer, applyMiddleware(thunk));

// Unidirectional Flow
// View dispatches action
store.dispatch(increment());
// Reducer produces new state
// Store notifies subscribers
// View re-renders with new state

Flux Principles

  • Single Direction: Data flows only one way (Action → Store → View)
  • Predictability: Same actions + same state = same result
  • Centralized State: One store, one source of truth
  • Immutability: State never mutated directly, always replaced

3.2 Event-Driven Pub Sub Implementation

Concept Pattern Description Use Case
Event Emitter EventEmitter Publish-subscribe pattern for decoupled component communication Cross-component events, micro-frontend communication
Custom Events new CustomEvent() Browser native event system for inter-component messaging Web components, widget communication, analytics tracking
Event Bus eventBus.emit/on Global message bus for application-wide events Global notifications, cross-feature communication
RxJS Observables Subject, BehaviorSubject Reactive streams for event handling with operators (map, filter, debounce) Complex event flows, async operations, real-time updates
Message Queue postMessage() Cross-origin communication between windows, iframes, workers Micro-frontends, iframe isolation, web workers

Event Emitter Pattern

// Simple Event Emitter
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
    
    // Return unsubscribe function
    return () => this.off(event, listener);
  }
  
  off(event, listener) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event]
      .filter(l => l !== listener);
  }
  
  emit(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach(listener => 
      listener(data)
    );
  }
  
  once(event, listener) {
    const onceWrapper = (data) => {
      listener(data);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
  }
}

// Usage
const bus = new EventEmitter();

bus.on('user:login', (user) => {
  console.log('User logged in:', user);
});

bus.emit('user:login', { id: 1, name: 'John' });

RxJS Observable Pattern

import { Subject, BehaviorSubject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

// Subject - hot observable
const userEvents$ = new Subject();

userEvents$.subscribe(event => {
  console.log('User event:', event);
});

userEvents$.next({ type: 'click', target: 'button' });

// BehaviorSubject - stores current value
const currentUser$ = new BehaviorSubject(null);

currentUser$.subscribe(user => {
  console.log('Current user:', user);
});

currentUser$.next({ id: 1, name: 'John' });

// Search input with debounce
const searchInput$ = new Subject();

searchInput$
  .pipe(
    debounceTime(300),
    distinctUntilChanged()
  )
  .subscribe(query => {
    console.log('Search:', query);
    // Trigger API call
  });

// Emit search queries
searchInput$.next('react');
searchInput$.next('redux'); // Debounced

Example: Custom Events for Web Components

// Dispatch custom event
class UserCard extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('user-selected', {
        detail: { userId: this.dataset.userId },
        bubbles: true,
        composed: true // Cross shadow DOM boundaries
      }));
    });
  }
}

// Listen for custom event
document.addEventListener('user-selected', (event) => {
  console.log('User selected:', event.detail.userId);
  // Load user details, navigate, etc.
});

// React integration
function App() {
  useEffect(() => {
    const handler = (e) => console.log(e.detail);
    document.addEventListener('user-selected', handler);
    return () => document.removeEventListener('user-selected', handler);
  }, []);
}
Caution: Event-driven patterns can lead to hidden dependencies and difficult debugging. Use typed events, clear naming conventions, and consider Redux DevTools for observable tracking.

3.3 Real-time WebSocket Socket.io

Technology API Description Use Case
WebSocket API new WebSocket(url) Native browser API for full-duplex bidirectional communication over TCP Real-time apps, low latency, persistent connection
Socket.IO io() Library with automatic reconnection, fallbacks, rooms, namespaces Chat apps, live collaboration, multiplayer games
Heartbeat/Ping-Pong socket.ping() Keep-alive mechanism to detect disconnections early Connection health monitoring, automatic reconnection
Rooms/Channels socket.join(room) Broadcast messages to specific groups of connected clients Chat rooms, game lobbies, team workspaces
Binary Support ArrayBuffer, Blob Send binary data efficiently (images, files, audio) File transfers, video streaming, game state sync

Example: WebSocket and Socket.IO Implementation

// Native WebSocket
const ws = new WebSocket('wss://example.com/socket');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Message:', data);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected');
  // Implement reconnection logic
  setTimeout(() => reconnect(), 1000);
};

// Socket.IO Client
import { io } from 'socket.io-client';

const socket = io('https://example.com', {
  reconnection: true,
  reconnectionDelay: 1000,
  reconnectionAttempts: 5,
  transports: ['websocket', 'polling'] // Fallback to polling
});

socket.on('connect', () => {
  console.log('Connected:', socket.id);
});

socket.on('message', (data) => {
  console.log('Received:', data);
});

socket.emit('chat-message', { room: 'general', text: 'Hello!' });

// Join room
socket.emit('join-room', 'room-123');

// React Hook for Socket.IO
function useSocket(url) {
  const [socket, setSocket] = useState(null);
  const [isConnected, setIsConnected] = useState(false);
  
  useEffect(() => {
    const newSocket = io(url);
    
    newSocket.on('connect', () => setIsConnected(true));
    newSocket.on('disconnect', () => setIsConnected(false));
    
    setSocket(newSocket);
    
    return () => newSocket.close();
  }, [url]);
  
  return { socket, isConnected };
}

WebSocket vs Socket.IO

Feature WebSocket Socket.IO
Protocol Native WS protocol Custom protocol
Reconnection Manual implementation Automatic
Fallback None Long polling
Rooms Manual Built-in
Bundle Size 0KB (native) ~50KB

Best Practices

  • Use wss:// (secure WebSocket) in production
  • Implement exponential backoff for reconnection
  • Validate and sanitize all incoming messages
  • Set connection timeout and heartbeat interval
  • Use binary format (MessagePack) for efficiency
  • Handle offline state gracefully with queue
  • Monitor connection status in UI
  • Close connections properly on unmount

3.4 Server-Sent Events SSE Streaming

Concept API Description Advantage
Server-Sent Events new EventSource(url) One-way server-to-client push over HTTP, automatic reconnection Simpler than WebSocket for one-way updates, HTTP/2 compatible
Text-Based Protocol data: message\n\n UTF-8 text format with event types, IDs, retry configuration Human-readable, easy debugging, works through proxies
Auto Reconnection retry: 3000 Browser automatically reconnects with last event ID Resilient connections without custom retry logic
Event Types event: custom-event Named events for different message types from single endpoint Type-safe event handling, multiple event listeners
HTTP Streaming Transfer-Encoding: chunked Keeps HTTP connection open, sends chunks incrementally Works with existing HTTP infrastructure, CDN support

Example: Server-Sent Events Implementation

// Client: EventSource API
const eventSource = new EventSource('/api/events');

// Default message event
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Message:', data);
};

// Custom event types
eventSource.addEventListener('user-update', (event) => {
  const user = JSON.parse(event.data);
  console.log('User updated:', user);
});

eventSource.addEventListener('notification', (event) => {
  const notification = JSON.parse(event.data);
  showToast(notification.message);
});

// Connection lifecycle
eventSource.onopen = () => {
  console.log('SSE connection opened');
};

eventSource.onerror = (error) => {
  console.error('SSE error:', error);
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  }
};

// Close connection
eventSource.close();

// Server: Node.js/Express
app.get('/api/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // Send initial message
  res.write('data: {"message": "Connected"}\n\n');
  
  // Send periodic updates
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
  }, 1000);
  
  // Custom event with ID
  res.write('event: user-update\n');
  res.write('id: 123\n');
  res.write('data: {"name": "John", "status": "online"}\n\n');
  
  // Set retry timeout (milliseconds)
  res.write('retry: 10000\n\n');
  
  // Cleanup on client disconnect
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

SSE vs WebSocket

Feature SSE WebSocket
Direction Server → Client Bidirectional
Protocol HTTP WS
Reconnection Automatic Manual
Data Format Text only Text + Binary
Complexity Simple Complex

SSE Use Cases

  • Live notifications and alerts
  • Stock price tickers
  • News feed updates
  • Server log streaming
  • Progress indicators for long tasks
  • Social media live feeds
  • Sports scores and updates
  • IoT sensor data monitoring
When to use SSE: Perfect for one-way server push scenarios. Simpler than WebSocket, works through firewalls, automatic reconnection, and HTTP/2 multiplexing support.

3.5 GraphQL Subscription Live Updates

Concept Implementation Description Use Case
GraphQL Subscriptions subscription { ... } Real-time GraphQL queries that push updates when data changes Live data feeds, collaborative editing, chat applications
Transport Layer WebSocket, SSE Subscriptions use WebSocket or SSE for bidirectional communication Flexible transport based on requirements
Apollo Client useSubscription() React hook for GraphQL subscriptions with automatic reconnection Declarative real-time data in React components
Pub/Sub Pattern pubsub.publish() Server-side publish-subscribe for triggering subscription updates Multi-client updates, event-driven architecture
Filtering withFilter() Server-side filtering to send updates only to relevant subscribers Optimize bandwidth, user-specific updates

Example: GraphQL Subscription Implementation

// Schema Definition
type Subscription {
  messageAdded(roomId: ID!): Message
  userStatusChanged(userId: ID!): UserStatus
  notificationReceived: Notification
}

type Message {
  id: ID!
  text: String!
  user: User!
  timestamp: String!
}

// Server: GraphQL Resolver
import { PubSub, withFilter } from 'graphql-subscriptions';

const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const resolvers = {
  Mutation: {
    addMessage: async (_, { roomId, text }, { user }) => {
      const message = {
        id: generateId(),
        text,
        user,
        roomId,
        timestamp: new Date().toISOString()
      };
      
      await saveMessage(message);
      
      // Publish to subscribers
      pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
      
      return message;
    }
  },
  
  Subscription: {
    messageAdded: {
      // Filter: only send to users in the same room
      subscribe: withFilter(
        () => pubsub.asyncIterator([MESSAGE_ADDED]),
        (payload, variables) => {
          return payload.messageAdded.roomId === variables.roomId;
        }
      )
    }
  }
};

// Client: Apollo Client Setup
import { split, HttpLink, ApolloClient, InMemoryCache } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';

const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });

const wsLink = new GraphQLWsLink(
  createClient({ url: 'ws://localhost:4000/graphql' })
);

// Split based on operation type
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache()
});

Example: React Component with Subscription

import { useSubscription, gql, useMutation } from '@apollo/client';

const MESSAGE_SUBSCRIPTION = gql`
  subscription OnMessageAdded($roomId: ID!) {
    messageAdded(roomId: $roomId) {
      id
      text
      user { name }
      timestamp
    }
  }
`;

const ADD_MESSAGE = gql`
  mutation AddMessage($roomId: ID!, $text: String!) {
    addMessage(roomId: $roomId, text: $text) {
      id
      text
    }
  }
`;

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  
  // Subscription
  const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION, {
    variables: { roomId },
    onData: ({ data }) => {
      setMessages(prev => [...prev, data.data.messageAdded]);
    }
  });
  
  // Mutation
  const [addMessage] = useMutation(ADD_MESSAGE);
  
  const handleSend = (text) => {
    addMessage({ variables: { roomId, text } });
  };
  
  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>{msg.user.name}: {msg.text}</div>
      ))}
      <input onSubmit={(e) => handleSend(e.target.value)} />
    </div>
  );
}

GraphQL Subscription Benefits

  • Type Safety: Strongly typed real-time data with schema validation
  • Selective Fields: Request only needed fields, reduce bandwidth
  • Unified API: Queries, mutations, and subscriptions in one endpoint
  • Client Control: Client decides what to subscribe to and when

3.6 React Props Context Drilling Solution

Pattern Solution Description When to Use
Prop Drilling Problem Parent → Child → GrandChild Passing props through intermediate components that don't need them Recognize as code smell when props pass through 3+ levels
Component Composition children, render props Pass components as children instead of data, invert dependencies First solution to try, maintains component isolation
Context API createContext + Provider Share data across component tree without explicit props Theme, auth, locale - infrequently changing data
State Management Redux, Zustand, Jotai External state store accessible from any component Complex state logic, frequent updates, shared state
Custom Hooks useCustomHook() Encapsulate data fetching and state logic in reusable hooks Share logic without sharing state, API abstractions

Problem: Prop Drilling

// Bad: Props drilled through components
function App() {
  const [user, setUser] = useState(null);
  return <Dashboard user={user} />;
}

function Dashboard({ user }) {
  // Dashboard doesn't use user
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  // Sidebar doesn't use user
  return <UserProfile user={user} />;
}

function UserProfile({ user }) {
  // Finally uses user
  return <div>{user.name}</div>;
}

Solution 1: Composition

// Good: Composition pattern
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <Dashboard>
      <Sidebar>
        <UserProfile user={user} />
      </Sidebar>
    </Dashboard>
  );
}

function Dashboard({ children }) {
  return <div className="dashboard">{children}</div>;
}

function Sidebar({ children }) {
  return <aside>{children}</aside>;
}

function UserProfile({ user }) {
  return <div>{user.name}</div>;
}

Example: Context API Solution

// Solution 2: Context API
import { createContext, useContext } from 'react';

const UserContext = createContext(null);

function App() {
  const [user, setUser] = useState(null);
  
  return (
    <UserContext.Provider value={user}>
      <Dashboard />
    </UserContext.Provider>
  );
}

function Dashboard() {
  // No user prop needed
  return <Sidebar />;
}

function Sidebar() {
  // No user prop needed
  return <UserProfile />;
}

function UserProfile() {
  const user = useContext(UserContext);
  return <div>{user.name}</div>;
}

// Solution 3: Custom Hook
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
}

function UserProfile() {
  const user = useUser(); // Clean API
  return <div>{user.name}</div>;
}

Decision Tree

Scenario Solution
1-2 levels deep Pass props directly
Layout composition Component composition
Infrequent updates Context API
Frequent updates State management library
Logic reuse Custom hooks

Anti-Patterns to Avoid

  • Using Context for frequently changing values
  • Creating one giant context for entire app
  • Drilling props more than 3 levels deep
  • Passing callbacks through many levels
  • Using Redux for truly local state
  • Creating context without custom hook
  • Not memoizing context values
Performance Note: Context API causes all consumers to re-render when value changes. For high-frequency updates, use state management libraries with selector functions (Zustand, Jotai) or split contexts by update frequency.

4. Modern Routing Navigation Implementation

4.1 React Router v6 Nested Routes

Feature API Description Benefit
BrowserRouter <BrowserRouter> HTML5 history API for clean URLs without hash (#) SEO-friendly, native browser navigation, back/forward support
Routes & Route <Routes><Route path element /> Declarative route configuration with element-based rendering Type-safe, composable, no render props needed
Nested Routes NEW <Outlet /> Parent routes render child routes using Outlet component Layout composition, shared UI, hierarchical routing
useNavigate navigate('/path') Programmatic navigation hook replacing useHistory Simpler API, TypeScript support, relative navigation
useParams & useSearchParams useParams(), useSearchParams() Access URL parameters and query strings in components Type-safe parameter extraction, URLSearchParams API
Index Routes <Route index element /> Default child route when parent path matches exactly Dashboard defaults, tab navigation, nested layouts

Example: React Router v6 Nested Routes

import { BrowserRouter, Routes, Route, Outlet, Link, useParams } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          
          {/* Nested routes */}
          <Route path="dashboard" element={<Dashboard />}>
            <Route index element={<DashboardHome />} />
            <Route path="profile" element={<Profile />} />
            <Route path="settings" element={<Settings />} />
          </Route>
          
          {/* Dynamic segments */}
          <Route path="users/:userId" element={<UserDetail />} />
          
          {/* Catch-all */}
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

// Layout with Outlet
function Layout() {
  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/dashboard">Dashboard</Link>
      </nav>
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

// Dashboard with nested routes
function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <Link to="/dashboard">Overview</Link>
        <Link to="/dashboard/profile">Profile</Link>
        <Link to="/dashboard/settings">Settings</Link>
      </nav>
      <Outlet /> {/* Nested child routes */}
    </div>
  );
}

// Access params
function UserDetail() {
  const { userId } = useParams();
  const [searchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'overview';
  
  return <div>User: {userId}, Tab: {tab}</div>;
}

// Programmatic navigation
function LoginForm() {
  const navigate = useNavigate();
  
  const handleSubmit = async (credentials) => {
    await login(credentials);
    navigate('/dashboard', { replace: true });
  };
}
v6 Changes: Removed <Switch> (use <Routes>), removed render/component props (use element), removed useHistory (use useNavigate), relative paths by default

4.2 Next.js App Router File-based

Concept File Convention Description Feature
App Router NEW app/ directory File-system based routing with React Server Components support Server components, streaming, layouts, loading states
page.js app/blog/page.js Defines route segment UI, makes path publicly accessible Route endpoints, unique per segment
layout.js app/layout.js Shared UI that wraps child layouts and pages, preserves state Persistent navigation, shared headers, nested layouts
loading.js app/blog/loading.js Loading UI with Suspense, instant loading state Automatic loading boundaries, streaming support
error.js app/blog/error.js Error boundary UI, automatically wraps route segment Granular error handling, error recovery
Dynamic Routes [slug]/page.js Dynamic segments using square brackets, catch-all routes Blog posts, product pages, user profiles
Route Groups (group)/page.js Organize routes without affecting URL structure Team organization, different layouts, logical grouping

Example: Next.js App Router File Structure

app/
├── layout.js           # Root layout (required)
├── page.js             # Home page (/)
├── loading.js          # Global loading UI
├── error.js            # Global error boundary
├── not-found.js        # 404 page
├── (marketing)/        # Route group (doesn't affect URL)
│   ├── about/
│   │   └── page.js     # /about
│   └── contact/
│       └── page.js     # /contact
├── dashboard/
│   ├── layout.js       # Dashboard layout
│   ├── page.js         # /dashboard
│   ├── loading.js      # Dashboard loading
│   ├── error.js        # Dashboard errors
│   └── [teamId]/
│       ├── page.js     # /dashboard/[teamId]
│       └── settings/
│           └── page.js # /dashboard/[teamId]/settings
└── blog/
    ├── [...slug]/      # Catch-all: /blog/a, /blog/a/b, etc.
    │   └── page.js
    └── [[...slug]]/    # Optional catch-all: includes /blog
        └── page.js

// app/layout.js - Root Layout (Required)
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <nav>Global Navigation</nav>
        {children}
        <footer>Global Footer</footer>
      </body>
    </html>
  );
}

// app/dashboard/layout.js - Nested Layout
export default function DashboardLayout({ children }) {
  return (
    <div>
      <aside>Dashboard Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

// app/dashboard/[teamId]/page.js - Dynamic Route
export default function TeamPage({ params }) {
  return <h1>Team: {params.teamId}</h1>;
}

// Generate static params at build time
export async function generateStaticParams() {
  const teams = await fetchTeams();
  return teams.map(team => ({ teamId: team.id }));
}

// app/dashboard/loading.js - Loading UI
export default function Loading() {
  return <div>Loading dashboard...</div>;
}

// app/dashboard/error.js - Error Boundary
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Special Files

File Purpose
layout.js Shared UI for segment
page.js Route endpoint UI
loading.js Loading UI with Suspense
error.js Error boundary
template.js Re-render on navigation
not-found.js 404 UI
route.js API endpoint
// Link component
import Link from 'next/link';

<Link href="/dashboard">Dashboard</Link>
<Link href={`/blog/${slug}`}>Post</Link>

// Programmatic navigation
'use client';
import { useRouter } from 'next/navigation';

function Button() {
  const router = useRouter();
  
  return (
    <button onClick={() => router.push('/dashboard')}>
      Go to Dashboard
    </button>
  );
}

// Prefetch on hover (default)
<Link href="/slow-page" prefetch={true}>
  Prefetched Link
</Link>

App Router Benefits

  • Server Components: Zero-bundle components with direct DB access
  • Streaming: Progressive rendering with Suspense boundaries
  • Layouts: Persistent UI that doesn't re-render on navigation
  • File Conventions: loading.js, error.js for automatic boundaries

4.3 Vue Router Composition API

Feature API Description Use Case
createRouter createRouter({ routes }) Create router instance with history mode and route configuration SPA routing, history management, route guards
useRouter & useRoute useRouter(), useRoute() Composition API hooks for router instance and current route Programmatic navigation, route information access
Nested Routes children: [] Child routes with nested RouterView components Complex layouts, multi-level navigation
Named Views <RouterView name="sidebar" /> Multiple RouterView components in same route Multi-panel layouts, sidebar + main content
Route Meta meta: { auth: true } Attach custom metadata to routes for guards, breadcrumbs Authentication, permissions, analytics tracking
Lazy Loading component: () => import() Code-split route components with dynamic imports Optimize initial bundle, load routes on-demand

Example: Vue Router with Composition API

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/dashboard',
    component: () => import('../layouts/Dashboard.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'DashboardHome',
        component: () => import('../views/DashboardHome.vue')
      },
      {
        path: 'profile',
        name: 'Profile',
        component: () => import('../views/Profile.vue')
      },
      {
        path: 'settings',
        name: 'Settings',
        component: () => import('../views/Settings.vue'),
        meta: { title: 'Settings' }
      }
    ]
  },
  {
    path: '/user/:id',
    name: 'UserDetail',
    component: () => import('../views/UserDetail.vue'),
    props: true // Pass route params as props
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition;
    return { top: 0 };
  }
});

export default router;

// Component using Composition API
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { computed } from 'vue';

const router = useRouter();
const route = useRoute();

// Access route params
const userId = computed(() => route.params.id);
const query = computed(() => route.query);

// Programmatic navigation
const navigateToDashboard = () => {
  router.push({ name: 'Dashboard' });
};

const goBack = () => {
  router.back();
};

// Navigation with params
const viewUser = (id) => {
  router.push({ 
    name: 'UserDetail', 
    params: { id },
    query: { tab: 'profile' }
  });
};
</script>

<template>
  <div>
    <RouterLink to="/dashboard">Dashboard</RouterLink>
    <RouterLink :to="{ name: 'Profile' }">Profile</RouterLink>
    <RouterView />
  </div>
</template>
Method Description
push() Navigate, add history entry
replace() Navigate, replace current entry
go(n) Move n steps in history
back() Go back one step
forward() Go forward one step

Route Object Properties

Property Description
route.path Current path string
route.params Dynamic route parameters
route.query Query parameters object
route.hash URL hash
route.name Route name
route.meta Route metadata

4.4 Code Splitting Route-based Lazy Loading

Strategy Implementation Description Benefit
Route-based Splitting React.lazy(() => import()) Split bundles at route boundaries, load on navigation Smaller initial bundle, faster TTI, pay-as-you-go loading
Suspense Boundary <Suspense fallback> Show loading state while lazy component loads Better UX, graceful loading, error boundaries
Prefetching <link rel="prefetch"> Load route bundle during browser idle time Instant navigation, zero loading delay
Preloading <link rel="preload"> High-priority loading for critical next routes Faster subsequent navigation, predictive loading
Magic Comments /* webpackChunkName */ Name chunks for better debugging and caching Named bundles, cache optimization, debugging

Example: Route-based Code Splitting

// React Router with lazy loading
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import(
  /* webpackChunkName: "dashboard" */
  /* webpackPrefetch: true */
  './pages/Dashboard'
));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

// Loading component
function LoadingSpinner() {
  return <div className="spinner">Loading...</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Next.js automatic code splitting
// pages/dashboard.js - automatically code-split
export default function Dashboard() {
  return <div>Dashboard</div>;
}

// Vue Router lazy loading
const routes = [
  {
    path: '/dashboard',
    component: () => import(
      /* webpackChunkName: "dashboard" */
      './views/Dashboard.vue'
    )
  }
];

// Prefetch on hover
import { useEffect } from 'react';

function NavLink({ to, children }) {
  const prefetchRoute = () => {
    // Prefetch route component
    import(/* webpackPrefetch: true */ `./pages/${to}`);
  };
  
  return (
    <a 
      href={to} 
      onMouseEnter={prefetchRoute}
      onFocus={prefetchRoute}
    >
      {children}
    </a>
  );
}

Bundle Strategies

Strategy When to Use
Route-based Main navigation routes
Component-based Heavy modals, charts, editors
Vendor splitting Separate node_modules
Common chunks Shared dependencies

Performance Metrics

  • Initial Bundle: Reduce 40-60% with route splitting
  • TTI: Time to Interactive improves by 30-50%
  • FCP: First Contentful Paint faster with smaller bundles
  • Cache Hit: Better caching with stable chunk names

4.5 Dynamic Import Module Loading

Technique Syntax Description Use Case
Dynamic Import import('module') Promise-based module loading at runtime, creates separate chunk Conditional loading, on-demand features, reduce initial bundle
Conditional Loading if (condition) import() Load modules based on runtime conditions (feature flags, user role) A/B testing, premium features, device-specific code
Import on Interaction onClick={() => import()} Lazy load when user interacts with UI element Modals, tooltips, rich editors, heavy components
Import on Visibility IntersectionObserver Load module when element enters viewport Below-fold content, infinite scroll, image galleries
Named Exports import().then(m => m.fn) Access specific exports from dynamically imported module Tree-shaking with dynamic imports, selective loading

Example: Dynamic Import Patterns

// Basic dynamic import
button.addEventListener('click', async () => {
  const module = await import('./heavyModule.js');
  module.initialize();
});

// React component lazy loading
function Editor() {
  const [EditorComponent, setEditorComponent] = useState(null);
  
  const loadEditor = async () => {
    const { default: Editor } = await import('react-quill');
    setEditorComponent(() => Editor);
  };
  
  useEffect(() => {
    loadEditor();
  }, []);
  
  if (!EditorComponent) return <div>Loading editor...</div>;
  return <EditorComponent />;
}

// Conditional loading based on feature flag
async function loadFeature() {
  if (featureFlags.newDashboard) {
    const { NewDashboard } = await import('./features/NewDashboard');
    return NewDashboard;
  } else {
    const { OldDashboard } = await import('./features/OldDashboard');
    return OldDashboard;
  }
}

// Load on viewport intersection
function LazyImage({ src }) {
  const [imageSrc, setImageSrc] = useState(null);
  const imgRef = useRef(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      async ([entry]) => {
        if (entry.isIntersecting) {
          // Load image processing library when needed
          const { processImage } = await import('./imageUtils');
          const processed = await processImage(src);
          setImageSrc(processed);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );
    
    if (imgRef.current) observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, [src]);
  
  return <img ref={imgRef} src={imageSrc} />;
}

// Named export extraction
const loadChart = async (type) => {
  const chartModule = await import('chart.js');
  
  switch (type) {
    case 'bar':
      return new chartModule.BarChart();
    case 'line':
      return new chartModule.LineChart();
    case 'pie':
      return new chartModule.PieChart();
  }
};

// Webpack magic comments for optimization
const Dashboard = () => import(
  /* webpackChunkName: "dashboard" */
  /* webpackPreload: true */
  /* webpackExports: ["default", "config"] */
  './Dashboard'
);

Import Strategies

  • Eager: Bundle with main app (critical features)
  • Lazy: Load on route/component render
  • Prefetch: Load during idle time
  • Preload: Load in parallel with main bundle
  • On-interaction: Load on click/hover
  • On-visibility: Load when in viewport

Webpack Magic Comments

Comment Effect
webpackChunkName Name the chunk
webpackPrefetch Prefetch during idle
webpackPreload Preload immediately
webpackMode lazy, eager, weak
webpackExports Tree-shake exports

4.6 Navigation Guards Authentication Middleware

Guard Type Implementation Description Use Case
Global Guards router.beforeEach() Run before every route navigation, app-wide checks Authentication, analytics, scroll restoration
Route Guards beforeEnter Per-route validation before entering specific route Permission checks, data prefetching, redirects
Component Guards beforeRouteEnter Inside component, access to component instance Component-level validation, cleanup
Protected Routes meta: { requiresAuth } Route metadata for conditional access control Private pages, role-based access, subscription gates
Middleware Pattern middleware.js Composable middleware functions executed in sequence Complex auth flows, multi-step validation

Vue Router Guards

// Global guard
router.beforeEach(async (to, from, next) => {
  // Check authentication
  if (to.meta.requiresAuth) {
    const isAuthenticated = await checkAuth();
    if (!isAuthenticated) {
      return next({ name: 'Login', query: { redirect: to.fullPath } });
    }
  }
  
  // Check permissions
  if (to.meta.roles) {
    const userRole = getUserRole();
    if (!to.meta.roles.includes(userRole)) {
      return next({ name: 'Forbidden' });
    }
  }
  
  next();
});

// Route-level guard
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next();
      } else {
        next('/unauthorized');
      }
    }
  }
];

// Component guard
export default {
  beforeRouteEnter(to, from, next) {
    // Can't access `this` yet
    fetchUserData(to.params.id).then(user => {
      next(vm => {
        vm.user = user;
      });
    });
  },
  
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges) {
      const confirm = window.confirm('Discard changes?');
      next(confirm);
    } else {
      next();
    }
  }
};

React Router Guards

// Protected Route wrapper
function ProtectedRoute({ children }) {
  const { user } = useAuth();
  const location = useLocation();
  
  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

// Role-based guard
function RoleGuard({ roles, children }) {
  const { user } = useAuth();
  
  if (!roles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }
  
  return children;
}

// Usage
<Routes>
  <Route path="/" element={<Home />} />
  
  <Route path="/dashboard" element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } />
  
  <Route path="/admin" element={
    <ProtectedRoute>
      <RoleGuard roles={['admin']}>
        <Admin />
      </RoleGuard>
    </ProtectedRoute>
  } />
</Routes>

// Hook-based guard
function useAuthGuard() {
  const { user } = useAuth();
  const navigate = useNavigate();
  
  useEffect(() => {
    if (!user) {
      navigate('/login');
    }
  }, [user, navigate]);
  
  return user;
}

Example: Next.js Middleware

// middleware.js (Next.js 13+)
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('session')?.value;
  const { pathname } = request.nextUrl;
  
  // Protected routes
  if (pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
    
    // Verify token
    const user = verifyToken(token);
    if (!user) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
  
  // Admin routes
  if (pathname.startsWith('/admin')) {
    const user = verifyToken(token);
    if (user?.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', request.url));
    }
  }
  
  // Add custom headers
  const response = NextResponse.next();
  response.headers.set('x-middleware-cache', 'no-cache');
  
  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*']
};

Guard Best Practices

  • Server-side validation: Always verify on server, guards can be bypassed
  • Token refresh: Handle expired tokens, automatic refresh
  • Loading states: Show loading during async guard checks
  • Redirect chains: Avoid infinite redirects, track navigation history
  • Error handling: Graceful degradation on guard failures
Security Note: Client-side guards are for UX only. Always enforce authentication and authorization on the server. Guards prevent navigation, not API access.

5. Frontend Scalability Implementation Patterns

5.1 Code Splitting Bundle Optimization

Strategy Implementation Description Impact
Entry Point Splitting entry: { app, vendor } Separate application code from third-party libraries Better caching, parallel loading, reduced main bundle size
Dynamic Splitting import() Runtime code splitting based on user interaction or route 50-70% reduction in initial bundle, faster TTI
Vendor Chunking splitChunks.cacheGroups Extract node_modules into separate vendor chunk Long-term caching, CDN optimization
Common Chunks splitChunks.chunks: 'all' Shared code between multiple entry points extracted automatically Reduce duplication, optimize cache usage
Granular Chunks maxSize, minSize Control chunk size for optimal HTTP/2 multiplexing Parallel downloads, better compression ratios
CSS Splitting MiniCssExtractPlugin Extract CSS into separate files for parallel loading Non-blocking CSS, better caching, critical CSS extraction

Example: Webpack Bundle Optimization

// webpack.config.js
module.exports = {
  entry: {
    app: './src/index.js',
  },
  
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // Vendor chunk: node_modules
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          priority: 10,
          reuseExistingChunk: true
        },
        
        // React/ReactDOM separate chunk
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 20
        },
        
        // Common chunks shared by 2+ modules
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
          enforce: true
        }
      },
      
      // Chunk size limits
      maxSize: 244000, // 244KB
      minSize: 20000,  // 20KB
    },
    
    // Runtime chunk for webpack module loading logic
    runtimeChunk: {
      name: 'runtime'
    },
    
    // Minimize bundles
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true
          }
        }
      })
    ]
  },
  
  plugins: [
    // Extract CSS
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[name].[contenthash:8].chunk.css'
    }),
    
    // Bundle analyzer
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false
    })
  ]
};

Bundle Size Targets

Bundle Type Target Size
Main JS <200KB (gzipped)
Vendor JS <150KB (gzipped)
CSS <50KB (gzipped)
Per Route <100KB (gzipped)
Total Initial <300KB (gzipped)

Analysis Tools

  • webpack-bundle-analyzer - Visual bundle size
  • source-map-explorer - Source file analysis
  • bundlephobia.com - Package size checker
  • size-limit - CI bundle size limits
  • Chrome DevTools Coverage tab
  • Lighthouse bundle audits

5.2 Tree Shaking Dead Code Elimination

Technique Configuration Description Requirement
ES Modules type: "module" Use ES6 import/export for static analysis, avoid CommonJS require Required for tree shaking, module.exports prevents elimination
sideEffects Flag package.json Mark files without side effects for safe removal Enables aggressive tree shaking, CSS imports need marking
Production Mode mode: 'production' Webpack/Vite automatically enables tree shaking optimizations Development mode keeps all code for debugging
Named Exports export { fn } Granular imports allow elimination of unused exports Prefer named over default exports for better tree shaking
Babel Configuration modules: false Preserve ES modules in Babel, don't transpile to CommonJS Critical for tree shaking, preset-env default breaks it
Unused Code Detection usedExports: true Mark unused exports for removal by minimizer Works with TerserPlugin to eliminate dead code

Example: Tree Shaking Configuration

// package.json - Mark side effects
{
  "name": "my-library",
  "version": "1.0.0",
  "sideEffects": [
    "*.css",
    "*.scss",
    "./src/polyfills.js"
  ]
  // Or false if no side effects
  // "sideEffects": false
}

// babel.config.js - Preserve ES modules
module.exports = {
  presets: [
    ['@babel/preset-env', {
      modules: false, // Don't transpile modules
      targets: { esmodules: true }
    }]
  ]
};

// Library: Use named exports
// ❌ Bad: Default export
export default {
  add,
  subtract,
  multiply,
  divide
};

// ✅ Good: Named exports
export { add };
export { subtract };
export { multiply };
export { divide };

// Consumer: Import only what's needed
// ❌ Bad: Entire library imported
import math from 'math-library';
math.add(1, 2);

// ✅ Good: Tree-shakeable
import { add } from 'math-library';
add(1, 2);

// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true, // Mark unused exports
    minimize: true,
    sideEffects: true, // Read package.json sideEffects
    
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            unused: true,
            dead_code: true
          }
        }
      })
    ]
  }
};

// Lodash tree shaking
// ❌ Bad: Imports entire library
import _ from 'lodash';
_.debounce(fn, 300);

// ✅ Good: Import specific module
import debounce from 'lodash/debounce';
// Or use lodash-es (ES module version)
import { debounce } from 'lodash-es';

Tree Shaking Checklist

Common Issues

Issue Solution
CommonJS requires Convert to ES6 imports
Missing sideEffects Add to package.json
Babel transpiling modules Set modules: false
CSS not loading Mark CSS as side effect
Library not tree-shakeable Find ES module alternative
Impact: Proper tree shaking can reduce bundle size by 30-50% by eliminating unused code. Lodash alone can save 60KB+ when properly tree-shaken.

5.3 CDN CloudFront Asset Distribution

Strategy Implementation Description Benefit
CDN Distribution CloudFront, Cloudflare Global edge network serving assets from nearest location to users 50-90% latency reduction, improved TTFB, global reach
Cache Headers Cache-Control: max-age Configure browser and CDN caching policies for optimal freshness Reduced bandwidth, faster repeat visits, lower origin load
Content Hashing [contenthash] Add hash to filenames for cache busting and long-term caching Immutable assets, aggressive caching, instant updates
Gzip/Brotli Compression Accept-Encoding Compress text assets (JS, CSS, HTML) at edge or origin 60-80% size reduction, faster transfers, less bandwidth
HTTP/2 Push Link: <url>; rel=preload Proactively push critical assets before browser requests them Reduced round trips, faster critical resource loading
Edge Functions CloudFront Functions Run lightweight logic at CDN edge for personalization, A/B testing Low latency, no origin hit, dynamic edge content

Example: CDN Configuration

// webpack.config.js - Content hashing
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[name].[contenthash:8][ext]',
    publicPath: 'https://cdn.example.com/'
  }
};

// CloudFront Cache Behaviors
{
  "CacheBehaviors": [
    {
      "PathPattern": "*.js",
      "ViewerProtocolPolicy": "redirect-to-https",
      "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
      "Compress": true,
      "AllowedMethods": ["GET", "HEAD", "OPTIONS"],
      "CachedMethods": ["GET", "HEAD"]
    },
    {
      "PathPattern": "*.css",
      "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
      "Compress": true
    }
  ]
}

// Cache-Control headers
// Long-term cache for hashed assets
Cache-Control: public, max-age=31536000, immutable

// Short cache for HTML
Cache-Control: public, max-age=0, must-revalidate

// Nginx configuration
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
  add_header X-Content-Type-Options "nosniff";
  
  # Gzip compression
  gzip on;
  gzip_types text/css application/javascript image/svg+xml;
  gzip_min_length 1000;
  
  # Brotli (if module enabled)
  brotli on;
  brotli_types text/css application/javascript;
}

// S3 + CloudFront deployment
aws s3 sync ./dist s3://my-bucket \
  --cache-control "public,max-age=31536000,immutable" \
  --exclude "*.html"

aws s3 sync ./dist s3://my-bucket \
  --cache-control "public,max-age=0,must-revalidate" \
  --exclude "*" \
  --include "*.html"

// Invalidate CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id E1234567890 \
  --paths "/index.html" "/*.html"

Cache Strategy

Asset Type Cache-Control
HTML no-cache, must-revalidate
JS/CSS (hashed) max-age=31536000, immutable
Images max-age=86400
Fonts max-age=31536000
API responses no-store or short TTL

CDN Features

  • Origin Shield: Additional caching layer
  • Edge Caching: 200+ global locations
  • DDoS Protection: AWS Shield Standard
  • SSL/TLS: Free certificates
  • Compression: Automatic Gzip/Brotli
  • HTTP/2 & HTTP/3: Modern protocols
  • Real-time Logs: Request analytics

CDN Best Practices

  • Content Hashing: Use [contenthash] for immutable assets with long cache
  • Separate HTML: Never cache HTML with long TTL, use cache busting
  • Compression: Enable Brotli at CDN, 20% better than Gzip
  • Multi-Region: Deploy to multiple regions for global redundancy

5.4 Service Workers PWA Caching

Strategy Pattern Description Use Case
Cache First cache → network Serve from cache if available, fetch from network as fallback Static assets (JS, CSS, images), offline-first apps
Network First network → cache Fetch from network first, use cache if network fails Dynamic content, API responses, fresh data priority
Stale While Revalidate cache + background update Serve cached version instantly, update cache in background Balance freshness and speed, user avatars, news feeds
Network Only network only Always fetch from network, never cache Sensitive data, real-time updates, analytics
Cache Only cache only Only serve from cache, fail if not cached Pre-cached critical resources, offline mode
Workbox workbox-webpack-plugin Google's service worker library with routing, strategies, precaching Production-ready PWA, automated caching, background sync

Example: Service Worker Caching Strategies

// service-worker.js - Manual implementation
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1-static').then((cache) => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js',
        '/logo.png'
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  const { request } = event;
  
  // Cache-first for static assets
  if (request.url.includes('/static/')) {
    event.respondWith(
      caches.match(request).then((cached) => {
        return cached || fetch(request).then((response) => {
          return caches.open('v1-static').then((cache) => {
            cache.put(request, response.clone());
            return response;
          });
        });
      })
    );
  }
  
  // Network-first for API
  else if (request.url.includes('/api/')) {
    event.respondWith(
      fetch(request)
        .then((response) => {
          const clone = response.clone();
          caches.open('v1-api').then((cache) => {
            cache.put(request, clone);
          });
          return response;
        })
        .catch(() => caches.match(request))
    );
  }
});

// Workbox implementation
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Precache build assets
precacheAndRoute(self.__WB_MANIFEST);

// Cache images - Cache First
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days
      })
    ]
  })
);

// Cache API - Network First
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api-cache',
    networkTimeoutSeconds: 3,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200]
      }),
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 5 * 60 // 5 minutes
      })
    ]
  })
);

// Stale While Revalidate for Google Fonts
registerRoute(
  ({ url }) => url.origin === 'https://fonts.googleapis.com',
  new StaleWhileRevalidate({
    cacheName: 'google-fonts-stylesheets'
  })
);

// webpack.config.js - Generate service worker
const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
      runtimeCaching: [
        {
          urlPattern: /^https:\/\/api\.example\.com/,
          handler: 'NetworkFirst',
          options: {
            cacheName: 'api-cache',
            expiration: { maxEntries: 50, maxAgeSeconds: 300 }
          }
        }
      ]
    })
  ]
};

Caching Decision Tree

Content Type Strategy
App Shell Cache First + Precache
Static Assets Cache First
User Content Stale While Revalidate
API Data Network First
Real-time Data Network Only
Offline Fallback Cache Only

Service Worker Lifecycle

// Register service worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then((registration) => {
        console.log('SW registered:', registration);
      })
      .catch((error) => {
        console.log('SW registration failed:', error);
      });
  });
}

// Skip waiting for updates
self.addEventListener('message', (event) => {
  if (event.data === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});
Offline Capability: Service Workers enable offline-first experiences with 50-90% faster repeat visits by serving assets from cache instead of network.

5.5 Webpack Vite Build Optimization

Tool Feature Description Performance
Vite NEW esbuild, Rollup Lightning-fast dev server with native ESM, instant HMR 10-100x faster cold start, sub-second HMR
Webpack 5 Persistent Cache Filesystem caching for faster rebuilds, long-term caching 90% faster rebuilds, module federation
SWC/esbuild Rust/Go compilers Ultra-fast JavaScript/TypeScript transpilation vs Babel 20-70x faster than Babel, drop-in replacement
Parallel Processing thread-loader Run expensive loaders in worker pool for parallel processing 30-50% faster builds with multi-core utilization
Module Federation ModuleFederationPlugin Share code between independently deployed applications Micro-frontend architecture, reduced duplication
Build Caching cache: { type: 'filesystem' } Cache build artifacts to disk for faster subsequent builds 5-10x faster rebuilds in CI/CD pipelines

Webpack 5 Optimization

// webpack.config.js
module.exports = {
  mode: 'production',
  
  // Persistent filesystem cache
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },
  
  optimization: {
    // Tree shaking
    usedExports: true,
    sideEffects: true,
    
    // Code splitting
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 25,
      minSize: 20000
    },
    
    // Minimize
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: { drop_console: true }
        }
      }),
      new CssMinimizerPlugin()
    ]
  },
  
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          // Parallel processing
          'thread-loader',
          {
            loader: 'swc-loader',
            options: {
              jsc: {
                parser: { syntax: 'ecmascript' },
                transform: { react: { runtime: 'automatic' } }
              }
            }
          }
        ]
      }
    ]
  },
  
  // Performance hints
  performance: {
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
  }
};

Vite Configuration

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  
  build: {
    target: 'esnext',
    minify: 'esbuild',
    
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ui: ['@mui/material']
        }
      }
    },
    
    chunkSizeWarningLimit: 600,
    
    // Source maps for production
    sourcemap: false,
    
    // CSS code splitting
    cssCodeSplit: true
  },
  
  server: {
    // HMR configuration
    hmr: {
      overlay: true
    }
  },
  
  optimizeDeps: {
    include: ['react', 'react-dom'],
    esbuildOptions: {
      target: 'esnext'
    }
  }
});

Build Speed Comparison

Operation Webpack 5 Vite
Cold Start 10-30s 0.5-2s
HMR Update 1-3s 50-200ms
Production Build 60-120s 30-60s
Memory Usage High Low

Optimization Checklist

When to Use Each Tool

  • Vite: New projects, fast dev experience, modern browsers only
  • Webpack 5: Complex builds, module federation, legacy browser support
  • Rollup: Libraries, tree-shaking priority, smaller bundles
  • esbuild: Ultra-fast builds, minimal config, bundler or loader

5.6 Micro-Frontend Container Orchestration

Pattern Implementation Description Trade-off
Container App Shell Application Host application that loads and orchestrates remote micro-frontends Centralized routing, shared layout, version coordination
Module Federation Webpack 5 Runtime module sharing, independent deployments, dynamic loading Best integration, version conflicts possible, Webpack-specific
iframe Integration <iframe> Complete isolation, technology agnostic, simple implementation Poor UX (scrolling, routing), SEO issues, performance overhead
Web Components Custom Elements Native browser standard for encapsulated components Framework agnostic, Shadow DOM isolation, limited React support
Single-SPA single-spa framework Meta-framework for orchestrating multiple SPA frameworks Framework agnostic, routing lifecycle, learning curve
Shared Dependencies Singleton sharing Share React, libraries across micro-frontends to reduce duplication Version coordination critical, potential conflicts

Example: Micro-Frontend Architecture

// Container/Host Application
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        header: 'header@http://localhost:3001/remoteEntry.js',
        products: 'products@http://localhost:3002/remoteEntry.js',
        checkout: 'checkout@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        'react-router-dom': { singleton: true }
      }
    })
  ]
};

// Container App.js
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Header = lazy(() => import('header/Header'));
const Products = lazy(() => import('products/ProductList'));
const Checkout = lazy(() => import('checkout/CheckoutFlow'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Header />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products/*" element={<Products />} />
          <Route path="/checkout/*" element={<Checkout />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Remote Micro-Frontend (Header)
// webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'header',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/components/Header'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// Single-SPA approach
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@org/header',
  app: () => System.import('@org/header'),
  activeWhen: '/' // Always active
});

registerApplication({
  name: '@org/products',
  app: () => System.import('@org/products'),
  activeWhen: '/products'
});

start();

// Web Components approach
class ProductCard extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div class="product-card">
        <h3>${this.getAttribute('name')}</h3>
        <p>${this.getAttribute('price')}</p>
      </div>
    `;
  }
}

customElements.define('product-card', ProductCard);

// Usage in any framework
<product-card name="Widget" price="29.99"></product-card>

Communication Patterns

Pattern Implementation
Props/Events Direct component communication
Custom Events Browser CustomEvent API
Event Bus Shared event emitter
Shared State Redux, Zustand in container
postMessage For iframe isolation

Deployment Strategies

  • Independent: Each MFE deployed separately
  • Versioned URLs: /v1/, /v2/ for gradual rollout
  • Feature Flags: Toggle MFE versions at runtime
  • Canary Deploy: Test with subset of users
  • Blue-Green: Switch entire MFE version
  • Fallback: Load previous version on error

Example: Error Handling & Fallbacks

// Error Boundary for Micro-Frontends
class MicroFrontendBoundary extends React.Component {
  state = { hasError: false, error: null };
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('MFE Error:', error, errorInfo);
    // Log to error tracking
    logError(this.props.mfeName, error);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="mfe-fallback">
          <h3>{this.props.mfeName} unavailable</h3>
          <button onClick={() => this.setState({ hasError: false })}>
            Retry
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage
<MicroFrontendBoundary mfeName="Header">
  <Suspense fallback={<HeaderSkeleton />}>
    <Header />
  </Suspense>
</MicroFrontendBoundary>

// Version fallback
const loadMicroFrontend = async (name, version = 'latest') => {
  try {
    return await import(`${name}@${version}/remoteEntry.js`);
  } catch (error) {
    console.warn(`Failed to load ${name}@${version}, trying v1`);
    return await import(`${name}@v1/remoteEntry.js`);
  }
};

Micro-Frontend Best Practices

  • Team Autonomy: Each team owns full stack for their domain
  • Shared Nothing: Minimize shared dependencies, avoid tight coupling
  • Error Isolation: Failures in one MFE shouldn't break entire app
  • Performance: Monitor bundle sizes, avoid duplication, lazy load
  • Versioning: Semantic versioning, backward compatibility, gradual rollout
Complexity Warning: Micro-frontends add significant complexity. Only adopt for large teams (50+ developers) where team autonomy and independent deployments outweigh coordination overhead.

6. Performance Optimization Best Practices

6.1 Core Web Vitals LCP CLS FID

Metric Measurement Good Threshold Optimization Strategy
LCP - Largest Contentful Paint Time to render largest visible element < 2.5s Optimize images, reduce render-blocking resources, use CDN, server-side rendering
FID - First Input Delay Time from user interaction to browser response < 100ms Minimize JavaScript, code splitting, defer non-critical JS, use Web Workers
CLS - Cumulative Layout Shift Visual stability, unexpected layout shifts < 0.1 Size attributes on images/videos, reserve space for ads, avoid inserting content above existing
INP - Interaction to Next Paint NEW Responsiveness to all user interactions < 200ms Optimize event handlers, reduce main thread work, yield to browser
TTFB - Time to First Byte Time from request to first byte received < 800ms CDN, edge caching, optimize database queries, reduce redirects
FCP - First Contentful Paint Time to render any content < 1.8s Inline critical CSS, preload fonts, optimize server response time

Example: Core Web Vitals Optimization

// LCP Optimization
// 1. Preload hero image
<link rel="preload" as="image" href="hero.jpg" />

// 2. Optimize image loading
<img 
  src="hero-800.jpg" 
  srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
  sizes="(max-width: 800px) 400px, 800px"
  loading="eager"
  fetchpriority="high"
  alt="Hero image"
/>

// 3. Use next/image for automatic optimization (Next.js)
import Image from 'next/image';

<Image 
  src="/hero.jpg" 
  width={1200} 
  height={600} 
  priority 
  alt="Hero"
/>

// FID Optimization
// 1. Code splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));

// 2. Defer non-critical JavaScript
<script defer src="analytics.js"></script>

// 3. Use Web Worker for heavy computation
const worker = new Worker('compute.worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
  console.log('Result:', e.data);
};

// CLS Optimization
// 1. Always specify dimensions
<img src="image.jpg" width="800" height="600" alt="..." />

// 2. Reserve space for dynamic content
.ad-slot {
  min-height: 250px; /* Prevent layout shift */
}

// 3. Avoid inserting content above existing
// ❌ Bad: Inserting banner shifts content
document.body.prepend(banner);

// ✅ Good: Reserve space or append
<div class="banner-placeholder" style="height: 60px">
  <!-- Banner loads here without shift -->
</div>

// 4. Font loading without FOUT
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: optional; /* Avoid layout shift */
}

// Measure Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  navigator.sendBeacon('/analytics', body);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

// Performance Observer
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
  }
});

observer.observe({ entryTypes: ['largest-contentful-paint', 'layout-shift'] });

Scoring Thresholds

Metric Good Needs Improvement Poor
LCP <2.5s 2.5-4s >4s
FID <100ms 100-300ms >300ms
CLS <0.1 0.1-0.25 >0.25
INP <200ms 200-500ms >500ms

Measurement Tools

  • Lighthouse: Chrome DevTools audits
  • PageSpeed Insights: Real-world data + lab tests
  • Chrome UX Report: Field data from real users
  • Web Vitals Extension: Real-time monitoring
  • Search Console: Core Web Vitals report
  • web-vitals library: JavaScript measurement
SEO Impact: Core Web Vitals are Google ranking factors. Pages meeting all three thresholds rank higher in search results.

6.2 React Memo useMemo useCallback

Hook/API Purpose Use Case When to Use
React.memo() Memoize component to prevent re-renders Pure components with expensive rendering Component re-renders frequently with same props
useMemo() Memoize expensive computed values Heavy calculations, filtered/sorted lists Computation is expensive and depends on specific values
useCallback() Memoize function references Callbacks passed to memoized children Function passed as prop causes child re-renders
useTransition() Mark updates as non-urgent (React 18) Search filtering, tab switching Keep UI responsive during heavy updates
useDeferredValue() Defer updating a value (React 18) Debouncing without setTimeout Expensive re-renders from input changes

Example: React Performance Optimizations

// React.memo - Prevent unnecessary re-renders
const ExpensiveComponent = React.memo(({ data, onClick }) => {
  console.log('Rendering ExpensiveComponent');
  return (
    <div onClick={onClick}>
      {data.map(item => <Item key={item.id} {...item} />)}
    </div>
  );
});

// Custom comparison function
const UserCard = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    return prevProps.user.id === nextProps.user.id;
  }
);

// useMemo - Memoize expensive calculations
function ProductList({ products, filter }) {
  // ❌ Bad: Recalculates on every render
  const filtered = products.filter(p => p.category === filter);
  
  // ✅ Good: Only recalculates when products or filter changes
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...');
    return products.filter(p => p.category === filter);
  }, [products, filter]);
  
  return <div>{filteredProducts.map(p => <Product key={p.id} {...p} />)}</div>;
}

// useCallback - Memoize function references
function Parent() {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState('');
  
  // ❌ Bad: New function on every render, Child re-renders
  const handleClick = () => setCount(c => c + 1);
  
  // ✅ Good: Same function reference, Child doesn't re-render
  const handleClickMemo = useCallback(() => {
    setCount(c => c + 1);
  }, []); // Empty deps - function never changes
  
  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <MemoizedChild onClick={handleClickMemo} />
    </div>
  );
}

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child render');
  return <button onClick={onClick}>Increment</button>;
});

// useTransition - Non-blocking updates (React 18)
function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // Urgent: Update input immediately
    
    startTransition(() => {
      // Non-urgent: Filter large list without blocking input
      setFilteredResults(expensiveFilter(value));
    });
  };
  
  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <Results data={filteredResults} />}
    </>
  );
}

// useDeferredValue - Defer expensive updates
function SearchWithDefer() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  // deferredQuery updates with lower priority
  const results = useMemo(() => {
    return expensiveFilter(deferredQuery);
  }, [deferredQuery]);
  
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results data={results} />
    </>
  );
}

When to Optimize

When NOT to Optimize

  • Simple components, cheap renders
  • Props change frequently anyway
  • No measured performance issue
  • Premature optimization
  • Small lists (<50 items)
  • Adding complexity without benefit
Warning: Don't overuse memoization! It adds memory overhead and complexity. Measure first with React DevTools Profiler before optimizing.

6.3 Virtual Scrolling Windowing Lists

Library Features Description Use Case
react-window FixedSizeList, VariableSizeList Lightweight virtualization library by same author as react-virtualized Simple lists, grids, efficient rendering of 1000+ items
react-virtualized List, Grid, Table, Collection Feature-rich virtualization with auto-sizing, infinite loading Complex grids, tables, variable heights, legacy projects
TanStack Virtual Framework agnostic Headless UI for virtualizing, works with React/Vue/Solid Custom virtualization, framework flexibility
Intersection Observer Native API Detect visibility, implement infinite scroll without library Lazy loading images, infinite pagination, simple cases
CSS content-visibility content-visibility: auto Browser-native rendering optimization for off-screen content Long pages, off-screen optimization, progressive enhancement

Example: Virtual Scrolling Implementation

// react-window - Fixed size list
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      Row {index}: {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// Variable size list
import { VariableSizeList } from 'react-window';

function VariableList({ items }) {
  const getItemSize = (index) => {
    // Dynamic height based on content
    return items[index].content.length > 100 ? 120 : 60;
  };
  
  return (
    <VariableSizeList
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
}

// Infinite loading with react-window
import InfiniteLoader from 'react-window-infinite-loader';

function InfiniteList({ hasNextPage, loadMoreItems }) {
  const itemCount = hasNextPage ? items.length + 1 : items.length;
  const isItemLoaded = (index) => !hasNextPage || index < items.length;
  
  return (
    <InfiniteLoader
      isItemLoaded={isItemLoaded}
      itemCount={itemCount}
      loadMoreItems={loadMoreItems}
    >
      {({ onItemsRendered, ref }) => (
        <FixedSizeList
          height={600}
          itemCount={itemCount}
          itemSize={50}
          onItemsRendered={onItemsRendered}
          ref={ref}
        >
          {Row}
        </FixedSizeList>
      )}
    </InfiniteLoader>
  );
}

// Intersection Observer for infinite scroll
function useInfiniteScroll(callback) {
  const observerRef = useRef(null);
  const loadMoreRef = useCallback((node) => {
    if (observerRef.current) observerRef.current.disconnect();
    
    observerRef.current = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        callback();
      }
    }, { threshold: 1.0 });
    
    if (node) observerRef.current.observe(node);
  }, [callback]);
  
  return loadMoreRef;
}

function InfiniteScrollList({ items, loadMore, hasMore }) {
  const loadMoreRef = useInfiniteScroll(loadMore);
  
  return (
    <div>
      {items.map((item) => <Item key={item.id} {...item} />)}
      {hasMore && <div ref={loadMoreRef}>Loading...</div>}
    </div>
  );
}

// CSS content-visibility
.long-content {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* Estimated height */
}

Performance Impact

List Size Without Virtual With Virtual
100 items ~100ms ~20ms
1,000 items ~800ms ~25ms
10,000 items ~5000ms ~30ms
Memory All DOM nodes Only visible

Library Comparison

Feature react-window react-virtualized
Bundle Size 6KB 28KB
Variable Heights
Auto-sizing
Performance Excellent Good
When to Virtualize: Lists with 100+ items, tables with many rows, chat messages, feeds. Saves 90% render time for large lists.

6.4 Image Optimization WebP AVIF

Format Compression Browser Support Use Case
AVIF NEW 50% smaller than JPEG Chrome 85+, Firefox 93+ Best quality/size ratio, modern browsers, with fallback
WebP 25-35% smaller than JPEG 95% global support Production-ready, excellent compression, wide support
JPEG Baseline format Universal Fallback format, legacy browser support
PNG Lossless, larger files Universal Transparency needed, logos, graphics with text
SVG Vector, scalable Universal Icons, logos, illustrations, infinite scaling

Example: Image Optimization Techniques

// Modern image with fallback (picture element)
<picture>
  <source srcset="hero.avif" type="image/avif" />
  <source srcset="hero.webp" type="image/webp" />
  <img src="hero.jpg" alt="Hero image" width="1200" height="600" />
</picture>

// Responsive images with srcset
<img
  src="product-800.jpg"
  srcset="
    product-400.jpg 400w,
    product-800.jpg 800w,
    product-1200.jpg 1200w,
    product-1600.jpg 1600w
  "
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  alt="Product"
  loading="lazy"
  decoding="async"
/>

// Next.js Image component (automatic optimization)
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  quality={85}
  priority // LCP image
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

// Lazy loading with Intersection Observer
function LazyImage({ src, alt }) {
  const [imageSrc, setImageSrc] = useState(null);
  const imgRef = useRef();
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setImageSrc(src);
          observer.disconnect();
        }
      },
      { rootMargin: '50px' } // Load 50px before visible
    );
    
    if (imgRef.current) observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, [src]);
  
  return (
    <img
      ref={imgRef}
      src={imageSrc || 'placeholder.jpg'}
      alt={alt}
      loading="lazy"
    />
  );
}

// Progressive JPEG with blur-up
.image-container {
  position: relative;
  overflow: hidden;
}

.image-placeholder {
  filter: blur(20px);
  transform: scale(1.1);
  transition: opacity 0.3s;
}

.image-placeholder.loaded {
  opacity: 0;
}

// Sharp (Node.js) - Generate optimized images
const sharp = require('sharp');

await sharp('input.jpg')
  .resize(800, 600, { fit: 'cover' })
  .webp({ quality: 85 })
  .toFile('output.webp');

await sharp('input.jpg')
  .resize(800, 600)
  .avif({ quality: 80 })
  .toFile('output.avif');

// Cloudinary transformation URL
<img src="https://res.cloudinary.com/demo/image/upload/
  w_800,h_600,c_fill,f_auto,q_auto/sample.jpg" />
// f_auto: automatic format (AVIF/WebP/JPEG)
// q_auto: automatic quality

Image Optimization Checklist

Size Comparison (1MB JPEG)

Format File Size Savings
JPEG (baseline) 1000 KB -
WebP 650 KB 35%
AVIF 500 KB 50%
JPEG (optimized) 750 KB 25%

Image Loading Strategy

  • Above fold: Priority load, eager, preload LCP image
  • Below fold: Lazy load with loading="lazy"
  • Format: Serve AVIF → WebP → JPEG with picture element
  • Responsive: Multiple sizes with srcset, let browser choose

6.5 Font Loading Strategy FOIT FOUT

Strategy font-display Behavior Use Case
FOIT - Flash of Invisible Text block Hide text until font loads (3s timeout) Brand-critical fonts where design consistency matters most
FOUT - Flash of Unstyled Text swap Show fallback immediately, swap when loaded Content-first, best for performance, recommended
font-display: optional optional Use font if cached, otherwise use fallback permanently Best CLS score, no layout shift, fast connections
font-display: fallback fallback 100ms block period, 3s swap period Balance between swap and optional
Preload Fonts <link rel="preload"> Load critical fonts early in page load Above-fold fonts, reduce FOUT duration

Example: Font Loading Optimization

// @font-face with font-display
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2'),
       url('/fonts/custom.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* Show fallback immediately */
}

// Preload critical fonts
<head>
  <link
    rel="preload"
    href="/fonts/custom.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  />
</head>

// Google Fonts with font-display
<link
  href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
  rel="stylesheet"
/>

// Variable fonts (single file, multiple weights)
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2-variations');
  font-weight: 100 900; /* All weights in one file */
  font-display: swap;
}

// Font loading API
const font = new FontFace(
  'CustomFont',
  'url(/fonts/custom.woff2)',
  { weight: '400', style: 'normal' }
);

await font.load();
document.fonts.add(font);

// React hook for font loading
function useFontLoaded(fontFamily) {
  const [loaded, setLoaded] = useState(false);
  
  useEffect(() => {
    document.fonts.ready.then(() => {
      const fontLoaded = document.fonts.check(`12px ${fontFamily}`);
      setLoaded(fontLoaded);
    });
  }, [fontFamily]);
  
  return loaded;
}

function App() {
  const fontLoaded = useFontLoaded('CustomFont');
  
  return (
    <div className={fontLoaded ? 'font-loaded' : 'font-loading'}>
      Content
    </div>
  );
}

// System font stack (no loading needed)
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 
  Oxygen, Ubuntu, Cantarell, sans-serif;

// Fallback font matching (reduce layout shift)
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%; /* Match fallback metrics */
  ascent-override: 95%;
  descent-override: 25%;
}

// Next.js Font Optimization
import { Inter } from 'next/font/google';

const inter = Inter({ 
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter'
});

export default function App({ Component, pageProps }) {
  return (
    <main className={inter.className}>
      <Component {...pageProps} />
    </main>
  );
}

font-display Values

Value Block Period Swap Period
auto Varies Varies
block ~3s Infinite
swap 0ms Infinite
fallback ~100ms ~3s
optional ~100ms None

Best Practices

  • Use font-display: swap for most fonts
  • Preload 1-2 critical fonts only
  • Use WOFF2 format (best compression)
  • Self-host fonts for better caching
  • Variable fonts reduce file count
  • Subset fonts to needed characters
  • Match fallback font metrics
  • Consider system font stack
CLS Impact: Use font-display: optional for zero layout shift or match fallback font metrics with size-adjust to minimize FOUT visual impact.

6.6 Critical CSS Above-fold Loading

Technique Implementation Description Benefit
Inline Critical CSS <style> in <head> Extract and inline CSS needed for above-fold content Eliminate render-blocking CSS, faster FCP
Async CSS Loading media="print" onload Load non-critical CSS asynchronously without blocking render Non-blocking CSS, progressive enhancement
Preload CSS <link rel="preload"> High-priority fetch for critical stylesheets Earlier CSS discovery, parallel loading
CSS-in-JS SSR styled-components, Emotion Extract critical CSS during server-side rendering Automatic critical CSS extraction, component-level
Tailwind Purge content: ['./src/**'] Remove unused utility classes from production build 90% size reduction, only ship used CSS

Example: Critical CSS Implementation

// Inline critical CSS in HTML
<!DOCTYPE html>
<html>
<head>
  <style>
    /* Critical CSS - above fold only */
    body { margin: 0; font-family: system-ui; }
    .header { height: 60px; background: #333; }
    .hero { height: 400px; background: url(hero.jpg); }
    /* ... only styles for visible content ... */
  </style>
  
  <!-- Async load remaining CSS -->
  <link
    rel="preload"
    href="/styles/main.css"
    as="style"
    onload="this.onload=null;this.rel='stylesheet'"
  />
  <noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>
<body>...</body>
</html>

// Critical CSS extraction (Node.js)
const critical = require('critical');

await critical.generate({
  inline: true,
  base: 'dist/',
  src: 'index.html',
  target: {
    html: 'index-critical.html',
    css: 'critical.css'
  },
  width: 1300,
  height: 900
});

// Webpack plugin
const CriticalCssPlugin = require('critical-css-webpack-plugin');

module.exports = {
  plugins: [
    new CriticalCssPlugin({
      base: 'dist/',
      src: 'index.html',
      inline: true,
      minify: true,
      extract: true,
      width: 1300,
      height: 900
    })
  ]
};

// Next.js with styled-components
// pages/_document.js
import Document from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;
    
    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />)
        });
      
      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  }
}

// Tailwind CSS purge configuration
// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}'
  ],
  theme: {},
  plugins: []
};

// PurgeCSS configuration
const purgecss = require('@fullhuman/postcss-purgecss');

module.exports = {
  plugins: [
    purgecss({
      content: ['./src/**/*.html', './src/**/*.jsx'],
      safelist: ['random', 'yep', 'button'] // Classes to keep
    })
  ]
};

// Loadable Components with CSS
import loadable from '@loadable/component';

const HeavyComponent = loadable(() => import('./Heavy'), {
  fallback: <div>Loading...</div>
});

// Async CSS loading utility
function loadCSS(href) {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = href;
  document.head.appendChild(link);
  
  return new Promise((resolve, reject) => {
    link.onload = resolve;
    link.onerror = reject;
  });
}

// Usage
await loadCSS('/styles/modal.css');
showModal();

Critical CSS Tools

Tool Purpose
Critical Extract & inline critical CSS
Critters Webpack plugin for critical CSS
PurgeCSS Remove unused CSS
UnCSS Remove unused CSS selectors
penthouse Critical path CSS generator

CSS Loading Patterns

  • Critical: Inline in <head>
  • Above-fold: Preload high priority
  • Below-fold: Async load with media hack
  • Route-specific: Code split per route
  • Component CSS: CSS-in-JS with SSR
  • Utility CSS: Purge unused (Tailwind)

Critical CSS Strategy

  • Inline: ~14KB of critical CSS for above-fold content
  • Async: Load remaining CSS without blocking render
  • Purge: Remove unused CSS, target <50KB total
  • Result: Faster FCP (First Contentful Paint) by 1-2 seconds
Balance: Don't inline too much CSS (>14KB hurts performance). Focus on truly critical above-fold styles only.

7. Frontend Security Implementation Patterns

7.1 OWASP Frontend Top 10 XSS CSRF

Vulnerability Attack Type Risk Level Prevention
XSS - Cross-Site Scripting Inject malicious scripts into trusted websites CRITICAL Escape output, CSP headers, DOMPurify sanitization
CSRF - Cross-Site Request Forgery Execute unwanted actions on authenticated user's behalf HIGH CSRF tokens, SameSite cookies, double submit pattern
Clickjacking Trick users into clicking hidden malicious elements MEDIUM X-Frame-Options: DENY, CSP frame-ancestors directive
Open Redirect Redirect users to malicious external sites MEDIUM Validate redirect URLs, whitelist allowed domains
DOM-based XSS Manipulate client-side JavaScript to inject scripts CRITICAL Avoid innerHTML, use textContent, sanitize user input
Sensitive Data Exposure Expose tokens, keys, or PII in client-side code HIGH Never store secrets client-side, HttpOnly cookies, encryption

Example: XSS and CSRF Prevention

// ❌ Vulnerable to XSS
function displayUserInput(input) {
  document.getElementById('output').innerHTML = input;
  // Attacker input: <script>alert('XSS')</script>
}

// ✅ Safe: Use textContent
function displayUserInputSafe(input) {
  document.getElementById('output').textContent = input;
  // Scripts won't execute
}

// ✅ React automatically escapes
function UserProfile({ name }) {
  return <div>{name}</div>; // Safe by default
}

// ❌ Dangerous: dangerouslySetInnerHTML
function RichText({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
  // Can execute malicious scripts
}

// ✅ Safe: Sanitize with DOMPurify
import DOMPurify from 'dompurify';

function SafeRichText({ html }) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href']
  });
  
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// CSRF Protection - Add token to requests
// Backend sets CSRF token in cookie
// Frontend includes token in headers
function apiRequest(url, data) {
  const csrfToken = getCookie('csrf-token');
  
  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken
    },
    credentials: 'include', // Send cookies
    body: JSON.stringify(data)
  });
}

// Double Submit Cookie Pattern
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

// SameSite Cookie (Backend sets)
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

// Axios CSRF configuration
import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrf-token';
axios.defaults.xsrfHeaderName = 'X-CSRF-Token';
axios.defaults.withCredentials = true;

// Prevent Clickjacking
// Set in HTTP headers (backend)
X-Frame-Options: DENY
// or
Content-Security-Policy: frame-ancestors 'none'

// Validate Redirects
function safeRedirect(url) {
  const allowedDomains = ['example.com', 'app.example.com'];
  try {
    const urlObj = new URL(url, window.location.origin);
    if (allowedDomains.includes(urlObj.hostname)) {
      window.location.href = url;
    } else {
      console.error('Redirect not allowed:', url);
    }
  } catch (e) {
    console.error('Invalid URL:', url);
  }
}

// DOM-based XSS Prevention
// ❌ Dangerous
element.innerHTML = userInput;
location.href = userInput;
eval(userInput);

// ✅ Safe alternatives
element.textContent = userInput;
element.setAttribute('href', sanitizedUrl);
// Never use eval()

// Input Validation
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

function sanitizeInput(input) {
  return input
    .trim()
    .replace(/[<>"']/g, (char) => {
      const escapeChars = {
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;'
      };
      return escapeChars[char];
    });
}

XSS Types

Type Attack Vector
Stored XSS Malicious script stored in database
Reflected XSS Script in URL, reflected by server
DOM XSS Client-side script manipulation

Security Headers

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin
  • Permissions-Policy: Feature control
Never Trust User Input: Always validate and sanitize on both client and server. Client-side validation is for UX, server-side is for security.

7.2 Content Security Policy CSP Headers

Directive Purpose Example Value Use Case
default-src Fallback for all fetch directives 'self' Only allow resources from same origin
script-src Valid sources for JavaScript 'self' 'nonce-abc123' Prevent inline scripts, XSS attacks
style-src Valid sources for stylesheets 'self' 'unsafe-inline' Allow same-origin and inline styles
img-src Valid sources for images 'self' https://cdn.example.com Allow images from specific domains
connect-src Valid sources for fetch, XHR, WebSocket 'self' https://api.example.com Restrict API endpoints
frame-ancestors Valid sources that can embed this page 'none' Prevent clickjacking
upgrade-insecure-requests Upgrade HTTP to HTTPS (no value) Force HTTPS for all resources

Example: CSP Implementation

// Basic CSP Header (backend)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.example.com

// Strict CSP with nonces
// Backend generates random nonce for each request
const nonce = crypto.randomBytes(16).toString('base64');

Content-Security-Policy: 
  default-src 'self';
  script-src 'self' 'nonce-${nonce}';
  style-src 'self' 'nonce-${nonce}';
  img-src 'self' https: data:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;

// HTML with nonce
<script nonce="${nonce}">
  console.log('This script is allowed');
</script>

// CSP Meta Tag (not recommended for production)
<meta http-equiv="Content-Security-Policy" 
  content="default-src 'self'; script-src 'self'">

// Report-Only Mode (testing)
Content-Security-Policy-Report-Only: 
  default-src 'self';
  report-uri /csp-violation-report

// CSP Violation Reporting
Content-Security-Policy: 
  default-src 'self';
  report-uri https://example.report-uri.com/r/d/csp/enforce

// Handle CSP violations
document.addEventListener('securitypolicyviolation', (e) => {
  console.log('CSP Violation:', {
    blockedURI: e.blockedURI,
    violatedDirective: e.violatedDirective,
    originalPolicy: e.originalPolicy
  });
  
  // Send to logging service
  fetch('/csp-report', {
    method: 'POST',
    body: JSON.stringify({
      blockedURI: e.blockedURI,
      violatedDirective: e.violatedDirective
    })
  });
});

// Next.js CSP Configuration
// next.config.js
const cspHeader = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
`;

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/\s{2,}/g, ' ').trim()
          }
        ]
      }
    ];
  }
};

// React Helmet for CSP
import { Helmet } from 'react-helmet';

function App() {
  return (
    <Helmet>
      <meta httpEquiv="Content-Security-Policy" 
        content="default-src 'self'; script-src 'self'" />
    </Helmet>
  );
}

CSP Levels

Level Strictness Example
Level 1 - Permissive Allows most sources default-src *
Level 2 - Moderate Restricts to trusted domains default-src 'self' cdn.com
Level 3 - Strict Nonce/hash-based script-src 'nonce-xxx'

Common CSP Issues

  • Inline scripts blocked: Use nonces or external files
  • eval() blocked: Avoid eval, use JSON.parse
  • CDN resources: Add CDN domains to directives
  • Google Analytics: Add google-analytics.com
  • Fonts blocked: Add font CDN to font-src
CSP Benefits: Blocks 99% of XSS attacks. Start with report-only mode to identify violations before enforcement.

7.3 JWT Token Storage HttpOnly Cookies

Storage Method Security XSS Vulnerable CSRF Vulnerable Use Case
HttpOnly Cookie BEST ❌ No ✅ Yes (mitigated with SameSite) Recommended for authentication tokens
localStorage POOR ✅ Yes ❌ No Non-sensitive data, user preferences
sessionStorage POOR ✅ Yes ❌ No Temporary session data, cleared on tab close
Memory (React state) Good ❌ No ❌ No Lost on refresh, single-page apps
Secure + HttpOnly + SameSite EXCELLENT ❌ No ❌ No Production authentication, maximum security

Example: Secure JWT Token Handling

// ❌ INSECURE: localStorage JWT storage
function login(credentials) {
  const response = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify(credentials)
  });
  
  const { token } = await response.json();
  localStorage.setItem('token', token); // Vulnerable to XSS!
}

// ✅ SECURE: HttpOnly Cookie (Backend)
// Express.js backend
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await authenticate(email, password);
  
  if (user) {
    const token = jwt.sign({ userId: user.id }, SECRET);
    
    res.cookie('token', token, {
      httpOnly: true,     // Not accessible via JavaScript
      secure: true,       // Only sent over HTTPS
      sameSite: 'strict', // CSRF protection
      maxAge: 3600000     // 1 hour
    });
    
    res.json({ success: true, user });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

// Frontend: Cookie sent automatically
function fetchProtectedData() {
  return fetch('/api/protected', {
    credentials: 'include' // Send cookies
  });
}

// Refresh Token Pattern (Best Practice)
// Backend: Access token (short-lived) + Refresh token (long-lived)
app.post('/api/login', async (req, res) => {
  const user = await authenticate(req.body);
  
  const accessToken = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '15m' });
  const refreshToken = jwt.sign({ userId: user.id }, REFRESH_SECRET, { expiresIn: '7d' });
  
  // Store refresh token in database
  await saveRefreshToken(user.id, refreshToken);
  
  // Send both as HttpOnly cookies
  res.cookie('accessToken', accessToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 15 * 60 * 1000 // 15 minutes
  });
  
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    path: '/api/refresh' // Only sent to refresh endpoint
  });
  
  res.json({ success: true });
});

// Refresh endpoint
app.post('/api/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'No refresh token' });
  }
  
  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
    const isValid = await validateRefreshToken(decoded.userId, refreshToken);
    
    if (isValid) {
      const newAccessToken = jwt.sign({ userId: decoded.userId }, SECRET, { expiresIn: '15m' });
      
      res.cookie('accessToken', newAccessToken, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        maxAge: 15 * 60 * 1000
      });
      
      res.json({ success: true });
    } else {
      res.status(401).json({ error: 'Invalid refresh token' });
    }
  } catch (error) {
    res.status(401).json({ error: 'Token expired' });
  }
});

// Frontend: Axios interceptor for auto-refresh
import axios from 'axios';

axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        await axios.post('/api/refresh', {}, { withCredentials: true });
        return axios(originalRequest);
      } catch (refreshError) {
        // Refresh failed, redirect to login
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

// Logout (Clear cookies)
app.post('/api/logout', (req, res) => {
  res.clearCookie('accessToken');
  res.clearCookie('refreshToken');
  res.json({ success: true });
});

// BFF (Backend for Frontend) Pattern
// API Gateway handles tokens, frontend never sees them
// Frontend → BFF → Microservices
// BFF stores tokens securely, frontend uses session
Attribute Purpose
HttpOnly Blocks JavaScript access
Secure HTTPS only transmission
SameSite=Strict No cross-site requests
SameSite=Lax Allow top-level navigation
SameSite=None Allow all (requires Secure)
Path Restrict cookie scope
Domain Subdomain sharing

Token Storage Comparison

Method XSS CSRF
localStorage ❌ Vulnerable ✅ Safe
Cookie (HttpOnly) ✅ Safe ⚠️ Mitigate
Memory only ✅ Safe ✅ Safe
Never Store Sensitive Data in localStorage: Any XSS vulnerability can steal tokens. Use HttpOnly cookies with SameSite=Strict for maximum security.

7.4 Input Validation Sanitization DOMPurify

Library/Tool Purpose Use Case Example
DOMPurify Sanitize HTML, prevent XSS Rich text editors, user-generated HTML DOMPurify.sanitize(html)
validator.js Validate and sanitize strings Email, URL, credit card validation validator.isEmail(input)
Yup / Zod Schema validation Form validation, type safety schema.parse(data)
sanitize-html HTML sanitization with config Node.js backend, allowlist tags sanitizeHtml(html, options)
OWASP Java Encoder Context-aware encoding Backend Java applications Encode.forHtml(input)

Example: Input Validation and Sanitization

// DOMPurify - Sanitize HTML
import DOMPurify from 'dompurify';

function RichTextDisplay({ html }) {
  const cleanHtml = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: ['href', 'title'],
    ALLOW_DATA_ATTR: false
  });
  
  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}

// Strict sanitization
const strictClean = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: [], // No tags allowed, text only
  ALLOWED_ATTR: []
});

// Allow specific attributes
const customClean = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: ['a', 'img'],
  ALLOWED_ATTR: ['href', 'src', 'alt'],
  ALLOWED_URI_REGEXP: /^https?:/ // Only http/https URLs
});

// validator.js - String validation
import validator from 'validator';

function validateUserInput(input) {
  const errors = {};
  
  if (!validator.isEmail(input.email)) {
    errors.email = 'Invalid email address';
  }
  
  if (!validator.isURL(input.website, { protocols: ['http', 'https'] })) {
    errors.website = 'Invalid URL';
  }
  
  if (!validator.isStrongPassword(input.password, {
    minLength: 8,
    minLowercase: 1,
    minUppercase: 1,
    minNumbers: 1,
    minSymbols: 1
  })) {
    errors.password = 'Weak password';
  }
  
  return errors;
}

// Sanitize strings
const clean = validator.escape(input); // Escape HTML entities
const trimmed = validator.trim(input);
const normalized = validator.normalizeEmail(email);

// Zod - Schema validation (TypeScript)
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18).max(120),
  username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
  website: z.string().url().optional(),
  role: z.enum(['user', 'admin', 'moderator'])
});

function validateUser(data) {
  try {
    const validated = userSchema.parse(data);
    return { success: true, data: validated };
  } catch (error) {
    return { success: false, errors: error.errors };
  }
}

// Yup - Schema validation (React)
import * as yup from 'yup';

const loginSchema = yup.object({
  email: yup.string().email('Invalid email').required('Email required'),
  password: yup.string().min(8, 'Min 8 characters').required('Password required')
});

// Formik integration
import { Formik } from 'formik';

<Formik
  initialValues={{ email: '', password: '' }}
  validationSchema={loginSchema}
  onSubmit={(values) => console.log(values)}
>
  {/* Form fields */}
</Formik>

// Custom validation functions
function sanitizeFilename(filename) {
  return filename
    .replace(/[^a-zA-Z0-9.-]/g, '_') // Replace special chars
    .replace(/\.{2,}/g, '.') // Prevent path traversal
    .substring(0, 255); // Limit length
}

function validateRedirect(url) {
  const allowedDomains = ['example.com', 'app.example.com'];
  
  try {
    const urlObj = new URL(url, window.location.origin);
    return allowedDomains.includes(urlObj.hostname);
  } catch {
    return false;
  }
}

function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;'
  };
  
  return text.replace(/[&<>"'\/]/g, (char) => map[char]);
}

// React Hook Form with Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(userSchema)
  });
  
  const onSubmit = (data) => {
    console.log('Validated data:', data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
    </form>
  );
}

Validation Checklist

Common Validation Rules

  • Email: RFC 5322 compliant regex
  • Password: 8+ chars, mixed case, numbers
  • URL: Valid protocol, no javascript:
  • Phone: Country-specific format
  • Credit Card: Luhn algorithm
  • Username: Alphanumeric, 3-20 chars
Defense in Depth: Validate on both client and server. Client validation is for UX, server validation is for security.

7.5 HTTPS TLS Certificate Pinning

Security Measure Implementation Purpose Use Case
HTTPS Enforcement Redirect HTTP to HTTPS, HSTS header Encrypt all traffic, prevent MITM attacks All production websites, mandatory for authentication
HSTS - HTTP Strict Transport Security Strict-Transport-Security: max-age=31536000 Force HTTPS for specified duration Prevent SSL stripping attacks, browser enforcement
Certificate Pinning Pin specific certificate or public key Prevent rogue CA certificates, targeted attacks Mobile apps, high-security APIs
TLS 1.3 Enable latest TLS protocol Faster handshake, stronger encryption Modern browsers, improved performance and security
Certificate Transparency Monitor CT logs for certificates Detect unauthorized certificate issuance Large organizations, prevent CA compromise

Example: HTTPS and TLS Configuration

// HSTS Header (Backend)
// Express.js
app.use((req, res, next) => {
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );
  next();
});

// Helmet.js (Express security headers)
const helmet = require('helmet');

app.use(helmet({
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// Nginx HTTPS redirect
server {
  listen 80;
  server_name example.com;
  return 301 https://$server_name$request_uri;
}

server {
  listen 443 ssl http2;
  server_name example.com;
  
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers on;
  
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

// Next.js HTTPS enforcement
// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/:path*',
        has: [{ type: 'header', key: 'x-forwarded-proto', value: 'http' }],
        destination: 'https://example.com/:path*',
        permanent: true
      }
    ];
  }
};

// Certificate Pinning (Mobile - React Native)
// iOS Info.plist
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSPinnedDomains</key>
  <dict>
    <key>api.example.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSPinnedCAIdentities</key>
      <array>
        <dict>
          <key>SPKI-SHA256-BASE64</key>
          <string>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</string>
        </dict>
      </array>
    </dict>
  </dict>
</dict>

// Public Key Pinning (Deprecated - use Certificate Transparency)
// Public-Key-Pins: pin-sha256="base64=="; max-age=5184000

// Detect HTTPS in JavaScript
if (window.location.protocol !== 'https:') {
  window.location.href = 'https:' + window.location.href.substring(window.location.protocol.length);
}

// Check for mixed content
const checkMixedContent = () => {
  const insecureResources = Array.from(document.querySelectorAll('img, script, link'))
    .filter(el => {
      const src = el.src || el.href;
      return src && src.startsWith('http://');
    });
  
  if (insecureResources.length > 0) {
    console.warn('Mixed content detected:', insecureResources);
  }
};

// Let's Encrypt SSL Certificate (Free)
// Certbot installation
sudo certbot --nginx -d example.com -d www.example.com

// Auto-renewal
sudo certbot renew --dry-run

// Cloudflare SSL modes
// Flexible: Client → Cloudflare (HTTPS), Cloudflare → Origin (HTTP)
// Full: Both encrypted, but origin certificate not verified
// Full (Strict): Both encrypted, origin certificate verified
// Recommended: Full (Strict)

// TLS 1.3 Configuration
ssl_protocols TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384';

// CSP upgrade-insecure-requests
Content-Security-Policy: upgrade-insecure-requests;

TLS Best Practices

Certificate Authorities

  • Let's Encrypt: Free, automated, 90-day certs
  • DigiCert: Commercial, EV certificates
  • Cloudflare: Free SSL with CDN
  • AWS Certificate Manager: Free for AWS resources
  • Self-signed: Development only, not trusted
HSTS Preload: Submit domain to hstspreload.org for browser preload list. Browsers will always use HTTPS, even on first visit.

7.6 Subresource Integrity SRI Verification

Feature Implementation Purpose Browser Support
SRI - Subresource Integrity integrity="sha384-hash" Verify CDN resources haven't been tampered with 95%+ modern browsers
crossorigin attribute crossorigin="anonymous" Enable CORS for integrity checking Required with SRI for cross-origin resources
Hash Algorithms sha256, sha384, sha512 Cryptographic hash of resource content Use sha384 or sha512 for best security
Fallback Scripts Load local copy if CDN fails Reliability when CDN is down or compromised Manual implementation with onerror handler

Example: SRI Implementation

// SRI for CDN resources
<script
  src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"
  integrity="sha384-KyZXEAg3QhqLMpG8r+Knujsl5+5hb7iegSk7F9+bL5wh8gL3LmqTLw3yPY4u6Rq6"
  crossorigin="anonymous"
></script>

// Multiple hashes (fallback algorithms)
<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4= sha512-hash"
  crossorigin="anonymous"
></script>

// CSS with SRI
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css"
  integrity="sha384-hash"
  crossorigin="anonymous"
/>

// Generate SRI hash
// Using OpenSSL
openssl dgst -sha384 -binary FILENAME.js | openssl base64 -A

// Using Node.js
const crypto = require('crypto');
const fs = require('fs');

function generateSRI(filePath) {
  const content = fs.readFileSync(filePath);
  const hash = crypto.createHash('sha384').update(content).digest('base64');
  return `sha384-${hash}`;
}

const sri = generateSRI('./script.js');
console.log(`integrity="${sri}"`);

// Using SRI online tools
// https://www.srihash.org/

// Webpack SRI Plugin
const SriPlugin = require('webpack-subresource-integrity');

module.exports = {
  output: {
    crossOriginLoading: 'anonymous'
  },
  plugins: [
    new SriPlugin({
      hashFuncNames: ['sha256', 'sha384'],
      enabled: process.env.NODE_ENV === 'production'
    })
  ]
};

// Next.js with SRI
// next.config.js
module.exports = {
  experimental: {
    sri: {
      algorithm: 'sha384'
    }
  }
};

// Fallback for CDN failure
<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-hash"
  crossorigin="anonymous"
  onerror="loadFallback()"
></script>

<script>
function loadFallback() {
  const script = document.createElement('script');
  script.src = '/local/library.js'; // Local fallback
  document.head.appendChild(script);
}
</script>

// React CDN with fallback
<script
  src="https://unpkg.com/react@18/umd/react.production.min.js"
  integrity="sha384-hash"
  crossorigin="anonymous"
></script>
<script>
  if (typeof React === 'undefined') {
    document.write('<script src="/vendor/react.js"><\/script>');
  }
</script>

// Automated SRI in build process
// package.json script
{
  "scripts": {
    "sri": "node scripts/generate-sri.js"
  }
}

// scripts/generate-sri.js
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const distDir = path.join(__dirname, '../dist');
const files = fs.readdirSync(distDir);

files.forEach((file) => {
  if (file.endsWith('.js') || file.endsWith('.css')) {
    const filePath = path.join(distDir, file);
    const content = fs.readFileSync(filePath);
    const hash = crypto.createHash('sha384').update(content).digest('base64');
    
    console.log(`${file}: sha384-${hash}`);
  }
});

SRI Benefits

  • Prevents CDN tampering/injection
  • Detects modified third-party scripts
  • Protects against MITM attacks on CDN
  • Ensures resource authenticity
  • No performance overhead
  • Browser enforced validation

When to Use SRI

Resource Type Use SRI?
Public CDN scripts ✅ Always
Third-party libraries ✅ Always
Same-origin assets ❌ Optional
Dynamic content ❌ Not possible

Frontend Security Summary

  • XSS Prevention: Sanitize input with DOMPurify, use CSP, avoid innerHTML
  • CSRF Protection: HttpOnly cookies with SameSite=Strict, CSRF tokens
  • Token Storage: HttpOnly cookies (best), never localStorage for auth tokens
  • HTTPS: Enforce HTTPS, HSTS preload, TLS 1.3, Let's Encrypt certificates
  • CSP: Implement strict Content Security Policy with nonces
  • SRI: Verify CDN resources with integrity hashes
  • Validation: Validate client and server, never trust user input
Security is Layered: No single technique is foolproof. Implement multiple security layers: CSP + HttpOnly cookies + input validation + HTTPS + SRI for comprehensive protection.

8. API Integration Modern Patterns

8.1 RESTful Services Axios Fetch API

Tool/Library Features Use Case Advantages
Fetch API Native browser API, Promise-based Modern browsers, simple requests, no dependencies Built-in, streaming support, standards-compliant
Axios Interceptors, auto JSON transform, timeout Complex apps, request/response manipulation, older browsers Request/response interceptors, automatic retries, progress tracking
TanStack Query (React Query) Caching, auto refetch, optimistic updates Data fetching with caching, background sync, pagination Built-in cache, loading states, automatic refetching
SWR Stale-while-revalidate, real-time updates Real-time dashboards, fast UI updates, simple API Lightweight, automatic revalidation, focus tracking
RTK Query Redux integration, code generation Redux apps, type-safe APIs, normalized cache Redux DevTools, normalized cache, tag-based invalidation

Example: RESTful API Integration

// Fetch API - Native
async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`
      }
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

// POST request with Fetch
async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });
  
  return response.json();
}

// Axios - Configure instance
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// Request interceptor
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor
api.interceptors.response.use(
  (response) => response.data,
  async (error) => {
    const originalRequest = error.config;
    
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        const { token } = await refreshToken();
        localStorage.setItem('token', token);
        originalRequest.headers.Authorization = `Bearer ${token}`;
        return api(originalRequest);
      } catch (refreshError) {
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

// Use Axios
async function getUsers() {
  try {
    const users = await api.get('/users');
    return users;
  } catch (error) {
    console.error('Error fetching users:', error);
  }
}

async function updateUser(id, data) {
  return api.patch(`/users/${id}`, data);
}

// TanStack Query (React Query)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function UsersList() {
  const queryClient = useQueryClient();
  
  // Fetch users
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => api.get('/users'),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000
  });
  
  // Create user mutation
  const createMutation = useMutation({
    mutationFn: (newUser) => api.post('/users', newUser),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      {users.map(user => <UserCard key={user.id} user={user} />)}
      <button onClick={() => createMutation.mutate({ name: 'New User' })}>
        Add User
      </button>
    </div>
  );
}

// SWR - Simple data fetching
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

function Dashboard() {
  const { data, error, isLoading } = useSWR('/api/dashboard', fetcher, {
    refreshInterval: 3000, // Refresh every 3 seconds
    revalidateOnFocus: true
  });
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load</div>;
  
  return <div>{data.stats}</div>;
}

// SWR with mutation
import useSWRMutation from 'swr/mutation';

async function updateUser(url, { arg }) {
  return fetch(url, {
    method: 'PATCH',
    body: JSON.stringify(arg)
  }).then(res => res.json());
}

function UserProfile({ userId }) {
  const { data } = useSWR(`/api/users/${userId}`, fetcher);
  const { trigger, isMutating } = useSWRMutation(`/api/users/${userId}`, updateUser);
  
  return (
    <div>
      <h1>{data?.name}</h1>
      <button onClick={() => trigger({ name: 'Updated Name' })} disabled={isMutating}>
        Update
      </button>
    </div>
  );
}

Fetch vs Axios

Feature Fetch Axios
Built-in ✅ Yes ❌ External
JSON Transform Manual ✅ Automatic
Interceptors ❌ No ✅ Yes
Timeout AbortController ✅ Built-in
Progress Events Manual ✅ Built-in

HTTP Methods

  • GET: Retrieve resources
  • POST: Create new resources
  • PUT: Replace entire resource
  • PATCH: Partial update
  • DELETE: Remove resource
  • HEAD: Get headers only
  • OPTIONS: Check allowed methods
Modern Approach: Use TanStack Query or SWR for data fetching. They handle caching, loading states, and refetching automatically, reducing boilerplate by 70%.

8.2 GraphQL Apollo Client Relay

Client Library Features Complexity Use Case
Apollo Client Normalized cache, DevTools, subscriptions Medium Most popular, production-ready, great DX
Relay Compile-time optimization, Facebook-backed High Large-scale apps, maximum performance, strict patterns
urql Lightweight, extensible, customizable cache Low Smaller bundle, flexible caching, simpler setup
graphql-request Minimal wrapper over fetch Very Low Simple queries, no caching needed, lightweight

Example: GraphQL Integration

// Apollo Client Setup
import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache(),
  headers: {
    authorization: `Bearer ${token}`
  }
});

function App() {
  return (
    <ApolloProvider client={client}>
      <Users />
    </ApolloProvider>
  );
}

// Query with Apollo
import { useQuery, useMutation } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers($limit: Int!) {
    users(limit: $limit) {
      id
      name
      email
      posts {
        id
        title
      }
    }
  }
`;

function Users() {
  const { loading, error, data, refetch } = useQuery(GET_USERS, {
    variables: { limit: 10 },
    fetchPolicy: 'cache-first' // cache-first, network-only, cache-and-network
  });
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return (
    <div>
      {data.users.map(user => (
        <div key={user.id}>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
}

// Mutation with Apollo
const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading, error }] = useMutation(CREATE_USER, {
    refetchQueries: [{ query: GET_USERS, variables: { limit: 10 } }],
    // or update cache manually
    update(cache, { data: { createUser } }) {
      cache.modify({
        fields: {
          users(existingUsers = []) {
            const newUserRef = cache.writeFragment({
              data: createUser,
              fragment: gql`
                fragment NewUser on User {
                  id
                  name
                  email
                }
              `
            });
            return [...existingUsers, newUserRef];
          }
        }
      });
    }
  });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    createUser({ variables: { input: { name: 'John', email: 'john@example.com' } } });
  };
  
  return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}

// Apollo Subscriptions (WebSocket)
import { useSubscription } from '@apollo/client';

const MESSAGE_SUBSCRIPTION = gql`
  subscription OnMessageAdded {
    messageAdded {
      id
      content
      user {
        name
      }
    }
  }
`;

function Chat() {
  const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION);
  
  return loading ? <p>Loading...</p> : <Message message={data.messageAdded} />;
}

// urql - Lightweight alternative
import { createClient, Provider, useQuery } from 'urql';

const client = createClient({
  url: 'https://api.example.com/graphql'
});

function App() {
  return (
    <Provider value={client}>
      <Users />
    </Provider>
  );
}

function Users() {
  const [result] = useQuery({
    query: GET_USERS,
    variables: { limit: 10 }
  });
  
  const { data, fetching, error } = result;
  
  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return <div>{/* render users */}</div>;
}

// graphql-request - Minimal
import { request, gql } from 'graphql-request';

const endpoint = 'https://api.example.com/graphql';

const query = gql`
  query GetUsers {
    users {
      id
      name
    }
  }
`;

async function fetchUsers() {
  const data = await request(endpoint, query);
  console.log(data.users);
}

// Code Generation with GraphQL Code Generator
// codegen.yml
schema: https://api.example.com/graphql
documents: './src/**/*.graphql'
generates:
  ./src/generated/graphql.tsx:
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo

// Generated types and hooks
import { useGetUsersQuery } from './generated/graphql';

function Users() {
  const { data, loading } = useGetUsersQuery({ variables: { limit: 10 } });
  // Fully typed!
}

GraphQL vs REST

Feature REST GraphQL
Over-fetching ❌ Common ✅ None
Multiple Endpoints ✅ Many ❌ Single
Type Safety Manual ✅ Built-in
Caching HTTP cache Normalized

Apollo Cache Policies

  • cache-first: Use cache, fetch if missing
  • cache-only: Never fetch, cache only
  • network-only: Always fetch, update cache
  • no-cache: Fetch without cache
  • cache-and-network: Return cache, fetch update
When to Use GraphQL: Multiple related resources, mobile apps (reduce requests), avoid over-fetching. REST is simpler for CRUD APIs with fixed data shapes.

8.3 tRPC Type-safe API Calls

Feature Description Benefit
End-to-end Type Safety TypeScript types from backend to frontend Zero code generation, compile-time safety, autocomplete
No Schema Definition No GraphQL schema or OpenAPI spec needed Less boilerplate, faster development, DX excellence
React Query Integration Built on TanStack Query Automatic caching, loading states, optimistic updates
Lightweight Minimal bundle size, simple API Fast, no code generation step, instant feedback
Zod Validation Runtime validation with Zod schemas Type-safe input validation, automatic error handling

Example: tRPC Implementation

// Backend - tRPC Router (Next.js API route or Express)
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

const appRouter = t.router({
  // Query (GET)
  getUsers: t.procedure
    .input(z.object({ limit: z.number().optional() }))
    .query(async ({ input }) => {
      const users = await db.user.findMany({
        take: input.limit || 10
      });
      return users;
    }),
  
  // Query with ID
  getUserById: t.procedure
    .input(z.string())
    .query(async ({ input }) => {
      const user = await db.user.findUnique({ where: { id: input } });
      if (!user) throw new Error('User not found');
      return user;
    }),
  
  // Mutation (POST/PUT/DELETE)
  createUser: t.procedure
    .input(z.object({
      name: z.string().min(3),
      email: z.string().email(),
      age: z.number().min(18).optional()
    }))
    .mutation(async ({ input }) => {
      const user = await db.user.create({ data: input });
      return user;
    }),
  
  updateUser: t.procedure
    .input(z.object({
      id: z.string(),
      name: z.string().optional(),
      email: z.string().email().optional()
    }))
    .mutation(async ({ input }) => {
      const { id, ...data } = input;
      return db.user.update({ where: { id }, data });
    }),
  
  deleteUser: t.procedure
    .input(z.string())
    .mutation(async ({ input }) => {
      await db.user.delete({ where: { id: input } });
      return { success: true };
    })
});

export type AppRouter = typeof appRouter;

// Next.js API route - pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';

export default createNextApiHandler({
  router: appRouter,
  createContext: () => ({})
});

// Frontend - tRPC Client Setup
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/router';

export const trpc = createTRPCReact<AppRouter>();

// _app.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';

function App({ Component, pageProps }) {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: 'http://localhost:3000/api/trpc',
          headers() {
            return {
              authorization: getAuthToken()
            };
          }
        })
      ]
    })
  );
  
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <Component {...pageProps} />
      </QueryClientProvider>
    </trpc.Provider>
  );
}

// Using tRPC in Components
function UsersList() {
  // Query - fully typed!
  const { data: users, isLoading } = trpc.getUsers.useQuery({ limit: 20 });
  
  // Mutation
  const utils = trpc.useContext();
  const createMutation = trpc.createUser.useMutation({
    onSuccess: () => {
      utils.getUsers.invalidate(); // Refetch users
    }
  });
  
  const handleCreate = () => {
    createMutation.mutate({
      name: 'John Doe',
      email: 'john@example.com',
      age: 25
    });
  };
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      {users?.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
      <button onClick={handleCreate}>Create User</button>
    </div>
  );
}

// Single user query
function UserProfile({ userId }: { userId: string }) {
  const { data: user } = trpc.getUserById.useQuery(userId);
  
  const updateMutation = trpc.updateUser.useMutation();
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <button onClick={() => updateMutation.mutate({ id: userId, name: 'Updated' })}>
        Update
      </button>
    </div>
  );
}

// Optimistic Updates
const deleteMutation = trpc.deleteUser.useMutation({
  onMutate: async (deletedId) => {
    await utils.getUsers.cancel();
    
    const previousUsers = utils.getUsers.getData();
    
    utils.getUsers.setData(undefined, (old) =>
      old?.filter(user => user.id !== deletedId)
    );
    
    return { previousUsers };
  },
  onError: (err, deletedId, context) => {
    utils.getUsers.setData(undefined, context?.previousUsers);
  },
  onSettled: () => {
    utils.getUsers.invalidate();
  }
});

// Middleware (Authentication)
const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) {
    throw new Error('Unauthorized');
  }
  return next({ ctx: { user: ctx.user } });
});

const protectedProcedure = t.procedure.use(isAuthed);

const protectedRouter = t.router({
  getProfile: protectedProcedure.query(({ ctx }) => {
    return ctx.user; // user is typed!
  })
});

tRPC Benefits

  • ✅ Full type safety, no codegen
  • ✅ Autocomplete everywhere
  • ✅ Catch errors at compile time
  • ✅ Built on React Query
  • ✅ Minimal boilerplate
  • ✅ Excellent DX
  • ✅ Works with monorepos

When to Use tRPC

Scenario Use tRPC?
Full TypeScript stack ✅ Perfect
Monorepo ✅ Excellent
Public API ❌ Use REST/GraphQL
Mobile apps ⚠️ Use REST/GraphQL
tRPC is Perfect For: Full-stack TypeScript apps where you control both frontend and backend. Get 100% type safety with zero code generation.

8.4 Offline-First IndexedDB Strategies

Storage API Capacity API Type Use Case
IndexedDB ~50% of disk space Async, transactional Large datasets, complex queries, offline apps
localStorage 5-10 MB Synchronous, key-value Small data, user preferences, simple storage
Cache API Varies by browser Async, HTTP responses Service Worker caching, PWA offline support
Web SQL DEPRECATED ~50 MB SQL database Legacy only, use IndexedDB instead

Example: Offline-First with IndexedDB

// Dexie.js - IndexedDB wrapper
import Dexie from 'dexie';

const db = new Dexie('MyAppDatabase');

db.version(1).stores({
  users: '++id, name, email, updatedAt',
  posts: '++id, userId, title, content, createdAt'
});

// Add data
async function addUser(user) {
  const id = await db.users.add({
    ...user,
    updatedAt: Date.now()
  });
  return id;
}

// Query data
async function getUsers() {
  return db.users.toArray();
}

async function getUserById(id) {
  return db.users.get(id);
}

async function searchUsers(query) {
  return db.users
    .filter(user => user.name.toLowerCase().includes(query.toLowerCase()))
    .toArray();
}

// Update data
async function updateUser(id, changes) {
  return db.users.update(id, {
    ...changes,
    updatedAt: Date.now()
  });
}

// Delete data
async function deleteUser(id) {
  return db.users.delete(id);
}

// React Hook for IndexedDB
import { useLiveQuery } from 'dexie-react-hooks';

function UsersList() {
  const users = useLiveQuery(() => db.users.toArray(), []);
  
  if (!users) return <div>Loading...</div>;
  
  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

// Offline-First Sync Strategy
class OfflineFirstSync {
  constructor(apiUrl) {
    this.apiUrl = apiUrl;
    this.syncQueue = [];
  }
  
  // Fetch with offline fallback
  async fetchWithCache(endpoint) {
    try {
      // Try network first
      const response = await fetch(`${this.apiUrl}${endpoint}`);
      const data = await response.json();
      
      // Store in IndexedDB
      await db.users.bulkPut(data);
      
      return data;
    } catch (error) {
      // Network failed, use cached data
      console.log('Using cached data');
      return db.users.toArray();
    }
  }
  
  // Queue operations when offline
  async queueOperation(operation) {
    this.syncQueue.push({
      id: Date.now(),
      operation,
      timestamp: Date.now()
    });
    
    // Save queue to IndexedDB
    await db.syncQueue.add(this.syncQueue[this.syncQueue.length - 1]);
    
    // Try to sync immediately
    this.syncQueuedOperations();
  }
  
  // Sync queued operations when online
  async syncQueuedOperations() {
    if (!navigator.onLine) return;
    
    const pending = await db.syncQueue.toArray();
    
    for (const item of pending) {
      try {
        await this.executeOperation(item.operation);
        await db.syncQueue.delete(item.id);
      } catch (error) {
        console.error('Sync failed:', error);
        break; // Stop on first error
      }
    }
  }
  
  async executeOperation(operation) {
    const { method, endpoint, data } = operation;
    return fetch(`${this.apiUrl}${endpoint}`, {
      method,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
  }
}

// Online/Offline Detection
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}

function App() {
  const isOnline = useOnlineStatus();
  
  return (
    <div>
      {!isOnline && <div className="offline-banner">You are offline</div>}
      {/* app content */}
    </div>
  );
}

// PouchDB - CouchDB sync
import PouchDB from 'pouchdb';

const localDB = new PouchDB('myapp');
const remoteDB = new PouchDB('https://mycouch.example.com/myapp');

// Sync databases
localDB.sync(remoteDB, {
  live: true,
  retry: true
}).on('change', (info) => {
  console.log('Sync change:', info);
}).on('error', (err) => {
  console.error('Sync error:', err);
});

// Add document
await localDB.put({
  _id: 'user-1',
  name: 'John Doe',
  email: 'john@example.com'
});

// Query document
const user = await localDB.get('user-1');

// Find documents
const result = await localDB.find({
  selector: { name: { $regex: /John/ } }
});

Storage Comparison

API Size Performance
IndexedDB ~GB Fast, async
localStorage 5-10 MB Slow, blocking
sessionStorage 5-10 MB Slow, blocking
Cache API Varies Fast, async

IndexedDB Libraries

  • Dexie.js: Simple API, live queries, best DX
  • PouchDB: CouchDB sync, replication
  • idb: Promise wrapper, minimal
  • localForage: localStorage API, IndexedDB backend
  • RxDB: Reactive database, observables
Offline-First Strategy: Local first, sync later. Store all data locally, sync with server when online. Provides instant UI updates and works offline.

8.5 API Mocking MSW Mock Service Worker

Tool Approach Use Case Advantage
MSW (Mock Service Worker) Intercept network at service worker level Development, testing, demos without backend Works in browser and Node.js, realistic network behavior
JSON Server Full REST API from JSON file Rapid prototyping, quick backend mock Zero config, CRUD operations, relationships
MirageJS In-memory API mock server Frontend development, complex scenarios Database simulation, relationships, factories
Nock HTTP mocking for Node.js Backend tests, integration tests Record/replay, precise request matching

Example: API Mocking with MSW

// MSW Setup - src/mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
  // GET request
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]);
  }),
  
  // GET with params
  http.get('/api/users/:id', ({ params }) => {
    const { id } = params;
    return HttpResponse.json({
      id,
      name: 'John Doe',
      email: 'john@example.com'
    });
  }),
  
  // POST request
  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json(
      { id: 3, ...body },
      { status: 201 }
    );
  }),
  
  // Error response
  http.get('/api/error', () => {
    return HttpResponse.json(
      { message: 'Internal server error' },
      { status: 500 }
    );
  }),
  
  // Delayed response
  http.get('/api/slow', async () => {
    await new Promise(resolve => setTimeout(resolve, 2000));
    return HttpResponse.json({ data: 'Slow response' });
  }),
  
  // GraphQL mock
  http.post('/graphql', async ({ request }) => {
    const { query } = await request.json();
    
    if (query.includes('GetUsers')) {
      return HttpResponse.json({
        data: {
          users: [
            { id: '1', name: 'John' },
            { id: '2', name: 'Jane' }
          ]
        }
      });
    }
  })
];

// Browser setup - src/mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

// Start in development
// src/index.tsx
import { worker } from './mocks/browser';

if (process.env.NODE_ENV === 'development') {
  worker.start();
}

// Node.js setup (for tests) - src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

// Test setup - setupTests.ts
import { server } from './mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// Override handler in test
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';

test('handles server error', async () => {
  server.use(
    http.get('/api/users', () => {
      return HttpResponse.json(
        { message: 'Server error' },
        { status: 500 }
      );
    })
  );
  
  // Test error handling
});

// JSON Server - db.json
{
  "users": [
    { "id": 1, "name": "John Doe", "email": "john@example.com" },
    { "id": 2, "name": "Jane Smith", "email": "jane@example.com" }
  ],
  "posts": [
    { "id": 1, "userId": 1, "title": "First Post", "content": "..." }
  ]
}

// Start JSON Server
npx json-server --watch db.json --port 3001

// API endpoints auto-generated:
// GET    /users
// GET    /users/1
// POST   /users
// PUT    /users/1
// PATCH  /users/1
// DELETE /users/1
// GET    /posts?userId=1

// MirageJS Setup
import { createServer, Model } from 'miragejs';

createServer({
  models: {
    user: Model
  },
  
  seeds(server) {
    server.create('user', { name: 'John', email: 'john@example.com' });
    server.create('user', { name: 'Jane', email: 'jane@example.com' });
  },
  
  routes() {
    this.namespace = 'api';
    
    this.get('/users', (schema) => {
      return schema.users.all();
    });
    
    this.get('/users/:id', (schema, request) => {
      const id = request.params.id;
      return schema.users.find(id);
    });
    
    this.post('/users', (schema, request) => {
      const attrs = JSON.parse(request.requestBody);
      return schema.users.create(attrs);
    });
  }
});

MSW Benefits

  • ✅ Works in browser and Node.js
  • ✅ No server needed
  • ✅ Network-level interception
  • ✅ Same code for dev and test
  • ✅ Realistic request/response
  • ✅ TypeScript support

When to Mock APIs

  • Backend not ready yet
  • Testing error scenarios
  • Offline development
  • Demos and prototypes
  • Integration tests
  • Slow API responses
MSW Recommendation: Use MSW for both development and testing. Single source of truth for mocks, works seamlessly across environments.

8.6 Rate Limiting Retry Logic Implementation

Strategy Implementation Use Case Backoff Formula
Exponential Backoff Increase delay exponentially between retries API rate limits, transient errors delay = baseDelay * 2^attempt
Linear Backoff Fixed delay increase between retries Simple retry scenarios delay = baseDelay * attempt
Exponential + Jitter Add randomness to exponential backoff Prevent thundering herd, distributed systems delay = random(0, 2^attempt * base)
Circuit Breaker Stop requests after threshold failures Failing services, prevent cascade failures Open → Half-Open → Closed states

Example: Retry Logic and Rate Limiting

// Exponential Backoff with Retry
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      // Retry on 5xx errors or rate limit
      if (response.status >= 500 || response.status === 429) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return response;
    } catch (error) {
      lastError = error;
      
      if (attempt < maxRetries) {
        const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Max 10s
        console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

// Usage
try {
  const response = await fetchWithRetry('/api/users');
  const data = await response.json();
} catch (error) {
  console.error('All retries failed:', error);
}

// Exponential Backoff with Jitter
function exponentialBackoffWithJitter(attempt, baseDelay = 1000, maxDelay = 30000) {
  const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
  const jitter = Math.random() * exponentialDelay;
  return Math.floor(jitter);
}

// Axios Retry Plugin
import axios from 'axios';
import axiosRetry from 'axios-retry';

const client = axios.create();

axiosRetry(client, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
           error.response?.status === 429;
  }
});

// Custom retry with condition
axiosRetry(client, {
  retries: 5,
  retryDelay: (retryCount) => {
    return retryCount * 1000; // Linear backoff
  },
  retryCondition: (error) => {
    return error.response?.status >= 500;
  },
  onRetry: (retryCount, error, requestConfig) => {
    console.log(`Retry attempt ${retryCount} for ${requestConfig.url}`);
  }
});

// React Query Retry Configuration
import { useQuery } from '@tanstack/react-query';

function Users() {
  const { data } = useQuery({
    queryKey: ['users'],
    queryFn: () => api.get('/users'),
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
  });
}

// Circuit Breaker Pattern
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureThreshold = threshold;
    this.timeout = timeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }
  
  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
  
  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      console.log('Circuit breaker opened');
    }
  }
}

// Usage
const breaker = new CircuitBreaker(5, 60000);

async function fetchData() {
  return breaker.execute(async () => {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('Request failed');
    return response.json();
  });
}

// Rate Limiting (Frontend)
class RateLimiter {
  constructor(maxRequests, timeWindow) {
    this.maxRequests = maxRequests;
    this.timeWindow = timeWindow;
    this.requests = [];
  }
  
  async throttle(fn) {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < this.timeWindow);
    
    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.timeWindow - (now - oldestRequest);
      await new Promise(resolve => setTimeout(resolve, waitTime));
      return this.throttle(fn);
    }
    
    this.requests.push(now);
    return fn();
  }
}

// Usage: Max 10 requests per minute
const limiter = new RateLimiter(10, 60000);

async function fetchWithRateLimit(url) {
  return limiter.throttle(() => fetch(url));
}

// Handle 429 Rate Limit Response
async function fetchWithRateLimitHandling(url) {
  const response = await fetch(url);
  
  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After');
    const delay = retryAfter ? parseInt(retryAfter) * 1000 : 60000;
    
    console.log(`Rate limited. Retrying after ${delay}ms`);
    await new Promise(resolve => setTimeout(resolve, delay));
    
    return fetchWithRateLimitHandling(url);
  }
  
  return response;
}

Retry Best Practices

Backoff Comparison

Attempt Linear (1s) Exponential (1s)
1 1s 1s
2 2s 2s
3 3s 4s
4 4s 8s
5 5s 16s

API Integration Summary

  • Modern Fetching: Use TanStack Query or SWR for automatic caching and revalidation
  • GraphQL: Apollo Client for complex data requirements, avoid over-fetching
  • Type Safety: tRPC for full-stack TypeScript, zero code generation
  • Offline: IndexedDB with Dexie.js, sync queue when online
  • Mocking: MSW for realistic API mocks in dev and test
  • Resilience: Exponential backoff with jitter, circuit breaker pattern
Don't Retry Everything: Only retry idempotent operations (GET, PUT, DELETE). Never auto-retry POST requests as they may create duplicates.

9. Internationalization Modern Implementation

9.1 React-intl i18next Localization

Library Features Complexity Use Case
react-intl (Format.js) ICU message format, React components, hooks Medium React apps, message formatting, pluralization, date/number formatting
i18next + react-i18next Plugin ecosystem, namespace support, lazy loading Low-Medium Most popular, flexible, works with any framework
next-intl Next.js optimized, server components support Low Next.js apps, App Router, Server Components
LinguiJS Compile-time optimization, extraction tool Medium Performance-critical apps, small bundle size
Polyglot.js Minimalist, lightweight Low Simple projects, small bundle, basic interpolation

Example: Internationalization with react-intl and i18next

// react-intl Setup
import { IntlProvider, FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';

const messages = {
  en: {
    'app.greeting': 'Hello, {name}!',
    'app.items': '{count, plural, =0 {No items} one {# item} other {# items}}',
    'app.welcome': 'Welcome to our app'
  },
  es: {
    'app.greeting': '¡Hola, {name}!',
    'app.items': '{count, plural, =0 {Sin artículos} one {# artículo} other {# artículos}}',
    'app.welcome': 'Bienvenido a nuestra aplicación'
  }
};

function App() {
  const [locale, setLocale] = useState('en');
  
  return (
    <IntlProvider locale={locale} messages={messages[locale]}>
      <HomePage />
    </IntlProvider>
  );
}

// Using FormattedMessage
function HomePage() {
  return (
    <div>
      <FormattedMessage 
        id="app.greeting" 
        values={{ name: 'John' }} 
      />
      
      <FormattedMessage 
        id="app.items" 
        values={{ count: 5 }} 
      />
      
      <FormattedNumber value={1234.56} style="currency" currency="USD" />
      
      <FormattedDate value={new Date()} year="numeric" month="long" day="2-digit" />
    </div>
  );
}

// Using useIntl hook
import { useIntl } from 'react-intl';

function MyComponent() {
  const intl = useIntl();
  
  const greeting = intl.formatMessage(
    { id: 'app.greeting' },
    { name: 'Jane' }
  );
  
  const placeholder = intl.formatMessage({ id: 'input.placeholder' });
  
  return (
    <div>
      <h1>{greeting}</h1>
      <input placeholder={placeholder} />
    </div>
  );
}

// i18next Setup
import i18n from 'i18next';
import { initReactI18next, useTranslation } from 'react-i18next';

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          welcome: 'Welcome',
          greeting: 'Hello, {{name}}!',
          items: '{{count}} items',
          items_plural: '{{count}} items'
        }
      },
      es: {
        translation: {
          welcome: 'Bienvenido',
          greeting: '¡Hola, {{name}}!',
          items: '{{count}} artículo',
          items_plural: '{{count}} artículos'
        }
      }
    },
    lng: 'en',
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false
    }
  });

// Using useTranslation hook
function MyComponent() {
  const { t, i18n } = useTranslation();
  
  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);
  };
  
  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('greeting', { name: 'John' })}</p>
      <p>{t('items', { count: 5 })}</p>
      
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('es')}>Español</button>
    </div>
  );
}

// i18next with namespaces
i18n.init({
  resources: {
    en: {
      common: { save: 'Save', cancel: 'Cancel' },
      dashboard: { title: 'Dashboard', stats: 'Statistics' }
    }
  },
  ns: ['common', 'dashboard'],
  defaultNS: 'common'
});

function Dashboard() {
  const { t } = useTranslation(['dashboard', 'common']);
  
  return (
    <div>
      <h1>{t('dashboard:title')}</h1>
      <button>{t('common:save')}</button>
    </div>
  );
}

// next-intl (Next.js)
// messages/en.json
{
  "Index": {
    "title": "Hello world!",
    "description": "This is a description"
  }
}

// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';

export default async function LocaleLayout({ children, params: { locale } }) {
  const messages = await getMessages();
  
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';

export default function IndexPage() {
  const t = useTranslations('Index');
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

Library Comparison

Library Bundle Size Stars
i18next ~10KB ⭐ Most Popular
react-intl ~14KB ⭐ Facebook/Format.js
next-intl ~6KB ⭐ Next.js optimized
LinguiJS ~3KB ⭐ Smallest

Message File Formats

  • JSON: Most common, easy to parse
  • YAML: More readable, comments
  • PO (Gettext): Translation industry standard
  • XLIFF: XML-based, professional tools
  • ICU Format: Advanced interpolation
Best Choice: Use i18next for most projects (flexibility + ecosystem). Use next-intl for Next.js App Router with Server Components.

9.2 Dynamic Locale Loading Lazy i18n

Strategy Implementation Benefit Use Case
Code Splitting per Locale Dynamic import for translation files Reduce initial bundle, load only active locale Large apps with many languages
Namespace-based Lazy Loading Load translation namespaces on-demand Progressive loading, better performance Apps with distinct feature sections
CDN-hosted Translations Fetch translations from CDN Update translations without deployment Frequent content updates, multi-tenant apps
Backend Translation API Fetch from translation service (Lokalise, Crowdin) Real-time updates, centralized management Large teams, continuous localization

Example: Dynamic Locale Loading

// i18next with lazy loading
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';

i18n
  .use(Backend)
  .use(initReactI18next)
  .init({
    lng: 'en',
    fallbackLng: 'en',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    },
    ns: ['common', 'dashboard'],
    defaultNS: 'common'
  });

// Translations loaded dynamically
// /locales/en/common.json
// /locales/en/dashboard.json
// /locales/es/common.json

// React component with dynamic import
import { Suspense } from 'react';

function App() {
  const [locale, setLocale] = useState('en');
  
  const changeLanguage = async (lng) => {
    await i18n.changeLanguage(lng);
    setLocale(lng);
  };
  
  return (
    <Suspense fallback={<div>Loading translations...</div>}>
      <MyComponent />
    </Suspense>
  );
}

// Next.js dynamic locale import
// app/[locale]/layout.tsx
export async function generateStaticParams() {
  return [{ locale: 'en' }, { locale: 'es' }, { locale: 'fr' }];
}

export default async function LocaleLayout({ children, params: { locale } }) {
  // Dynamic import of messages
  const messages = (await import(`../../messages/${locale}.json`)).default;
  
  return (
    <NextIntlClientProvider locale={locale} messages={messages}>
      {children}
    </NextIntlClientProvider>
  );
}

// Custom hook for dynamic locale loading
function useDynamicLocale(locale) {
  const [messages, setMessages] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    setLoading(true);
    import(`../locales/${locale}.json`)
      .then((module) => {
        setMessages(module.default);
        setLoading(false);
      })
      .catch((error) => {
        console.error('Failed to load locale:', error);
        setLoading(false);
      });
  }, [locale]);
  
  return { messages, loading };
}

// Usage
function App() {
  const [locale, setLocale] = useState('en');
  const { messages, loading } = useDynamicLocale(locale);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <IntlProvider locale={locale} messages={messages}>
      <HomePage />
    </IntlProvider>
  );
}

// Webpack chunk names for locale files
const loadLocale = (locale) => {
  return import(
    /* webpackChunkName: "locale-[request]" */
    `../locales/${locale}.json`
  );
};

// Vite dynamic import
const loadLocaleVite = async (locale) => {
  const modules = import.meta.glob('../locales/*.json');
  const module = await modules[`../locales/${locale}.json`]();
  return module.default;
};

// CDN-hosted translations
async function loadTranslationsFromCDN(locale) {
  const response = await fetch(`https://cdn.example.com/i18n/${locale}.json`);
  return response.json();
}

i18n.use(Backend).init({
  backend: {
    loadPath: 'https://cdn.example.com/i18n/{{lng}}/{{ns}}.json',
    crossDomain: true
  }
});

// Lokalise API integration
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';

i18n
  .use(Backend)
  .use(initReactI18next)
  .init({
    backend: {
      loadPath: 'https://api.lokalise.com/api2/projects/PROJECT_ID/files/download',
      customHeaders: {
        'X-Api-Token': 'YOUR_API_TOKEN'
      }
    }
  });

// Preload next likely locale
function preloadLocale(locale) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = `/locales/${locale}.json`;
  document.head.appendChild(link);
}

// Preload on hover
<button 
  onMouseEnter={() => preloadLocale('es')}
  onClick={() => changeLanguage('es')}
>
  Español
</button>

Lazy Loading Benefits

  • ✅ Smaller initial bundle (50-70% reduction)
  • ✅ Faster page load times
  • ✅ Load only needed languages
  • ✅ Update translations without rebuild
  • ✅ Better cache utilization

Loading Strategies

Strategy Load Time
Bundle all locales Initial
Dynamic import On language change
Prefetch On hover/idle
CDN fetch Runtime
Recommendation: Use dynamic imports for locale files. Reduces initial bundle by 60-80% for apps with 5+ languages.

9.3 RTL LTR Layout CSS Logical Properties

Physical Property Logical Property RTL Behavior Browser Support
margin-left margin-inline-start Becomes right margin in RTL 95%+ modern browsers
margin-right margin-inline-end Becomes left margin in RTL 95%+
padding-left padding-inline-start Automatic RTL flip 95%+
border-left border-inline-start Automatic RTL flip 95%+
text-align: left text-align: start Right-aligned in RTL Universal
float: left float: inline-start Float right in RTL Modern browsers

Example: RTL Support with Logical Properties

// Set document direction
<html lang="ar" dir="rtl">
</html>

// React component with direction
function App() {
  const [locale, setLocale] = useState('en');
  const direction = locale === 'ar' || locale === 'he' ? 'rtl' : 'ltr';
  
  return (
    <div dir={direction}>
      <Content />
    </div>
  );
}

// CSS Logical Properties
.card {
  /* ❌ Physical properties - need manual RTL handling */
  margin-left: 20px;
  padding-right: 10px;
  border-left: 2px solid blue;
  text-align: left;
}

.card {
  /* ✅ Logical properties - automatic RTL support */
  margin-inline-start: 20px;
  padding-inline-end: 10px;
  border-inline-start: 2px solid blue;
  text-align: start;
}

// Comprehensive logical properties
.container {
  /* Inline (horizontal in LTR) */
  margin-inline: 20px;           /* margin-left + margin-right */
  margin-inline-start: 20px;     /* margin-left in LTR, margin-right in RTL */
  margin-inline-end: 20px;       /* margin-right in LTR, margin-left in RTL */
  padding-inline-start: 10px;
  padding-inline-end: 10px;
  
  /* Block (vertical) */
  margin-block: 20px;            /* margin-top + margin-bottom */
  margin-block-start: 20px;      /* margin-top */
  margin-block-end: 20px;        /* margin-bottom */
  
  /* Borders */
  border-inline-start: 1px solid #ccc;
  border-inline-end: 1px solid #ccc;
  border-block-start: 1px solid #ccc;
  border-block-end: 1px solid #ccc;
  
  /* Border radius */
  border-start-start-radius: 8px; /* top-left in LTR, top-right in RTL */
  border-start-end-radius: 8px;   /* top-right in LTR, top-left in RTL */
  border-end-start-radius: 8px;   /* bottom-left in LTR, bottom-right in RTL */
  border-end-end-radius: 8px;     /* bottom-right in LTR, bottom-left in RTL */
  
  /* Inset (positioning) */
  inset-inline-start: 0;         /* left: 0 in LTR, right: 0 in RTL */
  inset-inline-end: 0;           /* right: 0 in LTR, left: 0 in RTL */
}

// Flexbox with logical properties
.flex-container {
  display: flex;
  flex-direction: row; /* Automatically reverses in RTL */
  justify-content: flex-start; /* Start of inline axis */
  gap: 1rem;
}

// RTL-specific styles (when needed)
[dir="rtl"] .special-case {
  /* Override for specific RTL behavior */
  transform: scaleX(-1); /* Flip horizontally */
}

// Icons that shouldn't flip
.icon-no-flip {
  /* Prevent automatic flipping for icons like arrows */
  transform: scaleX(var(--icon-flip, 1));
}

[dir="rtl"] .icon-no-flip {
  --icon-flip: -1;
}

// Styled-components with RTL
import styled from 'styled-components';

const Card = styled.div`
  margin-inline-start: ${props => props.theme.spacing.md};
  padding-inline: ${props => props.theme.spacing.sm};
  text-align: start;
`;

// Tailwind CSS RTL plugin
// tailwind.config.js
module.exports = {
  plugins: [
    require('tailwindcss-rtl')
  ]
};

// Usage
<div className="ms-4 pe-2">
  {/* ms-4: margin-inline-start: 1rem */}
  {/* pe-2: padding-inline-end: 0.5rem */}
</div>

// PostCSS RTL
// postcss.config.js
module.exports = {
  plugins: [
    require('postcss-rtlcss')
  ]
};

// Converts:
.element { margin-left: 10px; }
// To:
[dir="ltr"] .element { margin-left: 10px; }
[dir="rtl"] .element { margin-right: 10px; }

// Material-UI RTL
import { createTheme, ThemeProvider } from '@mui/material/styles';
import rtlPlugin from 'stylis-plugin-rtl';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cacheRtl = createCache({
  key: 'muirtl',
  stylisPlugins: [rtlPlugin]
});

const theme = createTheme({
  direction: 'rtl'
});

function RTLApp() {
  return (
    <CacheProvider value={cacheRtl}>
      <ThemeProvider theme={theme}>
        <App />
      </ThemeProvider>
    </CacheProvider>
  );
}

Logical Properties Map

Physical Logical
margin-left margin-inline-start
margin-right margin-inline-end
margin-top margin-block-start
margin-bottom margin-block-end
left inset-inline-start
right inset-inline-end

RTL Languages

  • Arabic (ar): العربية
  • Hebrew (he): עברית
  • Persian (fa): فارسی
  • Urdu (ur): اردو
  • Yiddish (yi): ייִדיש
Best Practice: Always use CSS logical properties in new projects. Automatic RTL support with zero JavaScript, better semantic meaning.

9.4 Date-fns Moment.js Timezone Handling

Library Bundle Size Features Status
date-fns ~13KB (tree-shakeable) Modular, immutable, TypeScript, i18n support RECOMMENDED
Moment.js ~70KB (monolithic) Comprehensive, mature, large ecosystem LEGACY
Day.js ~2KB Moment-compatible API, lightweight MODERN
Luxon ~20KB Immutable, timezone-aware, Moment successor MODERN
Intl.DateTimeFormat 0KB (native) Built-in browser API, locale-aware NATIVE

Example: Date/Time Localization

// date-fns with locales
import { format, formatDistance, formatRelative } from 'date-fns';
import { es, fr, ar } from 'date-fns/locale';

const date = new Date();

// Format with locale
format(date, 'PPPPpppp', { locale: es });
// "miércoles, 18 de diciembre de 2025 a las 14:30:00"

formatDistance(date, new Date(2025, 11, 1), { locale: fr });
// "17 jours"

formatRelative(date, new Date(), { locale: ar });
// "اليوم في 2:30 م"

// Custom format
format(date, "EEEE, MMMM do yyyy 'at' h:mm a", { locale: es });

// React component with date-fns
import { useIntl } from 'react-intl';
import { format } from 'date-fns';
import { enUS, es, fr, ar } from 'date-fns/locale';

const locales = { en: enUS, es, fr, ar };

function DateDisplay({ date }) {
  const intl = useIntl();
  const locale = locales[intl.locale];
  
  return (
    <time dateTime={date.toISOString()}>
      {format(date, 'PPP', { locale })}
    </time>
  );
}

// Intl.DateTimeFormat (Native)
const date = new Date();

// Format with locale
new Intl.DateTimeFormat('en-US').format(date);
// "12/18/2025"

new Intl.DateTimeFormat('es-ES').format(date);
// "18/12/2025"

new Intl.DateTimeFormat('ar-EG').format(date);
// "١٨‏/١٢‏/٢٠٢٥"

// Long format
new Intl.DateTimeFormat('en-US', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
}).format(date);
// "Wednesday, December 18, 2025"

// Time formatting
new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: true
}).format(date);
// "2:30 PM"

new Intl.DateTimeFormat('es-ES', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: false
}).format(date);
// "14:30"

// Relative time formatting (Intl.RelativeTimeFormat)
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

rtf.format(-1, 'day');    // "yesterday"
rtf.format(0, 'day');     // "today"
rtf.format(1, 'day');     // "tomorrow"
rtf.format(-3, 'month');  // "3 months ago"

const rtfEs = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
rtfEs.format(-1, 'day');  // "ayer"
rtfEs.format(1, 'day');   // "mañana"

// Timezone handling with date-fns-tz
import { formatInTimeZone, utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';

const date = new Date('2025-12-18T14:30:00Z');

// Format in specific timezone
formatInTimeZone(date, 'America/New_York', 'yyyy-MM-dd HH:mm:ss zzz');
// "2025-12-18 09:30:00 EST"

formatInTimeZone(date, 'Europe/London', 'yyyy-MM-dd HH:mm:ss zzz');
// "2025-12-18 14:30:00 GMT"

formatInTimeZone(date, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss zzz');
// "2025-12-18 23:30:00 JST"

// Convert to specific timezone
const nyTime = utcToZonedTime(date, 'America/New_York');

// Convert from timezone to UTC
const utcTime = zonedTimeToUtc(nyTime, 'America/New_York');

// Day.js with timezone
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import 'dayjs/locale/es';
import 'dayjs/locale/fr';

dayjs.extend(utc);
dayjs.extend(timezone);

// Set locale
dayjs.locale('es');

// Format with timezone
dayjs().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss z');
// "2025-12-18 09:30:00 EST"

// Luxon with timezone
import { DateTime } from 'luxon';

const dt = DateTime.now().setZone('America/New_York');
dt.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ');
// "2025-12-18 09:30:00 Eastern Standard Time"

// With locale
dt.setLocale('es').toFormat('DDDD');
// "miércoles, 18 de diciembre de 2025"

// React hook for formatted dates
function useFormattedDate(date, formatStr = 'PPP') {
  const intl = useIntl();
  const locale = locales[intl.locale];
  
  return useMemo(
    () => format(date, formatStr, { locale }),
    [date, formatStr, locale]
  );
}

// Usage
function EventCard({ event }) {
  const formattedDate = useFormattedDate(event.date);
  return <div>{formattedDate}</div>;
}

Library Comparison

Library Size Tree-shake
date-fns 13KB ✅ Yes
Day.js 2KB ⚠️ Plugins
Luxon 20KB ❌ No
Moment.js 70KB ❌ No
Intl API 0KB N/A

Common Date Formats

  • US: MM/DD/YYYY (12/18/2025)
  • Europe: DD/MM/YYYY (18/12/2025)
  • ISO: YYYY-MM-DD (2025-12-18)
  • Japan: YYYY年MM月DD日
  • Arabic: DD/MM/YYYY (٢٠٢٥/١٢/١٨)
Recommendation: Use date-fns for most projects (tree-shakeable, small). Use Intl API for simple formatting to avoid dependencies.

9.5 Pluralization ICU Message Format

Format Syntax Use Case Example
ICU Plural {count, plural, ...} Handle singular/plural forms "0 items", "1 item", "5 items"
ICU Select {gender, select, ...} Conditional text based on value "He/She/They went to the store"
ICU SelectOrdinal {count, selectordinal, ...} Ordinal numbers (1st, 2nd, 3rd) "1st place", "2nd place", "3rd place"
Nested Format Combination of plural + select Complex grammatical rules Gender + plural combinations

Example: ICU Message Format and Pluralization

// react-intl ICU Plural
const messages = {
  en: {
    'items.count': '{count, plural, =0 {No items} one {# item} other {# items}}'
  },
  es: {
    'items.count': '{count, plural, =0 {Sin artículos} one {# artículo} other {# artículos}}'
  },
  ar: {
    // Arabic has 6 plural forms!
    'items.count': '{count, plural, =0 {لا توجد عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}}'
  }
};

// Usage
<FormattedMessage id="items.count" values={{ count: 0 }} />  // "No items"
<FormattedMessage id="items.count" values={{ count: 1 }} />  // "1 item"
<FormattedMessage id="items.count" values={{ count: 5 }} />  // "5 items"

// ICU Select (gender)
const messages = {
  'notification': '{gender, select, male {He} female {She} other {They}} sent you a message'
};

<FormattedMessage id="notification" values={{ gender: 'female' }} />
// "She sent you a message"

// ICU SelectOrdinal
const messages = {
  'place': 'You finished in {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place'
};

<FormattedMessage id="place" values={{ place: 1 }} />  // "You finished in 1st place"
<FormattedMessage id="place" values={{ place: 2 }} />  // "You finished in 2nd place"
<FormattedMessage id="place" values={{ place: 23 }} /> // "You finished in 23rd place"

// Complex nested pluralization
const messages = {
  'complex': '{gender, select, male {He has} female {She has} other {They have}} {count, plural, one {# item} other {# items}}'
};

<FormattedMessage 
  id="complex" 
  values={{ gender: 'female', count: 5 }} 
/>
// "She has 5 items"

// i18next pluralization
const resources = {
  en: {
    translation: {
      'item': 'item',
      'item_plural': 'items',
      'key': '{{count}} item',
      'key_plural': '{{count}} items'
    }
  },
  es: {
    translation: {
      'item': 'artículo',
      'item_plural': 'artículos',
      'key': '{{count}} artículo',
      'key_plural': '{{count}} artículos'
    }
  }
};

// Usage
t('key', { count: 1 });  // "1 item"
t('key', { count: 5 });  // "5 items"

// Custom plural rules (i18next)
i18n.init({
  pluralSeparator: '_',
  nsSeparator: ':',
  keySeparator: '.',
  pluralRules: {
    ar: (count) => {
      if (count === 0) return 'zero';
      if (count === 1) return 'one';
      if (count === 2) return 'two';
      if (count % 100 >= 3 && count % 100 <= 10) return 'few';
      if (count % 100 >= 11 && count % 100 <= 99) return 'many';
      return 'other';
    }
  }
});

// Intl.PluralRules (Native)
const pr = new Intl.PluralRules('en-US');

pr.select(0);  // "other"
pr.select(1);  // "one"
pr.select(2);  // "other"
pr.select(5);  // "other"

const prAr = new Intl.PluralRules('ar-EG');
prAr.select(0);   // "zero"
prAr.select(1);   // "one"
prAr.select(2);   // "two"
prAr.select(3);   // "few"
prAr.select(11);  // "many"
prAr.select(100); // "other"

// Helper function for pluralization
function pluralize(count, singular, plural) {
  const pr = new Intl.PluralRules('en-US');
  const rule = pr.select(count);
  return rule === 'one' ? singular : plural;
}

pluralize(1, 'item', 'items');  // "item"
pluralize(5, 'item', 'items');  // "items"

// Advanced: Custom pluralization with object
const pluralForms = {
  en: {
    item: {
      zero: 'no items',
      one: '1 item',
      other: '{{count}} items'
    }
  },
  ru: {
    item: {
      one: '{{count}} предмет',      // 1, 21, 31, ...
      few: '{{count}} предмета',     // 2-4, 22-24, ...
      many: '{{count}} предметов',   // 0, 5-20, 25-30, ...
      other: '{{count}} предмета'
    }
  }
};

function getPluralForm(locale, key, count) {
  const pr = new Intl.PluralRules(locale);
  const rule = pr.select(count);
  const template = pluralForms[locale][key][rule];
  return template.replace('{{count}}', count);
}

// Number formatting with plurals
const messages = {
  'users.online': '{count, plural, one {# user is} other {# users are}} online'
};

// Format numbers
<FormattedMessage 
  id="users.online" 
  values={{ count: 1234 }} 
/>
// "1,234 users are online"

// With number formatting
<FormattedMessage 
  id="users.online" 
  values={{ 
    count: (
      <FormattedNumber value={1234} />
    )
  }} 
/>

Plural Categories

Language Plural Forms
English 2 (one, other)
French 2 (one, other)
Russian 3 (one, few, many)
Arabic 6 (zero, one, two, few, many, other)
Polish 3 (one, few, many)

ICU Format Types

  • plural: Count-based (0, 1, many)
  • select: Enum values (male, female)
  • selectordinal: Ordinals (1st, 2nd)
  • number: Number formatting
  • date: Date formatting
  • time: Time formatting
Complex Pluralization: Some languages like Arabic have 6 plural forms, Polish 3 forms. Always use proper i18n libraries, never hardcode plural logic!

9.6 Locale Detection Browser Language

Detection Method Source Priority Reliability
URL Parameter ?lang=es 1 - Highest Explicit user choice, shareable links
Subdomain es.example.com 2 SEO-friendly, clear intent
Path Prefix /es/page 3 SEO-friendly, clear structure
Cookie/localStorage Stored user preference 4 Remembers choice across sessions
Accept-Language Header Browser's language setting 5 Automatic, but may not match content preference
navigator.language Browser API 6 - Lowest Fallback, system language

Example: Locale Detection Strategies

// Comprehensive locale detection
function detectLocale() {
  // 1. Check URL parameter
  const urlParams = new URLSearchParams(window.location.search);
  const urlLang = urlParams.get('lang');
  if (urlLang) return urlLang;
  
  // 2. Check path prefix
  const pathMatch = window.location.pathname.match(/^\/([a-z]{2})\//);
  if (pathMatch) return pathMatch[1];
  
  // 3. Check cookie
  const cookieLang = document.cookie
    .split('; ')
    .find(row => row.startsWith('locale='))
    ?.split('=')[1];
  if (cookieLang) return cookieLang;
  
  // 4. Check localStorage
  const storedLang = localStorage.getItem('locale');
  if (storedLang) return storedLang;
  
  // 5. Check browser language
  const browserLang = navigator.language || navigator.userLanguage;
  const shortLang = browserLang.split('-')[0]; // "en-US" → "en"
  
  // 6. Fallback
  return 'en';
}

// React hook for locale detection
function useLocaleDetection(supportedLocales = ['en', 'es', 'fr']) {
  const [locale, setLocale] = useState(() => {
    const detected = detectLocale();
    return supportedLocales.includes(detected) ? detected : 'en';
  });
  
  useEffect(() => {
    // Save to localStorage
    localStorage.setItem('locale', locale);
    
    // Update document language
    document.documentElement.lang = locale;
  }, [locale]);
  
  return [locale, setLocale];
}

// Next.js locale detection
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Check if pathname has locale
  const pathnameHasLocale = ['en', 'es', 'fr'].some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );
  
  if (pathnameHasLocale) return;
  
  // Detect locale
  const locale = detectLocaleFromRequest(request);
  
  // Redirect to locale-prefixed URL
  request.nextUrl.pathname = `/${locale}${pathname}`;
  return NextResponse.redirect(request.nextUrl);
}

function detectLocaleFromRequest(request: NextRequest) {
  // 1. Check cookie
  const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
  if (cookieLocale) return cookieLocale;
  
  // 2. Check Accept-Language header
  const acceptLanguage = request.headers.get('accept-language');
  if (acceptLanguage) {
    const locale = acceptLanguage.split(',')[0].split('-')[0];
    if (['en', 'es', 'fr'].includes(locale)) return locale;
  }
  
  // 3. Fallback
  return 'en';
}

// i18next language detection
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(LanguageDetector)
  .init({
    detection: {
      order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
      lookupQuerystring: 'lng',
      lookupCookie: 'i18next',
      lookupLocalStorage: 'i18nextLng',
      caches: ['localStorage', 'cookie']
    },
    fallbackLng: 'en'
  });

// GeoIP-based locale detection
async function detectLocaleByIP() {
  try {
    const response = await fetch('https://ipapi.co/json/');
    const data = await response.json();
    
    const countryToLocale = {
      'US': 'en',
      'GB': 'en',
      'ES': 'es',
      'MX': 'es',
      'FR': 'fr',
      'DE': 'de'
    };
    
    return countryToLocale[data.country_code] || 'en';
  } catch (error) {
    return 'en';
  }
}

// Language switcher component
function LanguageSwitcher() {
  const { i18n } = useTranslation();
  const router = useRouter();
  
  const languages = [
    { code: 'en', name: 'English', flag: '🇺🇸' },
    { code: 'es', name: 'Español', flag: '🇪🇸' },
    { code: 'fr', name: 'Français', flag: '🇫🇷' },
    { code: 'ar', name: 'العربية', flag: '🇸🇦' }
  ];
  
  const changeLanguage = (code) => {
    i18n.changeLanguage(code);
    
    // Update URL
    const newPath = router.pathname.replace(/^\/[a-z]{2}/, `/${code}`);
    router.push(newPath);
    
    // Update cookie
    document.cookie = `locale=${code}; path=/; max-age=31536000`;
    
    // Update localStorage
    localStorage.setItem('locale', code);
    
    // Update HTML lang
    document.documentElement.lang = code;
  };
  
  return (
    <select value={i18n.language} onChange={(e) => changeLanguage(e.target.value)}>
      {languages.map(({ code, name, flag }) => (
        <option key={code} value={code}>
          {flag} {name}
        </option>
      ))}
    </select>
  );
}

// Accept-Language header parsing
function parseAcceptLanguage(header) {
  return header
    .split(',')
    .map(lang => {
      const [code, qValue] = lang.trim().split(';q=');
      return {
        code: code.split('-')[0],
        quality: qValue ? parseFloat(qValue) : 1.0
      };
    })
    .sort((a, b) => b.quality - a.quality)
    .map(lang => lang.code);
}

// Example: "en-US,es;q=0.9,fr;q=0.8"
// Returns: ["en", "es", "fr"]

// Automatic redirect based on locale
useEffect(() => {
  const detectedLocale = detectLocale();
  const currentLocale = i18n.language;
  
  if (detectedLocale !== currentLocale && !localStorage.getItem('locale-confirmed')) {
    // Show banner suggesting language
    setShowLocaleBanner(true);
  }
}, []);

function LocaleBanner({ suggestedLocale, onAccept, onDismiss }) {
  return (
    <div className="locale-banner">
      Would you like to view this site in {getLanguageName(suggestedLocale)}?
      <button onClick={onAccept}>Yes</button>
      <button onClick={onDismiss}>No</button>
    </div>
  );
}

Detection Priority

  1. URL parameter (?lang=es)
  2. Subdomain (es.example.com)
  3. Path prefix (/es/page)
  4. Cookie (persistent)
  5. localStorage (persistent)
  6. Accept-Language header
  7. navigator.language
  8. Fallback (en)

Best Practices

  • ✅ Respect explicit user choice
  • ✅ Store preference persistently
  • ✅ Show language switcher prominently
  • ✅ Use path prefixes for SEO
  • ✅ Fallback to English or default
  • ❌ Don't auto-redirect without confirmation
  • ❌ Don't rely solely on IP location

Internationalization Summary

  • Libraries: Use i18next (most flexible) or next-intl (Next.js optimized)
  • Lazy Loading: Dynamic imports for locale files, reduce bundle by 60-80%
  • RTL Support: CSS logical properties for automatic RTL layout
  • Dates: date-fns with locales or native Intl.DateTimeFormat
  • Pluralization: ICU message format for complex plural rules
  • Detection: URL parameter → Cookie → Browser language → Fallback
Testing is Critical: Test with native speakers for each language. Machine translation isn't enough. Consider hiring professional translators for production apps.

10. Modern Theming Styling Implementation

10.1 CSS-in-JS Styled-components Emotion

Library Features Bundle Size Use Case
styled-components Tagged templates, theming, SSR support ~16KB Most popular, component-scoped styles, dynamic theming
Emotion Framework agnostic, better performance, smaller bundle ~8KB Faster than styled-components, works with React/Vue/Angular
Linaria Zero-runtime, extracts to CSS files at build time 0KB runtime Best performance, no runtime overhead, static extraction
Vanilla Extract Zero-runtime, TypeScript-first, type-safe styles 0KB runtime Type-safe theming, build-time extraction, excellent DX
Stitches Near-zero runtime, variants API, best-in-class DX ~6KB Modern API, great TypeScript support, variant-based styling

Example: CSS-in-JS Implementation

// styled-components
import styled from 'styled-components';

const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  
  &:hover {
    background-color: ${props => props.theme.colors.primaryDark};
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// Dynamic props
const Container = styled.div`
  display: flex;
  flex-direction: ${props => props.direction || 'row'};
  gap: ${props => props.gap || '1rem'};
  padding: ${props => props.padding || '0'};
`;

// Theming
import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#007bff',
    primaryDark: '#0056b3',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545'
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px'
  },
  breakpoints: {
    mobile: '768px',
    tablet: '1024px',
    desktop: '1440px'
  }
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Click me</Button>
      <Container direction="column" gap="2rem">
        {/* Content */}
      </Container>
    </ThemeProvider>
  );
}

// Responsive styling
const ResponsiveBox = styled.div`
  width: 100%;
  padding: 1rem;
  
  @media (min-width: ${props => props.theme.breakpoints.mobile}) {
    width: 50%;
    padding: 2rem;
  }
  
  @media (min-width: ${props => props.theme.breakpoints.desktop}) {
    width: 33.333%;
    padding: 3rem;
  }
`;

// Extending styles
const PrimaryButton = styled(Button)`
  background-color: ${props => props.theme.colors.primary};
`;

const SecondaryButton = styled(Button)`
  background-color: ${props => props.theme.colors.secondary};
`;

// Emotion (@emotion/styled)
import styled from '@emotion/styled';
import { css } from '@emotion/react';

const Button = styled.button`
  ${props => css`
    background: ${props.theme.colors.primary};
    padding: ${props.theme.spacing.md};
  `}
`;

// Emotion css prop
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

function MyComponent() {
  return (
    <div
      css={css`
        color: hotpink;
        &:hover {
          color: turquoise;
        }
      `}
    >
      Hello
    </div>
  );
}

// Stitches (near-zero runtime)
import { styled } from '@stitches/react';

const Button = styled('button', {
  backgroundColor: '$primary',
  padding: '$2 $4',
  borderRadius: '$md',
  
  variants: {
    variant: {
      primary: { backgroundColor: '$primary' },
      secondary: { backgroundColor: '$secondary' }
    },
    size: {
      sm: { padding: '$1 $2', fontSize: '$sm' },
      md: { padding: '$2 $4', fontSize: '$md' },
      lg: { padding: '$3 $6', fontSize: '$lg' }
    }
  },
  
  defaultVariants: {
    variant: 'primary',
    size: 'md'
  }
});

// Usage
<Button variant="secondary" size="lg">Click me</Button>

// Vanilla Extract (zero-runtime, type-safe)
// styles.css.ts
import { style, createTheme } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
  colors: {
    primary: '#007bff',
    secondary: '#6c757d'
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '24px'
  }
});

export const button = style({
  backgroundColor: vars.colors.primary,
  padding: `${vars.spacing.sm} ${vars.spacing.md}`,
  borderRadius: '4px',
  ':hover': {
    opacity: 0.8
  }
});

// Component
import { button, themeClass } from './styles.css';

function Button() {
  return <button className={button}>Click me</button>;
}

// Global styles (styled-components)
import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  
  body {
    font-family: ${props => props.theme.fonts.body};
    color: ${props => props.theme.colors.text};
    background: ${props => props.theme.colors.background};
  }
`;

function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />
      <Content />
    </ThemeProvider>
  );
}

CSS-in-JS Comparison

Library Runtime Performance
styled-components Runtime Good
Emotion Runtime Better
Linaria Zero Excellent
Vanilla Extract Zero Excellent
Stitches Near-zero Excellent

Benefits of CSS-in-JS

  • ✅ Component-scoped styles
  • ✅ Dynamic theming
  • ✅ No class name collisions
  • ✅ Dead code elimination
  • ✅ Type-safe styles (TypeScript)
  • ✅ Critical CSS automatic
  • ⚠️ Runtime overhead (except zero-runtime)
Modern Recommendation: Use Vanilla Extract or Stitches for new projects. Zero/near-zero runtime = best performance. styled-components for existing projects.

10.2 CSS Variables Custom Properties

Feature CSS Variables Sass Variables Advantage
Runtime Updates ✅ Yes ❌ No (compile-time) Dynamic theming, dark mode without re-compile
JavaScript Access ✅ getComputedStyle ❌ No Toggle themes with JS, reactive updates
Inheritance ✅ Cascades ❌ Static Scoped themes, component-level overrides
Browser Support 96%+ modern browsers N/A (compiles to CSS) Native, no preprocessor needed

Example: CSS Variables for Theming

// Define CSS variables in :root
:root {
  /* Colors */
  --color-primary: #007bff;
  --color-primary-dark: #0056b3;
  --color-primary-light: #66b3ff;
  --color-secondary: #6c757d;
  --color-success: #28a745;
  --color-danger: #dc3545;
  --color-warning: #ffc107;
  
  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  /* Typography */
  --font-family-base: 'Inter', -apple-system, sans-serif;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 18px;
  --font-weight-normal: 400;
  --font-weight-bold: 700;
  
  /* Borders */
  --border-radius-sm: 4px;
  --border-radius-md: 8px;
  --border-radius-lg: 12px;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
  
  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-base: 300ms ease;
  --transition-slow: 500ms ease;
}

// Using CSS variables
.button {
  background-color: var(--color-primary);
  color: white;
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--border-radius-md);
  font-family: var(--font-family-base);
  font-size: var(--font-size-md);
  transition: all var(--transition-fast);
  box-shadow: var(--shadow-sm);
}

.button:hover {
  background-color: var(--color-primary-dark);
  box-shadow: var(--shadow-md);
}

// Fallback values
.element {
  color: var(--color-text, #333); // Falls back to #333
  padding: var(--spacing-md, 16px);
}

// Scoped CSS variables (component-level)
.card {
  --card-padding: var(--spacing-lg);
  --card-background: white;
  --card-shadow: var(--shadow-md);
  
  padding: var(--card-padding);
  background: var(--card-background);
  box-shadow: var(--card-shadow);
}

.card.large {
  --card-padding: var(--spacing-xl);
}

// Dark mode with CSS variables
:root {
  --color-background: white;
  --color-text: #333;
  --color-border: #e0e0e0;
}

[data-theme="dark"] {
  --color-background: #1a1a1a;
  --color-text: #f0f0f0;
  --color-border: #404040;
}

// JavaScript: Toggle theme
function toggleTheme() {
  const root = document.documentElement;
  const currentTheme = root.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  root.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
}

// React: Get/Set CSS variables
function useCSSVariable(variable, defaultValue) {
  const [value, setValue] = useState(() => {
    const root = document.documentElement;
    return getComputedStyle(root).getPropertyValue(variable) || defaultValue;
  });
  
  const updateValue = (newValue) => {
    document.documentElement.style.setProperty(variable, newValue);
    setValue(newValue);
  };
  
  return [value, updateValue];
}

// Usage
function ColorPicker() {
  const [primaryColor, setPrimaryColor] = useCSSVariable('--color-primary', '#007bff');
  
  return (
    <input
      type="color"
      value={primaryColor}
      onChange={(e) => setPrimaryColor(e.target.value)}
    />
  );
}

// Calc with CSS variables
:root {
  --base-spacing: 8px;
}

.element {
  padding: calc(var(--base-spacing) * 2); // 16px
  margin: calc(var(--base-spacing) * 3);  // 24px
}

// Responsive CSS variables
:root {
  --container-padding: 1rem;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 2rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 3rem;
  }
}

.container {
  padding: var(--container-padding);
}

// CSS variables with HSL for color variations
:root {
  --primary-h: 211;
  --primary-s: 100%;
  --primary-l: 50%;
  
  --color-primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
  --color-primary-light: hsl(var(--primary-h), var(--primary-s), calc(var(--primary-l) + 10%));
  --color-primary-dark: hsl(var(--primary-h), var(--primary-s), calc(var(--primary-l) - 10%));
}

// Tailwind CSS with CSS variables
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: 'var(--color-primary)',
        secondary: 'var(--color-secondary)'
      },
      spacing: {
        'custom': 'var(--spacing-custom)'
      }
    }
  }
};

// Usage
<div className="bg-primary text-white p-custom">Content</div>

CSS Variables Best Practices

When to Use CSS Variables

  • ✅ Dark mode / theme switching
  • ✅ User-customizable colors
  • ✅ Responsive design tokens
  • ✅ Dynamic component theming
  • ✅ Design system tokens
  • ✅ Runtime theme updates
  • ❌ Complex calculations (use Sass)
Performance: CSS variables have zero runtime overhead and are faster than JavaScript-based theming. Perfect for dark mode and design systems.

10.3 Tailwind CSS Utility-first Design

Approach Pros Cons Use Case
Tailwind CSS Rapid development, consistent design, small production CSS Verbose HTML, learning curve, not semantic Prototyping, design systems, consistent styling
Traditional CSS Semantic class names, separation of concerns CSS bloat, naming conventions, specificity issues Custom designs, legacy projects
CSS Modules Scoped styles, traditional CSS Boilerplate, no utility classes Component-scoped styles

Example: Tailwind CSS Implementation

// Installation
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}'
  ],
  theme: {
    extend: {
      colors: {
        primary: '#007bff',
        secondary: '#6c757d'
      },
      spacing: {
        '128': '32rem',
        '144': '36rem'
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif']
      }
    }
  },
  plugins: []
};

// Basic usage
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Click me
</button>

// Responsive design
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <div className="bg-white p-4 rounded shadow">Card 1</div>
  <div className="bg-white p-4 rounded shadow">Card 2</div>
  <div className="bg-white p-4 rounded shadow">Card 3</div>
</div>

// Flexbox layout
<div className="flex items-center justify-between p-4 bg-gray-100">
  <div className="text-lg font-bold">Logo</div>
  <nav className="flex gap-4">
    <a href="#" className="text-gray-700 hover:text-blue-500">Home</a>
    <a href="#" className="text-gray-700 hover:text-blue-500">About</a>
  </nav>
</div>

// Dark mode
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Content
</div>

// State variants
<input
  type="text"
  className="border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 disabled:opacity-50"
/>

// Custom components with @apply
// styles.css
@layer components {
  .btn-primary {
    @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
  }
  
  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }
}

// React component
function Button({ children, variant = 'primary' }) {
  const baseClasses = 'font-bold py-2 px-4 rounded transition-colors';
  
  const variants = {
    primary: 'bg-blue-500 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-500 hover:bg-gray-700 text-white',
    outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white'
  };
  
  return (
    <button className={`${baseClasses} ${variants[variant]}`}>
      {children}
    </button>
  );
}

// Conditional classes with clsx
import clsx from 'clsx';

function Card({ featured, disabled }) {
  return (
    <div className={clsx(
      'p-4 rounded shadow',
      featured && 'border-2 border-blue-500',
      disabled && 'opacity-50 cursor-not-allowed'
    )}>
      Content
    </div>
  );
}

// Tailwind with CSS variables
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: 'rgb(var(--color-primary) / <alpha-value>)',
        secondary: 'rgb(var(--color-secondary) / <alpha-value>)'
      }
    }
  }
};

// CSS
:root {
  --color-primary: 0 123 255;
  --color-secondary: 108 117 125;
}

// Usage with opacity
<div className="bg-primary/50 text-primary">50% opacity</div>

// Arbitrary values
<div className="w-[137px] bg-[#1da1f2] top-[117px]">
  Custom values
</div>

// JIT mode (Just-in-Time)
// Generates only used classes, instant compile
<div className="mt-[137px]">Custom spacing</div>

// Plugins
// tailwind.config.js
module.exports = {
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
    require('@tailwindcss/line-clamp')
  ]
};

// Typography plugin
<article className="prose lg:prose-xl">
  <h1>Title</h1>
  <p>Automatically styled content</p>
</article>

Tailwind Benefits

  • ✅ No CSS naming conventions needed
  • ✅ Consistent design system
  • ✅ Tiny production CSS (3-10KB)
  • ✅ Rapid prototyping
  • ✅ Mobile-first responsive
  • ✅ Purges unused classes
  • ✅ Great with component libraries

Common Patterns

Pattern Classes
Center content flex items-center justify-center
Full height min-h-screen
Responsive grid grid grid-cols-1 md:grid-cols-3
Card bg-white rounded-lg shadow-md p-6
Production Ready: Tailwind CSS is used by GitHub, Shopify, Netflix. Production CSS is 3-10KB after purging unused classes.

10.4 Design Tokens Theme UI

Tool Purpose Format Use Case
Style Dictionary Transform design tokens to multiple platforms JSON → CSS/JS/iOS/Android Multi-platform design systems
Theme UI Constraint-based theming for React JavaScript theme object React apps with design constraints
Design Tokens W3C Standard format for design tokens JSON spec Industry standard, tool interoperability
Figma Tokens Sync design tokens with Figma Figma plugin → JSON Designer-developer collaboration

Example: Design Tokens Implementation

// Design tokens JSON
// tokens.json
{
  "color": {
    "brand": {
      "primary": { "value": "#007bff" },
      "secondary": { "value": "#6c757d" }
    },
    "semantic": {
      "success": { "value": "#28a745" },
      "danger": { "value": "#dc3545" },
      "warning": { "value": "#ffc107" }
    },
    "neutral": {
      "100": { "value": "#f8f9fa" },
      "200": { "value": "#e9ecef" },
      "900": { "value": "#212529" }
    }
  },
  "spacing": {
    "xs": { "value": "4px" },
    "sm": { "value": "8px" },
    "md": { "value": "16px" },
    "lg": { "value": "24px" },
    "xl": { "value": "32px" }
  },
  "typography": {
    "fontFamily": {
      "base": { "value": "Inter, system-ui, sans-serif" },
      "heading": { "value": "Poppins, sans-serif" },
      "mono": { "value": "Fira Code, monospace" }
    },
    "fontSize": {
      "xs": { "value": "12px" },
      "sm": { "value": "14px" },
      "base": { "value": "16px" },
      "lg": { "value": "18px" },
      "xl": { "value": "20px" },
      "2xl": { "value": "24px" }
    },
    "fontWeight": {
      "normal": { "value": "400" },
      "medium": { "value": "500" },
      "semibold": { "value": "600" },
      "bold": { "value": "700" }
    }
  },
  "borderRadius": {
    "sm": { "value": "4px" },
    "md": { "value": "8px" },
    "lg": { "value": "12px" },
    "full": { "value": "9999px" }
  },
  "shadow": {
    "sm": { "value": "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
    "md": { "value": "0 4px 6px -1px rgba(0, 0, 0, 0.1)" },
    "lg": { "value": "0 10px 15px -3px rgba(0, 0, 0, 0.1)" }
  }
}

// Style Dictionary config
// config.json
{
  "source": ["tokens.json"],
  "platforms": {
    "css": {
      "transformGroup": "css",
      "buildPath": "build/css/",
      "files": [{
        "destination": "variables.css",
        "format": "css/variables"
      }]
    },
    "js": {
      "transformGroup": "js",
      "buildPath": "build/js/",
      "files": [{
        "destination": "tokens.js",
        "format": "javascript/es6"
      }]
    }
  }
}

// Generated CSS variables
:root {
  --color-brand-primary: #007bff;
  --color-brand-secondary: #6c757d;
  --spacing-md: 16px;
  --font-family-base: Inter, system-ui, sans-serif;
}

// Theme UI
// theme.js
export const theme = {
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#007bff',
    secondary: '#6c757d',
    muted: '#f6f6f6'
  },
  fonts: {
    body: 'Inter, system-ui, sans-serif',
    heading: 'Poppins, sans-serif',
    monospace: 'Fira Code, monospace'
  },
  fontSizes: [12, 14, 16, 20, 24, 32, 48, 64],
  space: [0, 4, 8, 16, 24, 32, 64, 128, 256],
  breakpoints: ['40em', '52em', '64em'],
  radii: {
    small: 4,
    medium: 8,
    large: 16,
    round: 9999
  },
  shadows: {
    small: '0 1px 2px rgba(0, 0, 0, 0.05)',
    medium: '0 4px 6px rgba(0, 0, 0, 0.1)',
    large: '0 10px 15px rgba(0, 0, 0, 0.1)'
  }
};

// Theme UI usage
/** @jsxImportSource theme-ui */
import { ThemeProvider } from 'theme-ui';

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Box
        sx={{
          bg: 'primary',
          color: 'white',
          p: 3,
          borderRadius: 'medium',
          boxShadow: 'medium'
        }}
      >
        Themed Box
      </Box>
    </ThemeProvider>
  );
}

// Responsive values
<Box
  sx={{
    width: ['100%', '50%', '33.333%'],
    p: [2, 3, 4]
  }}
/>

// TypeScript theme
// theme.ts
export interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
  };
  spacing: {
    xs: string;
    sm: string;
    md: string;
    lg: string;
  };
}

export const lightTheme: Theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    background: '#ffffff',
    text: '#212529'
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px'
  }
};

export const darkTheme: Theme = {
  colors: {
    primary: '#66b3ff',
    secondary: '#9ca3af',
    background: '#1a1a1a',
    text: '#f0f0f0'
  },
  spacing: lightTheme.spacing
};

// Design tokens in components
import { theme } from './theme';

const Button = styled.button`
  background: ${theme.colors.primary};
  padding: ${theme.spacing.md};
  border-radius: ${theme.radii.medium};
  font-family: ${theme.fonts.body};
`;

Token Categories

  • Color: Brand, semantic, neutral scales
  • Spacing: Padding, margin, gap values
  • Typography: Font families, sizes, weights
  • Border: Radius, width, style
  • Shadow: Elevation levels
  • Animation: Duration, easing
  • Breakpoint: Responsive sizes

Design System Benefits

  • ✅ Consistent design across products
  • ✅ Single source of truth
  • ✅ Easy theme switching
  • ✅ Designer-developer sync
  • ✅ Multi-platform support
  • ✅ Scalable system
Design System Scale: Large companies like Airbnb, Shopify, IBM use design tokens to maintain consistency across 100+ products and platforms.

10.5 Dark Mode Light Mode Toggle

Implementation Method Persistence SSR Support
CSS Variables Toggle data-theme attribute localStorage ✅ Yes (with script)
Class Toggle Add/remove .dark class localStorage/cookie ✅ Yes
prefers-color-scheme CSS media query System preference ✅ Yes (automatic)
Context API React context provider localStorage ⚠️ Requires hydration

Example: Dark Mode Implementation

// CSS with prefers-color-scheme
:root {
  --color-bg: white;
  --color-text: #333;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #1a1a1a;
    --color-text: #f0f0f0;
  }
}

body {
  background: var(--color-bg);
  color: var(--color-text);
}

// Manual toggle with data-theme
:root[data-theme="light"] {
  --color-bg: white;
  --color-text: #333;
}

:root[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #f0f0f0;
}

// React: Dark mode hook
function useDarkMode() {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });
  
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return { theme, toggleTheme };
}

// Usage
function App() {
  const { theme, toggleTheme } = useDarkMode();
  
  return (
    <div>
      <button onClick={toggleTheme}>
        {theme === 'light' ? '🌙' : '☀️'}
      </button>
    </div>
  );
}

// Respect system preference
function useDarkMode() {
  const [theme, setTheme] = useState(() => {
    const stored = localStorage.getItem('theme');
    if (stored) return stored;
    
    // Check system preference
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  });
  
  // Listen for system preference changes
  useEffect(() => {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    
    const handleChange = (e) => {
      if (!localStorage.getItem('theme')) {
        setTheme(e.matches ? 'dark' : 'light');
      }
    };
    
    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);
  
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  return { theme, setTheme };
}

// Next.js: Prevent flash of wrong theme
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <script
            dangerouslySetInnerHTML={{
              __html: `
                (function() {
                  const theme = localStorage.getItem('theme') || 
                    (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
                  document.documentElement.setAttribute('data-theme', theme);
                })();
              `
            }}
          />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// next-themes library (recommended)
import { ThemeProvider } from 'next-themes';

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="data-theme" defaultTheme="system">
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

// Usage with next-themes
import { useTheme } from 'next-themes';

function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Toggle Theme
    </button>
  );
}

// Tailwind dark mode
// tailwind.config.js
module.exports = {
  darkMode: 'class', // or 'media'
  // ...
};

// Usage
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Content adapts to theme
</div>

// Toggle dark class
function toggleDarkMode() {
  document.documentElement.classList.toggle('dark');
  const isDark = document.documentElement.classList.contains('dark');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
}

// Animated theme transition
html {
  transition: background-color 0.3s ease, color 0.3s ease;
}

// Three-way toggle (light/dark/system)
function ThemeSelector() {
  const { theme, setTheme } = useTheme();
  
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      <option value="light">☀️ Light</option>
      <option value="dark">🌙 Dark</option>
      <option value="system">💻 System</option>
    </select>
  );
}

Dark Mode Checklist

Color Considerations

  • Light Mode: High contrast, white backgrounds
  • Dark Mode: Not pure black (#1a1a1a better)
  • Contrast: Maintain WCAG AA (4.5:1)
  • Colors: Adjust saturation for dark mode
  • Images: Consider opacity or invert
  • Shadows: Lighter in dark mode
User Preference: 85% of users prefer dark mode in low-light environments. Always provide both options and respect system preferences.

10.6 Component Library Storybook Documentation

Tool Purpose Features Use Case
Storybook Component development environment Isolated development, documentation, testing, addons Build UI components in isolation, design system docs
Docusaurus Documentation website builder MDX, versioning, search, blog API docs, guides, design system documentation
Styleguidist React component documentation Auto-generated props, live editor React component libraries, simpler than Storybook
Ladle Lightweight Storybook alternative Vite-powered, faster, simpler Smaller projects, faster build times

Example: Storybook Setup

// Installation
npx storybook@latest init

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger']
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg']
    },
    disabled: {
      control: 'boolean'
    }
  }
};

export default meta;
type Story = StoryObj<typeof Button>;

// Stories
export const Primary: Story = {
  args: {
    children: 'Primary Button',
    variant: 'primary',
    size: 'md'
  }
};

export const Secondary: Story = {
  args: {
    children: 'Secondary Button',
    variant: 'secondary'
  }
};

export const Large: Story = {
  args: {
    children: 'Large Button',
    size: 'lg'
  }
};

export const Disabled: Story = {
  args: {
    children: 'Disabled Button',
    disabled: true
  }
};

// All variants
export const AllVariants: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: '1rem' }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
    </div>
  )
};

// With decorators
export const WithBackground: Story = {
  args: {
    children: 'Button on Dark Background'
  },
  decorators: [
    (Story) => (
      <div style={{ background: '#333', padding: '2rem' }}>
        <Story />
      </div>
    )
  ]
};

// Storybook configuration
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-themes'
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {}
  }
};

export default config;

// Theme configuration
// .storybook/preview.ts
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from '../src/theme';

export const decorators = [
  withThemeFromJSXProvider({
    themes: {
      light: lightTheme,
      dark: darkTheme
    },
    Provider: ThemeProvider
  })
];

// MDX documentation
// Button.mdx
import { Canvas, Meta, Story } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

# Button

A versatile button component with multiple variants and sizes.

## Usage

```jsx
import { Button } from '@/components/Button';

<Button variant="primary">Click me</Button>
```

## Variants

<Canvas of={ButtonStories.AllVariants} />

## Props

| Prop     | Type                              | Default   | Description          |
|----------|-----------------------------------|-----------|----------------------|
| variant  | 'primary' | 'secondary' | 'danger' | 'primary' | Button style variant |
| size     | 'sm' | 'md' | 'lg'              | 'md'      | Button size          |
| disabled | boolean                           | false     | Disable button       |

## Accessibility

- Uses semantic `button` element
- Supports keyboard navigation
- Includes focus styles
- Compatible with screen readers

// Testing with Storybook
// Button.test.tsx
import { composeStories } from '@storybook/react';
import { render, screen } from '@testing-library/react';
import * as stories from './Button.stories';

const { Primary, Disabled } = composeStories(stories);

test('renders primary button', () => {
  render(<Primary />);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

test('disabled button is not clickable', () => {
  render(<Disabled />);
  expect(screen.getByRole('button')).toBeDisabled();
});

// Interaction testing
// Button.stories.tsx
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';

export const ClickInteraction: Story = {
  args: {
    children: 'Click me'
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    await userEvent.click(button);
    await expect(button).toHaveFocus();
  }
};

// Visual regression testing
// Use Chromatic or Percy for visual testing
npm install --save-dev chromatic

// Build and publish
npm run build-storybook
npx chromatic --project-token=YOUR_TOKEN

Storybook Addons

  • Essentials: Docs, controls, actions, viewport
  • A11y: Accessibility testing
  • Interactions: Test user interactions
  • Themes: Theme switching
  • Links: Link stories together
  • Measure: Measure spacing
  • Outline: Show element outlines

Benefits

  • ✅ Isolated component development
  • ✅ Living documentation
  • ✅ Visual regression testing
  • ✅ Accessibility testing
  • ✅ Design system showcase
  • ✅ Component playground
  • ✅ Share with designers/PMs

Modern Theming & Styling Summary

  • CSS-in-JS: Use Vanilla Extract or Stitches for zero/near-zero runtime
  • CSS Variables: Perfect for dynamic theming and dark mode, zero overhead
  • Tailwind: Rapid development, consistent design, 3-10KB production CSS
  • Design Tokens: Single source of truth, multi-platform support
  • Dark Mode: CSS variables + localStorage + system preference
  • Documentation: Storybook for component libraries and design systems
Choose Wisely: Don't mix too many styling approaches. Pick one primary method (Tailwind OR CSS-in-JS OR CSS Modules) and stick with it for consistency.

11. Frontend Testing Implementation Stack

11.1 Jest Vitest Unit Testing Framework

Framework Speed Features Use Case
Jest Good Snapshot testing, mocking, coverage, jsdom Most popular, React default, comprehensive ecosystem
Vitest HOT Excellent (5-10x faster) Vite-powered, Jest-compatible API, ESM native Modern projects, Vite ecosystem, faster CI/CD
Mocha + Chai Good Flexible, BDD/TDD, require plugins Node.js testing, flexible assertion libraries
AVA Excellent (parallel) Concurrent tests, minimal API, fast Node.js, simple tests, parallel execution

Example: Jest & Vitest Unit Tests

// Jest configuration
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy",
    "^@/(.*)$": "<rootDir>/src/$1"
  },
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.tsx'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

// jest.setup.js
import '@testing-library/jest-dom';

// Basic unit test
// sum.test.ts
import { sum } from './sum';

describe('sum', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
  
  test('adds negative numbers', () => {
    expect(sum(-1, -2)).toBe(-3);
  });
});

// Async testing
// fetchUser.test.ts
import { fetchUser } from './api';

describe('fetchUser', () => {
  test('fetches user successfully', async () => {
    const user = await fetchUser(1);
    expect(user).toEqual({
      id: 1,
      name: 'John Doe'
    });
  });
  
  test('throws error for invalid id', async () => {
    await expect(fetchUser(-1)).rejects.toThrow('Invalid user ID');
  });
});

// Mocking
// userService.test.ts
import { getUserById } from './userService';
import * as api from './api';

jest.mock('./api');

describe('getUserById', () => {
  test('returns user data', async () => {
    const mockUser = { id: 1, name: 'John' };
    (api.fetchUser as jest.Mock).mockResolvedValue(mockUser);
    
    const user = await getUserById(1);
    expect(user).toEqual(mockUser);
    expect(api.fetchUser).toHaveBeenCalledWith(1);
  });
});

// Snapshot testing
// Button.test.tsx
import { render } from '@testing-library/react';
import { Button } from './Button';

test('renders button correctly', () => {
  const { container } = render(<Button>Click me</Button>);
  expect(container.firstChild).toMatchSnapshot();
});

// Vitest configuration
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './vitest.setup.ts',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'src/**/*.test.{ts,tsx}'
      ]
    }
  }
});

// Vitest test (Jest-compatible API)
// calculator.test.ts
import { describe, test, expect, vi } from 'vitest';
import { Calculator } from './Calculator';

describe('Calculator', () => {
  test('adds two numbers', () => {
    const calc = new Calculator();
    expect(calc.add(2, 3)).toBe(5);
  });
  
  test('mocks function call', () => {
    const mockFn = vi.fn(() => 42);
    expect(mockFn()).toBe(42);
    expect(mockFn).toHaveBeenCalledTimes(1);
  });
});

// Testing hooks
// useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  test('increments counter', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
});

// Parameterized tests
describe.each([
  [1, 1, 2],
  [2, 2, 4],
  [3, 3, 6]
])('sum(%i, %i)', (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(sum(a, b)).toBe(expected);
  });
});

// Timer mocking
test('calls callback after 1 second', () => {
  jest.useFakeTimers();
  const callback = jest.fn();
  
  setTimeout(callback, 1000);
  
  expect(callback).not.toBeCalled();
  
  jest.runAllTimers();
  
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
  
  jest.useRealTimers();
});

// Module mocking
jest.mock('axios');
import axios from 'axios';

test('fetches data from API', async () => {
  const mockData = { data: { id: 1 } };
  (axios.get as jest.Mock).mockResolvedValue(mockData);
  
  const result = await fetchData();
  expect(result).toEqual(mockData.data);
});

// Run tests
npm test                     // Run all tests
npm test -- --watch          // Watch mode
npm test -- --coverage       // With coverage
npm test -- Button.test.tsx  // Specific file
vitest                       // Vitest watch mode
vitest run                   // Vitest single run

Jest vs Vitest

Feature Jest Vitest
Speed Good 5-10x faster
ESM Support Experimental Native
Config Separate Shares with Vite
HMR
Ecosystem Mature Growing

Testing Best Practices

  • ✅ Write tests before fixing bugs
  • ✅ Test behavior, not implementation
  • ✅ Use descriptive test names
  • ✅ Follow AAA: Arrange, Act, Assert
  • ✅ Mock external dependencies
  • ✅ Aim for 80%+ code coverage
  • ✅ Keep tests fast (<100ms each)
Modern Choice: Use Vitest for new Vite projects (5-10x faster). Use Jest for existing projects or Create React App.

11.2 React Testing Library Vue Test Utils

Library Framework Philosophy Key Features
React Testing Library React Test user behavior, not implementation Query by role/text, user-event, accessibility
Vue Test Utils Vue Official Vue testing utility Mount, shallow, wrapper API, composition API
Enzyme React (legacy) Shallow rendering, implementation testing Deprecated, use RTL instead
Testing Library (core) Framework agnostic Base for all Testing Library variants Angular, Svelte, Preact support

Example: Component Testing

// React Testing Library
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';

describe('Button', () => {
  test('renders button with text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });
  
  test('calls onClick when clicked', async () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    const button = screen.getByRole('button');
    await userEvent.click(button);
    
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  test('is disabled when disabled prop is true', () => {
    render(<Button disabled>Click me</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

// Form testing
// LoginForm.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';

test('submits form with username and password', async () => {
  const handleSubmit = jest.fn();
  render(<LoginForm onSubmit={handleSubmit} />);
  
  const usernameInput = screen.getByLabelText(/username/i);
  const passwordInput = screen.getByLabelText(/password/i);
  const submitButton = screen.getByRole('button', { name: /login/i });
  
  await userEvent.type(usernameInput, 'john@example.com');
  await userEvent.type(passwordInput, 'password123');
  await userEvent.click(submitButton);
  
  await waitFor(() => {
    expect(handleSubmit).toHaveBeenCalledWith({
      username: 'john@example.com',
      password: 'password123'
    });
  });
});

// Async component testing
// UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { fetchUser } from './api';

jest.mock('./api');

test('displays user data after loading', async () => {
  const mockUser = { name: 'John Doe', email: 'john@example.com' };
  (fetchUser as jest.Mock).mockResolvedValue(mockUser);
  
  render(<UserProfile userId={1} />);
  
  // Loading state
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  
  // Wait for data to load
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
  
  expect(screen.getByText('john@example.com')).toBeInTheDocument();
});

// Query priority (recommended order)
// 1. getByRole (most accessible)
screen.getByRole('button', { name: /submit/i });

// 2. getByLabelText (for forms)
screen.getByLabelText(/username/i);

// 3. getByPlaceholderText
screen.getByPlaceholderText(/enter email/i);

// 4. getByText
screen.getByText(/welcome/i);

// 5. getByDisplayValue (for inputs with value)
screen.getByDisplayValue('John Doe');

// 6. getByAltText (for images)
screen.getByAltText(/profile picture/i);

// 7. getByTitle
screen.getByTitle(/close/i);

// 8. getByTestId (last resort)
screen.getByTestId('custom-element');

// Custom render with providers
// test-utils.tsx
import { render } from '@testing-library/react';
import { ThemeProvider } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const AllTheProviders = ({ children }) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false }
    }
  });
  
  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        {children}
      </ThemeProvider>
    </QueryClientProvider>
  );
};

export const customRender = (ui, options) => {
  return render(ui, { wrapper: AllTheProviders, ...options });
};

// Usage
import { customRender as render } from './test-utils';

test('renders with providers', () => {
  render(<MyComponent />);
  // Test code...
});

// Vue Test Utils
// Button.spec.ts
import { mount } from '@vue/test-utils';
import Button from './Button.vue';

describe('Button', () => {
  test('renders button with text', () => {
    const wrapper = mount(Button, {
      props: { label: 'Click me' }
    });
    
    expect(wrapper.text()).toContain('Click me');
  });
  
  test('emits click event', async () => {
    const wrapper = mount(Button);
    
    await wrapper.trigger('click');
    
    expect(wrapper.emitted()).toHaveProperty('click');
    expect(wrapper.emitted('click')).toHaveLength(1);
  });
});

// Vue with Composition API
// Counter.spec.ts
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';

test('increments count', async () => {
  const wrapper = mount(Counter);
  
  expect(wrapper.text()).toContain('Count: 0');
  
  await wrapper.find('button').trigger('click');
  
  expect(wrapper.text()).toContain('Count: 1');
});

// Testing slots
test('renders slot content', () => {
  const wrapper = mount(Card, {
    slots: {
      default: '<div>Slot content</div>'
    }
  });
  
  expect(wrapper.text()).toContain('Slot content');
});

RTL Query Methods

Query Returns Throws
getBy Element ✅ If not found
queryBy Element or null ❌ Never
findBy Promise<Element> ✅ If not found (async)
getAllBy Element[] ✅ If none found

Testing Checklist

Testing Philosophy: "The more your tests resemble the way your software is used, the more confidence they can give you." - Kent C. Dodds (RTL creator)

11.3 Cypress Playwright E2E Automation

Framework Features Speed Use Case
Cypress Time-travel debugging, real-time reload, screenshots Good E2E testing, developer-friendly, great DX
Playwright HOT Multi-browser, parallel, mobile emulation, traces Excellent Cross-browser E2E, modern automation, Microsoft-backed
Selenium Multi-language, WebDriver protocol, mature Slow Legacy projects, multi-language teams
Puppeteer Chrome DevTools Protocol, headless Chrome Good Chrome-only automation, web scraping

Example: E2E Testing

// Cypress
// cypress/e2e/login.cy.ts
describe('Login Flow', () => {
  beforeEach(() => {
    cy.visit('/login');
  });
  
  it('allows user to login successfully', () => {
    cy.get('input[name="email"]').type('john@example.com');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    
    // Assertions
    cy.url().should('include', '/dashboard');
    cy.contains('Welcome, John').should('be.visible');
  });
  
  it('shows error for invalid credentials', () => {
    cy.get('input[name="email"]').type('invalid@example.com');
    cy.get('input[name="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();
    
    cy.contains('Invalid credentials').should('be.visible');
    cy.url().should('include', '/login');
  });
});

// Custom commands
// cypress/support/commands.ts
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.get('input[name="email"]').type(email);
    cy.get('input[name="password"]').type(password);
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

// Usage
cy.login('john@example.com', 'password123');

// Intercept network requests
cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' }
  ]
}).as('getUsers');

cy.visit('/users');
cy.wait('@getUsers');
cy.contains('John Doe').should('be.visible');

// Playwright
// tests/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test('allows user to login successfully', async ({ page }) => {
    await page.goto('/login');
    
    await page.fill('input[name="email"]', 'john@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    
    // Assertions
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.locator('text=Welcome, John')).toBeVisible();
  });
  
  test('shows error for invalid credentials', async ({ page }) => {
    await page.goto('/login');
    
    await page.fill('input[name="email"]', 'invalid@example.com');
    await page.fill('input[name="password"]', 'wrongpassword');
    await page.click('button[type="submit"]');
    
    await expect(page.locator('text=Invalid credentials')).toBeVisible();
    await expect(page).toHaveURL(/.*login/);
  });
});

// Playwright fixtures (reusable context)
// tests/fixtures.ts
import { test as base } from '@playwright/test';

type MyFixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<MyFixtures>({
  authenticatedPage: async ({ page }, use) => {
    await page.goto('/login');
    await page.fill('input[name="email"]', 'john@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    await page.waitForURL('**/dashboard');
    await use(page);
  }
});

// Usage
test('user can access protected page', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/profile');
  await expect(authenticatedPage).toHaveURL(/.*profile/);
});

// Multi-browser testing (Playwright)
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 12'] } }
  ]
});

// API mocking (Playwright)
await page.route('**/api/users', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([
      { id: 1, name: 'John Doe' }
    ])
  });
});

// Screenshots and videos
test('takes screenshot on failure', async ({ page }) => {
  await page.goto('/');
  await page.screenshot({ path: 'screenshot.png' });
});

// Playwright traces (debugging)
npx playwright test --trace on
npx playwright show-trace trace.zip

// Parallel execution
// Cypress
npx cypress run --parallel --record --key YOUR_KEY

// Playwright
npx playwright test --workers 4

// Visual regression (Playwright)
await expect(page).toHaveScreenshot('homepage.png');

// Component testing (Cypress)
// components/Button.cy.tsx
import { Button } from './Button';

describe('Button Component', () => {
  it('renders', () => {
    cy.mount(<Button>Click me</Button>);
    cy.contains('Click me').should('be.visible');
  });
  
  it('calls onClick', () => {
    const onClick = cy.stub();
    cy.mount(<Button onClick={onClick}>Click me</Button>);
    cy.contains('Click me').click();
    cy.wrap(onClick).should('have.been.called');
  });
});

Cypress vs Playwright

Feature Cypress Playwright
Browser Support Chrome, Firefox, Edge Chrome, Firefox, Safari
Speed Good Faster (parallel)
Debugging Excellent (time-travel) Good (traces)
Mobile Viewport only Full emulation
Learning Curve Easy Moderate

E2E Testing Best Practices

  • ✅ Test critical user journeys
  • ✅ Use data-testid for stability
  • ✅ Mock external APIs
  • ✅ Run in CI/CD pipeline
  • ✅ Take screenshots on failure
  • ✅ Keep tests independent
  • ✅ Use page objects pattern
  • ⚠️ E2E tests are slow (run critical paths)
Modern Recommendation: Use Playwright for new projects (faster, better browser support). Use Cypress for better DX and time-travel debugging.

11.4 Storybook Visual Regression Testing

Tool Method Pricing Use Case
Chromatic Cloud-based visual diff, Storybook integration Free tier + paid Official Storybook solution, automated visual testing
Percy Visual snapshots, cross-browser testing Free tier + paid Multi-platform, CI/CD integration
BackstopJS Screenshot comparison, local testing Free (open source) Self-hosted, budget-friendly
Playwright Visual Built-in screenshot comparison Free Integrated with Playwright E2E tests

Example: Visual Regression Testing

// Chromatic with Storybook
// Install
npm install --save-dev chromatic

// Run visual tests
npx chromatic --project-token=YOUR_TOKEN

// package.json
{
  "scripts": {
    "chromatic": "chromatic --exit-zero-on-changes"
  }
}

// GitHub Actions integration
// .github/workflows/chromatic.yml
name: Chromatic
on: push

jobs:
  chromatic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build-storybook
      - uses: chromaui/action@v1
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

// Interaction testing with Storybook
// Button.stories.tsx
import { within, userEvent, expect } from '@storybook/test';

export const ClickInteraction: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    // Take screenshot before click
    await expect(button).toBeInTheDocument();
    
    // Interact
    await userEvent.click(button);
    
    // Verify state change
    await expect(button).toHaveClass('active');
  }
};

// Percy with Playwright
// Install
npm install --save-dev @percy/cli @percy/playwright

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'http://localhost:3000'
  }
});

// Visual test
import percySnapshot from '@percy/playwright';

test('homepage visual test', async ({ page }) => {
  await page.goto('/');
  await percySnapshot(page, 'Homepage');
});

test('responsive visual test', async ({ page }) => {
  await page.goto('/');
  
  // Desktop
  await page.setViewportSize({ width: 1920, height: 1080 });
  await percySnapshot(page, 'Homepage Desktop');
  
  // Tablet
  await page.setViewportSize({ width: 768, height: 1024 });
  await percySnapshot(page, 'Homepage Tablet');
  
  // Mobile
  await page.setViewportSize({ width: 375, height: 667 });
  await percySnapshot(page, 'Homepage Mobile');
});

// BackstopJS configuration
// backstop.json
{
  "id": "my_project",
  "viewports": [
    {
      "label": "phone",
      "width": 375,
      "height": 667
    },
    {
      "label": "tablet",
      "width": 768,
      "height": 1024
    },
    {
      "label": "desktop",
      "width": 1920,
      "height": 1080
    }
  ],
  "scenarios": [
    {
      "label": "Homepage",
      "url": "http://localhost:3000",
      "delay": 500
    },
    {
      "label": "Login Page",
      "url": "http://localhost:3000/login",
      "delay": 500
    }
  ],
  "paths": {
    "bitmaps_reference": "backstop_data/bitmaps_reference",
    "bitmaps_test": "backstop_data/bitmaps_test",
    "html_report": "backstop_data/html_report"
  }
}

// Run BackstopJS
backstop reference  // Create reference screenshots
backstop test       // Compare against reference
backstop approve    // Approve changes

// Playwright built-in visual regression
test('visual regression test', async ({ page }) => {
  await page.goto('/');
  
  // First run creates baseline
  // Subsequent runs compare against baseline
  await expect(page).toHaveScreenshot('homepage.png');
});

// Custom threshold
await expect(page).toHaveScreenshot('homepage.png', {
  maxDiffPixels: 100,
  threshold: 0.2
});

// Ignore specific elements
await expect(page).toHaveScreenshot('homepage.png', {
  mask: [page.locator('.dynamic-content')]
});

// Component-level visual testing
test('button variants visual test', async ({ page }) => {
  await page.goto('/storybook');
  
  const variants = ['primary', 'secondary', 'danger'];
  
  for (const variant of variants) {
    await page.goto(`/storybook/button-${variant}`);
    await expect(page.locator('.button')).toHaveScreenshot(`button-${variant}.png`);
  }
});

// Storybook test runner with visual testing
// .storybook/test-runner.ts
import { toMatchImageSnapshot } from 'jest-image-snapshot';

expect.extend({ toMatchImageSnapshot });

module.exports = {
  async postRender(page, context) {
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotsDir: `__snapshots__/${context.id}`,
      customSnapshotIdentifier: context.id
    });
  }
};

// Run Storybook test runner
npm run test-storybook

Visual Testing Benefits

  • ✅ Catch unintended UI changes
  • ✅ Detect CSS regression
  • ✅ Verify responsive layouts
  • ✅ Cross-browser consistency
  • ✅ Component library stability
  • ✅ Automated design QA
  • ✅ Reduce manual testing

Visual Testing Checklist

ROI: Visual regression testing caught 40% more bugs than manual testing in production systems (Shopify case study).

11.5 MSW API Mocking Integration Tests

Tool Approach Environment Use Case
MSW BEST Service Worker intercepts, same API mocks for tests/dev Browser + Node Modern API mocking, integration tests, dev environment
json-server Full REST API from JSON file Node server Quick prototypes, fake backend
Mirage JS In-memory API server, ORM-like Browser Complex data relationships, Ember/React
Nock HTTP request mocking for Node.js Node only Node.js API testing, backend tests

Example: MSW API Mocking

// Installation
npm install msw --save-dev

// Define handlers
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  // GET request
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]);
  }),
  
  // POST request
  http.post('/api/users', async ({ request }) => {
    const newUser = await request.json();
    return HttpResponse.json(
      { id: 3, ...newUser },
      { status: 201 }
    );
  }),
  
  // Dynamic route params
  http.get('/api/users/:userId', ({ params }) => {
    const { userId } = params;
    return HttpResponse.json({
      id: userId,
      name: 'John Doe',
      email: 'john@example.com'
    });
  }),
  
  // Error response
  http.get('/api/error', () => {
    return HttpResponse.json(
      { message: 'Internal Server Error' },
      { status: 500 }
    );
  }),
  
  // Delayed response
  http.get('/api/slow', async () => {
    await delay(2000);
    return HttpResponse.json({ data: 'Slow response' });
  })
];

// Setup for browser (development)
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

// Start in browser
// src/index.tsx
if (process.env.NODE_ENV === 'development') {
  const { worker } = await import('./mocks/browser');
  await worker.start();
}

// Setup for Node (testing)
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

// Jest setup
// jest.setup.ts
import { server } from './mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// Integration test with MSW
// UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';

test('displays list of users', async () => {
  render(<UserList />);
  
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('Jane Smith')).toBeInTheDocument();
  });
});

// Override handler for specific test
test('handles API error', async () => {
  server.use(
    http.get('/api/users', () => {
      return HttpResponse.json(
        { message: 'Server error' },
        { status: 500 }
      );
    })
  );
  
  render(<UserList />);
  
  await waitFor(() => {
    expect(screen.getByText(/error loading users/i)).toBeInTheDocument();
  });
});

// GraphQL mocking
import { graphql, HttpResponse } from 'msw';

const handlers = [
  graphql.query('GetUser', ({ query, variables }) => {
    return HttpResponse.json({
      data: {
        user: {
          id: variables.id,
          name: 'John Doe'
        }
      }
    });
  }),
  
  graphql.mutation('CreateUser', ({ query, variables }) => {
    return HttpResponse.json({
      data: {
        createUser: {
          id: '123',
          name: variables.name
        }
      }
    });
  })
];

// Request inspection
http.post('/api/users', async ({ request }) => {
  const body = await request.json();
  const headers = request.headers;
  
  console.log('Request body:', body);
  console.log('Auth header:', headers.get('Authorization'));
  
  return HttpResponse.json({ id: 1, ...body });
});

// Conditional responses
http.get('/api/users', ({ request }) => {
  const url = new URL(request.url);
  const role = url.searchParams.get('role');
  
  if (role === 'admin') {
    return HttpResponse.json([
      { id: 1, name: 'Admin User', role: 'admin' }
    ]);
  }
  
  return HttpResponse.json([
    { id: 2, name: 'Regular User', role: 'user' }
  ]);
});

// Response composition
import { HttpResponse } from 'msw';

http.get('/api/users', () => {
  return HttpResponse.json(
    { users: [] },
    {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
        'X-Custom-Header': 'value'
      }
    }
  );
});

// Life-cycle events
server.events.on('request:start', ({ request }) => {
  console.log('Outgoing:', request.method, request.url);
});

server.events.on('response:mocked', ({ response }) => {
  console.log('Mocked:', response.status);
});

// Browser DevTools integration
// Enable in-browser debugging
worker.start({
  onUnhandledRequest: 'warn' // or 'error', 'bypass'
});

// json-server (quick prototyping)
// db.json
{
  "users": [
    { "id": 1, "name": "John Doe" },
    { "id": 2, "name": "Jane Smith" }
  ],
  "posts": [
    { "id": 1, "userId": 1, "title": "Hello World" }
  ]
}

// Start server
npx json-server --watch db.json --port 3001

// Full CRUD API automatically available:
// GET    /users
// GET    /users/1
// POST   /users
// PUT    /users/1
// PATCH  /users/1
// DELETE /users/1

MSW Benefits

  • ✅ Same mocks for tests and dev
  • ✅ No code changes needed
  • ✅ Intercepts at network level
  • ✅ Works with any HTTP client
  • ✅ GraphQL support
  • ✅ Browser and Node.js
  • ✅ TypeScript-first
  • ✅ No proxy server needed

API Mocking Use Cases

  • Development: Work without backend
  • Testing: Predictable API responses
  • E2E: Mock external services
  • Edge Cases: Test error scenarios
  • Offline: Work without internet
  • Performance: Fast local responses
  • Demos: Reliable demo data
Industry Standard: MSW is used by Microsoft, Netflix, Amazon. Over 1M+ downloads/month. Winner of "Best Testing Tool" at JSNation 2022.

11.6 Coverage.js Istanbul Test Coverage

Tool Method Integration Use Case
Istanbul (nyc) Code instrumentation, coverage reports Jest, Mocha, CLI Industry standard, comprehensive reports
c8 Native V8 coverage, faster Node.js, CLI Modern Node.js, no instrumentation
Vitest Coverage V8 or Istanbul Vitest built-in Vite projects, fast coverage
Jest Coverage Istanbul integration Jest built-in React projects, zero config

Example: Test Coverage Configuration

// Jest with coverage
// jest.config.js
module.exports = {
  collectCoverage: true,
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.tsx',
    '!src/**/*.test.tsx',
    '!src/index.tsx'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    },
    './src/components/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90
    }
  },
  coverageReporters: ['text', 'lcov', 'html', 'json-summary']
};

// Run with coverage
npm test -- --coverage
npm test -- --coverage --watchAll=false

// Vitest with coverage
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8', // or 'istanbul'
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'src/**/*.test.ts',
        'src/**/*.spec.ts',
        '**/*.config.ts'
      ],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 80,
        statements: 80
      }
    }
  }
});

// Run with coverage
npm run test -- --coverage

// NYC (Istanbul CLI)
// .nycrc
{
  "all": true,
  "include": ["src/**/*.js"],
  "exclude": [
    "**/*.test.js",
    "**/node_modules/**"
  ],
  "reporter": ["html", "text", "lcov"],
  "check-coverage": true,
  "lines": 80,
  "functions": 80,
  "branches": 80,
  "statements": 80
}

// Run with nyc
npx nyc mocha

// Coverage reports interpretation
// Text output
------------|---------|----------|---------|---------|
File        | % Stmts | % Branch | % Funcs | % Lines |
------------|---------|----------|---------|---------|
All files   |   85.5  |   78.3   |   82.1  |   85.2  |
 Button.tsx |   100   |   100    |   100   |   100   |
 Input.tsx  |   92.3  |   85.7   |   90    |   92.1  |
 Modal.tsx  |   68.4  |   50     |   60    |   67.8  |
------------|---------|----------|---------|---------|

// Coverage metrics explained:
// - Statements: % of executable statements covered
// - Branches: % of if/else branches covered
// - Functions: % of functions called
// - Lines: % of lines executed

// Ignore coverage for specific code
/* istanbul ignore next */
function debugOnly() {
  console.log('Debug info');
}

// Ignore entire file
/* istanbul ignore file */

// c8 (native V8 coverage)
{
  "scripts": {
    "test:coverage": "c8 --reporter=html --reporter=text npm test"
  }
}

// Codecov integration (CI/CD)
// .github/workflows/test.yml
name: Test Coverage

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm test -- --coverage
      - uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

// Coveralls integration
- run: npm test -- --coverage --coverageReporters=text-lcov | coveralls

// SonarQube integration
sonar.javascript.lcov.reportPaths=coverage/lcov.info

// Custom coverage reporter
// custom-reporter.js
class CustomReporter {
  onRunComplete(contexts, results) {
    const { numTotalTests, numPassedTests } = results;
    const coverage = (numPassedTests / numTotalTests) * 100;
    
    console.log(`Coverage: ${coverage.toFixed(2)}%`);
    
    if (coverage < 80) {
      throw new Error('Coverage threshold not met');
    }
  }
}

module.exports = CustomReporter;

// Coverage badges (README.md)
![Coverage](https://img.shields.io/codecov/c/github/username/repo)

// Branch-specific thresholds
coverageThreshold: {
  global: {
    branches: 80,
    functions: 80,
    lines: 80,
    statements: 80
  },
  './src/critical/**/*.ts': {
    branches: 100,
    functions: 100,
    lines: 100,
    statements: 100
  }
}

// Coverage diff in PRs
npx coverage-diff --base=main --head=feature-branch

// Enforce coverage on commit
// .husky/pre-push
#!/bin/sh
npm test -- --coverage --coverageThreshold='{"global":{"branches":80}}'

Coverage Metrics

Metric Target Priority
Statements 80%+ High
Branches 80%+ Critical
Functions 80%+ High
Lines 80%+ Medium

Coverage Best Practices

  • ✅ Aim for 80%+ coverage
  • ✅ 100% for critical paths
  • ✅ Focus on branch coverage
  • ✅ Exclude test files
  • ✅ Run in CI/CD pipeline
  • ✅ Track coverage trends
  • ⚠️ Don't chase 100% blindly
  • ⚠️ Quality > Quantity

Frontend Testing Stack Summary

  • Unit Tests: Vitest (5-10x faster than Jest) for Vite projects, Jest for React/CRA
  • Component Tests: React Testing Library (test behavior not implementation)
  • E2E Tests: Playwright (faster, better browsers) or Cypress (better DX)
  • Visual Tests: Chromatic for Storybook, Playwright for E2E visual regression
  • API Mocking: MSW (Mock Service Worker) - same mocks for dev and tests
  • Coverage: Built-in with Jest/Vitest, aim for 80%+ with focus on branches
Testing Pyramid: Write mostly unit tests (70%), some integration tests (20%), few E2E tests (10%). E2E tests are slow and brittle.

12. Accessibility Implementation Best Practices

12.1 WCAG 2.1 AA Compliance Implementation

Level Requirements Target Audience Legal Status
WCAG 2.1 A 30 criteria - basic accessibility Minimum for most disabilities Baseline legal requirement
WCAG 2.1 AA TARGET 50 criteria - enhanced accessibility Most users with disabilities Required by ADA, Section 508, EU
WCAG 2.1 AAA 78 criteria - highest accessibility Specialized needs Recommended but not required
WCAG 2.2 NEW 9 new criteria (mostly AA) Mobile, cognitive disabilities Emerging standard (2023)

Example: WCAG 2.1 AA Implementation

// 1.1.1 Non-text Content (A)
// All images must have alt text
<img src="logo.png" alt="Company Logo" />
<img src="decorative.png" alt="" /> {/* Decorative images */}

// 1.3.1 Info and Relationships (A)
// Use semantic HTML
<header>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>Page Title</h1>
    <section>
      <h2>Section Title</h2>
    </section>
  </article>
</main>

// 1.4.3 Contrast Minimum (AA)
// Minimum contrast ratio 4.5:1 for normal text, 3:1 for large text
.text {
  color: #333; /* On white background = 12.6:1 ratio ✅ */
  background: #fff;
}

.button {
  color: #fff;
  background: #0066cc; /* 4.5:1 ratio ✅ */
}

// 1.4.5 Images of Text (AA)
// Use actual text, not images of text (unless logo/brand)
<h1>Welcome</h1> {/* Good */}
<img src="welcome-text.png" alt="Welcome" /> {/* Avoid */}

// 1.4.10 Reflow (AA)
// Content reflows at 320px without horizontal scrolling
@media (max-width: 320px) {
  .content {
    width: 100%;
    overflow-x: hidden;
  }
}

// 1.4.11 Non-text Contrast (AA)
// UI components and graphical objects need 3:1 contrast
.button {
  border: 2px solid #767676; /* 3:1 contrast against white ✅ */
}

// 2.1.1 Keyboard (A)
// All functionality available via keyboard
<button onClick={handleClick} onKeyDown={handleKeyDown}>
  Click me
</button>

// Custom interactive elements
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  Custom Button
</div>

// 2.1.2 No Keyboard Trap (A)
// Users can navigate away using keyboard only
function Modal({ onClose }) {
  useEffect(() => {
    const handleEscape = (e) => {
      if (e.key === 'Escape') onClose();
    };
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [onClose]);
  
  return (
    <div role="dialog" aria-modal="true">
      <button onClick={onClose}>Close</button>
      {/* Modal content */}
    </div>
  );
}

// 2.4.1 Bypass Blocks (A)
// Skip navigation link
<a href="#main-content" className="skip-link">
  Skip to main content
</a>

<main id="main-content">
  {/* Content */}
</main>

.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
}

.skip-link:focus {
  top: 0;
}

// 2.4.3 Focus Order (A)
// Logical focus order using tabindex
<div>
  <button tabIndex={0}>First</button>
  <button tabIndex={0}>Second</button>
  <button tabIndex={0}>Third</button>
</div>

// 2.4.7 Focus Visible (AA)
// Visible focus indicator
button:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

// 3.2.1 On Focus (A)
// No context change on focus alone
<input
  type="text"
  onFocus={() => console.log('Focused')} {/* OK */}
  onFocus={() => submitForm()} {/* BAD - unexpected */}
/>

// 3.2.2 On Input (A)
// No context change on input alone
<input
  type="text"
  onChange={(e) => setValue(e.target.value)} {/* OK */}
  onChange={() => navigateAway()} {/* BAD - unexpected */}
/>

// 3.3.1 Error Identification (A)
// Clearly identify input errors
<form onSubmit={handleSubmit}>
  <label htmlFor="email">Email</label>
  <input
    id="email"
    type="email"
    aria-invalid={!!errors.email}
    aria-describedby={errors.email ? 'email-error' : undefined}
  />
  {errors.email && (
    <span id="email-error" role="alert" className="error">
      {errors.email}
    </span>
  )}
</form>

// 3.3.2 Labels or Instructions (A)
// Label all form inputs
<label htmlFor="username">Username</label>
<input id="username" type="text" required />

// Or use aria-label
<input type="search" aria-label="Search articles" />

// 4.1.2 Name, Role, Value (A)
// Custom components need ARIA
<div
  role="checkbox"
  aria-checked={isChecked}
  aria-labelledby="checkbox-label"
  tabIndex={0}
  onClick={toggle}
  onKeyDown={(e) => {
    if (e.key === ' ') toggle();
  }}
>
  <span id="checkbox-label">Accept terms</span>
</div>

// React accessibility hook
function useA11yAnnouncement() {
  const [announcement, setAnnouncement] = useState('');
  
  return {
    announce: (message) => {
      setAnnouncement(message);
      setTimeout(() => setAnnouncement(''), 100);
    },
    LiveRegion: () => (
      <div
        role="status"
        aria-live="polite"
        aria-atomic="true"
        className="sr-only"
      >
        {announcement}
      </div>
    )
  };
}

// Screen reader only utility
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

// Accessible form validation
function AccessibleForm() {
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  return (
    <form noValidate>
      <div>
        <label htmlFor="email">
          Email <span aria-label="required">*</span>
        </label>
        <input
          id="email"
          type="email"
          required
          aria-required="true"
          aria-invalid={touched.email && !!errors.email}
          aria-describedby={
            touched.email && errors.email ? 'email-error' : undefined
          }
        />
        {touched.email && errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>
    </form>
  );
}

// WCAG 2.2 new criteria
// 2.4.11 Focus Not Obscured (Minimum) (AA)
// Ensure focused element is not fully hidden
.modal {
  z-index: 1000;
  /* Ensure focus is visible within modal */
}

// 3.2.6 Consistent Help (A)
// Help mechanism in same location on all pages
<header>
  <a href="/help" aria-label="Get help">Help</a>
</header>

WCAG 2.1 AA Checklist

  • ADA (US): WCAG 2.1 AA for websites
  • Section 508 (US): Federal sites must comply
  • EAA (EU): June 2025 deadline
  • AODA (Canada): WCAG 2.0 AA required
  • DDA (UK): Reasonable adjustments
  • Penalties: Lawsuits, fines up to $75,000
Statistics: 26% of US adults have a disability. 1 in 4 potential customers. Accessible sites have better SEO, mobile UX, and legal protection.

12.2 Screen Reader Testing NVDA JAWS

Screen Reader Platform Market Share Cost
JAWS Windows ~40% $1,000+ (most feature-rich)
NVDA Windows ~30% Free (open source)
VoiceOver macOS, iOS ~15% Free (built-in)
TalkBack Android ~10% Free (built-in)
Narrator Windows ~5% Free (built-in)

Example: Screen Reader Testing

// Screen reader keyboard shortcuts
// NVDA (Windows)
NVDA + Space           // Toggle browse/focus mode
NVDA + Down Arrow      // Read next item
NVDA + T               // Next table
NVDA + H               // Next heading
NVDA + K               // Next link
NVDA + B               // Next button
NVDA + F               // Next form field
NVDA + Insert + F7     // Elements list

// JAWS (Windows)
Insert + Down Arrow    // Say all (read from cursor)
Insert + F5            // Form fields list
Insert + F6            // Headings list
Insert + F7            // Links list
H                      // Next heading
T                      // Next table
B                      // Next button

// VoiceOver (macOS)
Cmd + F5               // Turn on/off VoiceOver
VO + A                 // Read all
VO + Right Arrow       // Next item
VO + U                 // Rotor (elements list)
VO + H + H             // Next heading
VO + J + J             // Next form control

// Optimizing for screen readers
// Accessible button
<button
  aria-label="Delete item"
  onClick={handleDelete}
>
  <TrashIcon aria-hidden="true" />
</button>

// Accessible icon button with text
<button onClick={handleSave}>
  <SaveIcon aria-hidden="true" />
  <span>Save</span>
</button>

// Hidden from screen readers
<div aria-hidden="true">
  Decorative content
</div>

// Accessible navigation
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

// Multiple navigation landmarks
<nav aria-label="Primary navigation">...</nav>
<nav aria-label="Footer navigation">...</nav>

// Accessible table
<table>
  <caption>Monthly Sales Data</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>$10,000</td>
    </tr>
  </tbody>
</table>

// Accessible form with fieldset
<form>
  <fieldset>
    <legend>Shipping Address</legend>
    <label htmlFor="street">Street</label>
    <input id="street" type="text" />
    
    <label htmlFor="city">City</label>
    <input id="city" type="text" />
  </fieldset>
</form>

// Accessible dialog/modal
function Modal({ isOpen, onClose, title, children }) {
  const titleId = useId();
  const descId = useId();
  
  useEffect(() => {
    if (isOpen) {
      // Trap focus
      const firstFocusable = modalRef.current.querySelector('button, a, input');
      firstFocusable?.focus();
    }
  }, [isOpen]);
  
  if (!isOpen) return null;
  
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      aria-describedby={descId}
    >
      <h2 id={titleId}>{title}</h2>
      <div id={descId}>{children}</div>
      <button onClick={onClose}>Close</button>
    </div>
  );
}

// Accessible tabs
function Tabs({ tabs, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <div>
      <div role="tablist" aria-label="Content tabs">
        {tabs.map((tab) => (
          <button
            key={tab.id}
            role="tab"
            aria-selected={activeTab === tab.id}
            aria-controls={`panel-${tab.id}`}
            id={`tab-${tab.id}`}
            onClick={() => setActiveTab(tab.id)}
          >
            {tab.label}
          </button>
        ))}
      </div>
      {tabs.map((tab) => (
        <div
          key={tab.id}
          role="tabpanel"
          id={`panel-${tab.id}`}
          aria-labelledby={`tab-${tab.id}`}
          hidden={activeTab !== tab.id}
        >
          {tab.content}
        </div>
      ))}
    </div>
  );
}

// Accessible dropdown/combobox
<div>
  <label htmlFor="country">Country</label>
  <input
    id="country"
    role="combobox"
    aria-expanded={isOpen}
    aria-controls="country-list"
    aria-autocomplete="list"
    value={value}
    onChange={handleChange}
  />
  {isOpen && (
    <ul id="country-list" role="listbox">
      {options.map((option) => (
        <li
          key={option.id}
          role="option"
          aria-selected={value === option.label}
        >
          {option.label}
        </li>
      ))}
    </ul>
  )}
</div>

// Testing checklist for screen readers
// 1. Can you navigate with keyboard only?
// 2. Does Tab key move logically?
// 3. Are headings announced correctly?
// 4. Are landmarks (nav, main, footer) announced?
// 5. Are form labels read before inputs?
// 6. Are error messages announced?
// 7. Are button purposes clear?
// 8. Are icons described?
// 9. Can you complete main tasks?
// 10. Is dynamic content announced?

// React hook for screen reader announcements
function useScreenReaderAnnounce() {
  const [message, setMessage] = useState('');
  
  const announce = useCallback((msg, priority = 'polite') => {
    setMessage('');
    setTimeout(() => {
      setMessage(msg);
    }, 100);
  }, []);
  
  return {
    announce,
    LiveRegion: () => (
      <div
        role="status"
        aria-live="polite"
        aria-atomic="true"
        style={{
          position: 'absolute',
          left: '-10000px',
          width: '1px',
          height: '1px',
          overflow: 'hidden'
        }}
      >
        {message}
      </div>
    )
  };
}

Screen Reader Testing Checklist

Common Screen Reader Issues

  • ❌ Missing alt text on images
  • ❌ Unlabeled form inputs
  • ❌ Poor heading structure
  • ❌ Icon buttons without labels
  • ❌ Custom controls without ARIA
  • ❌ Dynamic content not announced
  • ❌ Focus lost after interactions
  • ❌ Decorative images not hidden
User Testing: Nothing beats real users. Hire screen reader users for usability testing. Automated tools catch only ~30% of issues.

12.3 Axe-core Automated Accessibility Testing

Tool Type Coverage Integration
axe-core BEST JavaScript library, browser extension ~57% of WCAG issues (best in class) Jest, Cypress, Playwright, Chrome DevTools
Lighthouse Chrome DevTools audit ~40% of issues (powered by axe-core) Chrome, CI/CD, PageSpeed Insights
WAVE Browser extension, API ~45% of issues, visual feedback Manual testing, browser extension
Pa11y CLI tool, CI/CD ~40% (uses HTML_CodeSniffer) Command line, automated testing

Example: Automated Accessibility Testing

// Install axe-core for Jest + React Testing Library
npm install --save-dev jest-axe

// jest.setup.js
import 'jest-axe/extend-expect';

// Component test with axe
// Button.test.tsx
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { Button } from './Button';

expect.extend(toHaveNoViolations);

test('Button has no accessibility violations', async () => {
  const { container } = render(<Button>Click me</Button>);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

// Test with custom rules
test('custom axe rules', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container, {
    rules: {
      'color-contrast': { enabled: true },
      'label': { enabled: true }
    }
  });
  expect(results).toHaveNoViolations();
});

// Cypress with axe
// cypress/support/commands.ts
import 'cypress-axe';

// cypress/e2e/accessibility.cy.ts
describe('Accessibility tests', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.injectAxe();
  });
  
  it('Has no detectable accessibility violations', () => {
    cy.checkA11y();
  });
  
  it('Checks specific element', () => {
    cy.checkA11y('.main-content');
  });
  
  it('Excludes specific elements', () => {
    cy.checkA11y(null, {
      exclude: [['.third-party-widget']]
    });
  });
  
  it('Checks with specific rules', () => {
    cy.checkA11y(null, {
      rules: {
        'color-contrast': { enabled: true }
      }
    });
  });
});

// Playwright with axe
npm install --save-dev @axe-core/playwright

// tests/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('should not have any automatically detectable accessibility issues', async ({ page }) => {
  await page.goto('http://localhost:3000');
  
  const accessibilityScanResults = await new AxeBuilder({ page })
    .analyze();
  
  expect(accessibilityScanResults.violations).toEqual([]);
});

// With specific tags
test('WCAG 2.1 Level AA compliance', async ({ page }) => {
  await page.goto('http://localhost:3000');
  
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();
  
  expect(results.violations).toEqual([]);
});

// Exclude third-party content
test('accessibility excluding ads', async ({ page }) => {
  await page.goto('http://localhost:3000');
  
  const results = await new AxeBuilder({ page })
    .exclude('.advertisement')
    .analyze();
  
  expect(results.violations).toEqual([]);
});

// Storybook with addon-a11y
// .storybook/main.ts
export default {
  addons: ['@storybook/addon-a11y']
};

// Button.stories.tsx - violations shown in panel
export default {
  title: 'Components/Button',
  component: Button,
  parameters: {
    a11y: {
      config: {
        rules: [
          {
            id: 'color-contrast',
            enabled: true
          }
        ]
      }
    }
  }
};

// Disable a11y for specific story
export const LowContrast: Story = {
  parameters: {
    a11y: {
      disable: true
    }
  }
};

// Chrome DevTools Lighthouse
// Run from DevTools > Lighthouse tab
// Or programmatically
npm install -g lighthouse

lighthouse https://example.com --only-categories=accessibility --output html --output-path ./report.html

// Pa11y CLI
npm install -g pa11y

// Test single page
pa11y https://example.com

// Test with specific standard
pa11y --standard WCAG2AA https://example.com

// JSON output
pa11y --reporter json https://example.com > results.json

// Pa11y CI for multiple pages
// .pa11yci.json
{
  "defaults": {
    "standard": "WCAG2AA",
    "timeout": 10000
  },
  "urls": [
    "http://localhost:3000/",
    "http://localhost:3000/about",
    "http://localhost:3000/contact"
  ]
}

// Run
pa11y-ci

// GitHub Actions for accessibility
// .github/workflows/a11y.yml
name: Accessibility Tests

on: [push, pull_request]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build
      - run: npm run serve &
      - run: sleep 5
      - run: npx pa11y-ci

// React hook for runtime a11y checks (dev only)
import { useEffect } from 'react';

function useA11yCheck() {
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      import('@axe-core/react').then((axe) => {
        axe.default(React, ReactDOM, 1000);
      });
    }
  }, []);
}

// Custom axe rules
const customRule = {
  id: 'custom-button-name',
  impact: 'serious',
  selector: 'button',
  any: [{
    id: 'has-accessible-name',
    evaluate: (node) => {
      return node.hasAttribute('aria-label') || node.textContent.trim();
    }
  }]
};

axe.configure({
  rules: [customRule]
});

// Accessibility testing strategy
// 1. Automated tests (30% of issues)
//    - Run axe-core in unit tests
//    - Run Lighthouse in CI/CD
//    - Use Storybook addon-a11y
// 2. Manual keyboard testing (40% of issues)
//    - Tab through entire page
//    - Test with screen reader
// 3. User testing (30% of issues)
//    - Test with real users with disabilities

Automated Testing Benefits

  • ✅ Fast feedback during development
  • ✅ Catches ~57% of WCAG issues (axe)
  • ✅ Prevents regressions
  • ✅ CI/CD integration
  • ✅ Component-level testing
  • ✅ Educates developers
  • ⚠️ Can't replace manual testing
  • ⚠️ Misses keyboard/SR issues

Axe-core Rule Categories

Category Example Rules
Color Contrast color-contrast
Forms label, input-button-name
Images image-alt, object-alt
Keyboard tabindex, focus-order
ARIA aria-valid-attr, aria-roles
Best Practice: Run axe-core in unit tests, E2E tests, Storybook, and CI/CD. Catch issues before production. Used by Microsoft, Google, Netflix.

12.4 Focus Management Roving Tabindex

Pattern Use Case Behavior Implementation
Roving Tabindex Toolbars, menus, grids One tab stop, arrow keys navigate Only active item has tabindex="0"
Focus Trap Modals, dialogs Keep focus within container Cycle focus between first/last
Focus Management SPA navigation, dynamic content Move focus after actions Focus heading after navigation
Skip Links Bypass repetitive content Jump to main content Hidden link, visible on focus

Example: Focus Management Patterns

// Roving tabindex for toolbar
function Toolbar({ items }) {
  const [focusedIndex, setFocusedIndex] = useState(0);
  const itemRefs = useRef([]);
  
  const handleKeyDown = (e, index) => {
    let newIndex = index;
    
    switch (e.key) {
      case 'ArrowRight':
        e.preventDefault();
        newIndex = (index + 1) % items.length;
        break;
      case 'ArrowLeft':
        e.preventDefault();
        newIndex = (index - 1 + items.length) % items.length;
        break;
      case 'Home':
        e.preventDefault();
        newIndex = 0;
        break;
      case 'End':
        e.preventDefault();
        newIndex = items.length - 1;
        break;
      default:
        return;
    }
    
    setFocusedIndex(newIndex);
    itemRefs.current[newIndex]?.focus();
  };
  
  return (
    <div role="toolbar" aria-label="Text formatting">
      {items.map((item, index) => (
        <button
          key={item.id}
          ref={(el) => (itemRefs.current[index] = el)}
          tabIndex={index === focusedIndex ? 0 : -1}
          onKeyDown={(e) => handleKeyDown(e, index)}
          onClick={item.onClick}
          aria-label={item.label}
        >
          {item.icon}
        </button>
      ))}
    </div>
  );
}

// Focus trap for modal
function useFocusTrap(ref) {
  useEffect(() => {
    if (!ref.current) return;
    
    const focusableElements = ref.current.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    const handleTab = (e) => {
      if (e.key !== 'Tab') return;
      
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          e.preventDefault();
          lastElement.focus();
        }
      } else {
        if (document.activeElement === lastElement) {
          e.preventDefault();
          firstElement.focus();
        }
      }
    };
    
    ref.current.addEventListener('keydown', handleTab);
    firstElement?.focus();
    
    return () => {
      ref.current?.removeEventListener('keydown', handleTab);
    };
  }, [ref]);
}

// Modal with focus trap
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);
  const previousFocus = useRef(null);
  
  useFocusTrap(modalRef);
  
  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
    } else if (previousFocus.current) {
      previousFocus.current.focus();
    }
  }, [isOpen]);
  
  if (!isOpen) return null;
  
  return (
    <div className="modal-backdrop">
      <div
        ref={modalRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
      >
        <h2 id="modal-title">Modal Title</h2>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

// Focus management for SPA navigation
function useRouteAnnouncement() {
  const location = useLocation();
  const [message, setMessage] = useState('');
  
  useEffect(() => {
    // Focus main heading after navigation
    const mainHeading = document.querySelector('h1');
    if (mainHeading) {
      mainHeading.setAttribute('tabindex', '-1');
      mainHeading.focus();
      mainHeading.removeAttribute('tabindex');
    }
    
    // Announce page change
    const pageName = document.title;
    setMessage(`Navigated to ${pageName}`);
  }, [location]);
  
  return (
    <div role="status" aria-live="polite" aria-atomic="true" className="sr-only">
      {message}
    </div>
  );
}

// Skip navigation link
<a href="#main-content" className="skip-link">
  Skip to main content
</a>

<header>
  <nav>{/* Navigation */}</nav>
</header>

<main id="main-content" tabIndex="-1">
  {/* Main content */}
</main>

// CSS for skip link
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
  text-decoration: none;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}

// Dropdown menu with roving tabindex
function DropdownMenu({ items }) {
  const [isOpen, setIsOpen] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(0);
  const itemRefs = useRef([]);
  
  const handleKeyDown = (e) => {
    if (!isOpen) {
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setIsOpen(true);
        setFocusedIndex(0);
      }
      return;
    }
    
    let newIndex = focusedIndex;
    
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        newIndex = (focusedIndex + 1) % items.length;
        break;
      case 'ArrowUp':
        e.preventDefault();
        newIndex = (focusedIndex - 1 + items.length) % items.length;
        break;
      case 'Home':
        e.preventDefault();
        newIndex = 0;
        break;
      case 'End':
        e.preventDefault();
        newIndex = items.length - 1;
        break;
      case 'Escape':
        e.preventDefault();
        setIsOpen(false);
        return;
      case 'Enter':
      case ' ':
        e.preventDefault();
        items[focusedIndex].onClick();
        setIsOpen(false);
        return;
      default:
        return;
    }
    
    setFocusedIndex(newIndex);
    itemRefs.current[newIndex]?.focus();
  };
  
  return (
    <div>
      <button
        aria-haspopup="true"
        aria-expanded={isOpen}
        onClick={() => setIsOpen(!isOpen)}
        onKeyDown={handleKeyDown}
      >
        Menu
      </button>
      {isOpen && (
        <ul role="menu">
          {items.map((item, index) => (
            <li
              key={item.id}
              role="menuitem"
              ref={(el) => (itemRefs.current[index] = el)}
              tabIndex={index === focusedIndex ? 0 : -1}
              onKeyDown={handleKeyDown}
              onClick={item.onClick}
            >
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// Focus management after delete action
function deleteItem(id) {
  // Delete item
  api.deleteItem(id);
  
  // Focus next item or previous if last
  const nextItem = document.querySelector(`[data-id="${id}"]`)?.nextElementSibling;
  const prevItem = document.querySelector(`[data-id="${id}"]`)?.previousElementSibling;
  
  if (nextItem) {
    nextItem.focus();
  } else if (prevItem) {
    prevItem.focus();
  } else {
    // No items left, focus add button
    document.querySelector('.add-button')?.focus();
  }
}

// Grid navigation (2D roving tabindex)
function Grid({ rows, cols }) {
  const [focusedCell, setFocusedCell] = useState({ row: 0, col: 0 });
  const cellRefs = useRef({});
  
  const handleKeyDown = (e, row, col) => {
    let newRow = row;
    let newCol = col;
    
    switch (e.key) {
      case 'ArrowRight':
        newCol = Math.min(col + 1, cols - 1);
        break;
      case 'ArrowLeft':
        newCol = Math.max(col - 1, 0);
        break;
      case 'ArrowDown':
        newRow = Math.min(row + 1, rows - 1);
        break;
      case 'ArrowUp':
        newRow = Math.max(row - 1, 0);
        break;
      case 'Home':
        if (e.ctrlKey) {
          newRow = 0;
          newCol = 0;
        } else {
          newCol = 0;
        }
        break;
      case 'End':
        if (e.ctrlKey) {
          newRow = rows - 1;
          newCol = cols - 1;
        } else {
          newCol = cols - 1;
        }
        break;
      default:
        return;
    }
    
    e.preventDefault();
    setFocusedCell({ row: newRow, col: newCol });
    cellRefs.current[`${newRow}-${newCol}`]?.focus();
  };
  
  return (
    <div role="grid">
      {Array.from({ length: rows }).map((_, row) => (
        <div key={row} role="row">
          {Array.from({ length: cols }).map((_, col) => (
            <div
              key={col}
              role="gridcell"
              ref={(el) => (cellRefs.current[`${row}-${col}`] = el)}
              tabIndex={focusedCell.row === row && focusedCell.col === col ? 0 : -1}
              onKeyDown={(e) => handleKeyDown(e, row, col)}
            >
              Cell {row},{col}
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

// Focus visible styles
button:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

// Remove default outline, add custom for focus-visible
button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.2);
}

Focus Management Patterns

Pattern Keys
Toolbar ←→ Home End
Menu ↑↓ Home End Enter Esc
Tabs ←→ Home End
Grid ←→↑↓ Home End Ctrl+Home/End
Tree ←→↑↓ Home End * Enter

Focus Management Checklist

ARIA Authoring Practices: Follow W3C ARIA patterns for standard widgets. Don't reinvent the wheel - use established patterns for menus, tabs, grids.

12.5 Color Contrast Checker WebAIM

Standard Ratio Use Case Example
WCAG AA (Normal) 4.5:1 minimum Body text, small text #767676 on white = 4.54:1 ✅
WCAG AA (Large) 3:1 minimum 18pt+ or 14pt+ bold #959595 on white = 3.02:1 ✅
WCAG AAA (Normal) 7:1 minimum Enhanced contrast #595959 on white = 7.01:1 ✅
UI Components 3:1 minimum Buttons, icons, borders #767676 border = 4.54:1 ✅

Example: Color Contrast Implementation

// Color contrast calculation
function getContrastRatio(color1, color2) {
  const l1 = getRelativeLuminance(color1);
  const l2 = getRelativeLuminance(color2);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

function getRelativeLuminance(color) {
  const rgb = hexToRgb(color);
  const [r, g, b] = rgb.map((val) => {
    val = val / 255;
    return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

// Check if contrast meets WCAG AA
function meetsWCAGAA(foreground, background, isLarge = false) {
  const ratio = getContrastRatio(foreground, background);
  const threshold = isLarge ? 3 : 4.5;
  return ratio >= threshold;
}

// Example usage
console.log(meetsWCAGAA('#767676', '#FFFFFF')); // true (4.54:1)
console.log(meetsWCAGAA('#999999', '#FFFFFF')); // false (2.85:1)

// Accessible color palette
const colors = {
  // Text on white background
  textPrimary: '#212529',    // 16.1:1 ✅
  textSecondary: '#6c757d',  // 4.69:1 ✅
  textDisabled: '#adb5bd',   // 2.73:1 ❌ (decorative only)
  
  // Backgrounds for white text
  primaryBg: '#0066cc',      // 4.5:1 ✅
  successBg: '#28a745',      // 3.04:1 ✅ (large text)
  dangerBg: '#dc3545',       // 4.5:1 ✅
  
  // UI components
  border: '#dee2e6',         // 1.5:1 (not text, OK for border)
  iconActive: '#495057'      // 9.1:1 ✅
};

// CSS with good contrast
:root {
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --border: #dee2e6;
  --link: #0066cc;
  --link-hover: #004499;
}

body {
  color: var(--text-primary);
  background: var(--bg-primary);
}

a {
  color: var(--link);
}

a:hover {
  color: var(--link-hover);
}

// Dark mode with good contrast
[data-theme="dark"] {
  --text-primary: #f8f9fa;     // 16.1:1 on dark
  --text-secondary: #adb5bd;   // 6.8:1 on dark
  --bg-primary: #212529;
  --bg-secondary: #343a40;
  --border: #495057;
  --link: #66b3ff;             // 5.2:1 on dark
}

// Contrast-safe color function
function getAccessibleColor(background) {
  const lightText = '#ffffff';
  const darkText = '#212529';
  
  const lightRatio = getContrastRatio(lightText, background);
  const darkRatio = getContrastRatio(darkText, background);
  
  return lightRatio >= 4.5 ? lightText : darkText;
}

// Auto-adjust text color based on background
function Button({ backgroundColor, children }) {
  const textColor = getAccessibleColor(backgroundColor);
  
  return (
    <button style={{ backgroundColor, color: textColor }}>
      {children}
    </button>
  );
}

// Common contrast failures
// ❌ Gray text on white background
.text-muted {
  color: #999999; // 2.85:1 - FAILS
}

// ✅ Fix: Use darker gray
.text-muted {
  color: #6c757d; // 4.69:1 - PASSES
}

// ❌ Light blue link on white
a {
  color: #66b3ff; // 3.2:1 - FAILS
}

// ✅ Fix: Use darker blue
a {
  color: #0066cc; // 4.5:1 - PASSES
}

// ❌ Yellow warning on white
.warning {
  background: #ffc107; // 1.8:1 with white text - FAILS
  color: white;
}

// ✅ Fix: Add dark text or darker background
.warning {
  background: #ffc107;
  color: #212529; // 8.4:1 - PASSES
}

// Testing tools integration
// Jest test for contrast
import { getContrastRatio } from './colorUtils';

test('primary button has sufficient contrast', () => {
  const bgColor = '#0066cc';
  const textColor = '#ffffff';
  const ratio = getContrastRatio(bgColor, textColor);
  expect(ratio).toBeGreaterThanOrEqual(4.5);
});

// Storybook addon for contrast checking
// Shows contrast ratio in panel
export default {
  title: 'Components/Button',
  component: Button,
  parameters: {
    a11y: {
      config: {
        rules: [
          {
            id: 'color-contrast',
            enabled: true
          }
        ]
      }
    }
  }
};

// Tailwind with accessible colors
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        // All colors meet WCAG AA with white text
        primary: {
          DEFAULT: '#0066cc', // 4.5:1
          dark: '#004499'     // 7.9:1
        },
        secondary: {
          DEFAULT: '#6c757d', // 4.69:1
          dark: '#495057'     // 9.1:1
        }
      }
    }
  }
};

// Design system with contrast tokens
const designTokens = {
  color: {
    text: {
      primary: { value: '#212529', contrast: '16.1:1' },
      secondary: { value: '#6c757d', contrast: '4.69:1' }
    },
    background: {
      primary: { value: '#ffffff' },
      secondary: { value: '#f8f9fa' }
    }
  }
};

// Browser DevTools contrast checker
// Chrome DevTools > Inspect element > Styles panel
// Shows contrast ratio with AA/AAA badges

// WebAIM Contrast Checker
// https://webaim.org/resources/contrastchecker/
// Enter foreground and background colors
// See ratio and pass/fail for WCAG AA/AAA

Contrast Quick Reference

Text Size WCAG AA WCAG AAA
<18pt normal 4.5:1 7:1
<14pt bold 4.5:1 7:1
≥18pt normal 3:1 4.5:1
≥14pt bold 3:1 4.5:1
UI components 3:1 N/A

Contrast Testing Tools

  • WebAIM: Online contrast checker
  • Chrome DevTools: Built-in inspector
  • Stark: Figma/Sketch plugin
  • Color Oracle: Colorblindness simulator
  • Lighthouse: Automated audit
  • axe DevTools: Browser extension
  • Contrast Ratio: lea.verou.me/contrast-ratio
Colorblindness: 8% of men, 0.5% of women have color vision deficiency. Don't rely on color alone - use icons, text, patterns.

12.6 ARIA Live Regions Dynamic Content

Attribute Behavior Interruption Use Case
aria-live="polite" Announce when idle No interruption Status updates, notifications
aria-live="assertive" Announce immediately Interrupts current speech Errors, warnings, urgent alerts
role="status" Polite live region No interruption Status messages (implicit aria-live="polite")
role="alert" Assertive live region Interrupts Important errors (implicit aria-live="assertive")

Example: ARIA Live Regions

// Basic live region for status updates
<div role="status" aria-live="polite" aria-atomic="true">
  {message}
</div>

// Alert for errors
<div role="alert" aria-live="assertive">
  {errorMessage}
</div>

// React hook for announcements
function useAnnouncer() {
  const [message, setMessage] = useState('');
  const [key, setKey] = useState(0);
  
  const announce = useCallback((text, priority = 'polite') => {
    setMessage('');
    setTimeout(() => {
      setMessage(text);
      setKey(k => k + 1);
    }, 100);
  }, []);
  
  return {
    announce,
    LiveRegion: ({ priority = 'polite' }) => (
      <div
        key={key}
        role={priority === 'assertive' ? 'alert' : 'status'}
        aria-live={priority}
        aria-atomic="true"
        className="sr-only"
      >
        {message}
      </div>
    )
  };
}

// Usage in component
function SearchResults() {
  const { announce, LiveRegion } = useAnnouncer();
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    if (results.length > 0) {
      announce(`${results.length} results found`);
    } else {
      announce('No results found');
    }
  }, [results, announce]);
  
  return (
    <>
      <LiveRegion />
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </>
  );
}

// Form validation with live region
function LoginForm() {
  const [errors, setErrors] = useState({});
  const { announce, LiveRegion } = useAnnouncer();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    const validationErrors = validate(formData);
    
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      announce(
        `Form has ${Object.keys(validationErrors).length} errors`,
        'assertive'
      );
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <LiveRegion priority="assertive" />
      {/* Form fields */}
    </form>
  );
}

// Loading state announcement
function DataTable() {
  const [loading, setLoading] = useState(false);
  const { announce, LiveRegion } = useAnnouncer();
  
  useEffect(() => {
    if (loading) {
      announce('Loading data, please wait');
    } else {
      announce('Data loaded');
    }
  }, [loading, announce]);
  
  return (
    <>
      <LiveRegion />
      {loading ? <Spinner aria-hidden="true" /> : <Table />}
    </>
  );
}

// Timer countdown announcement
function Countdown({ seconds }) {
  const { announce, LiveRegion } = useAnnouncer();
  const [remaining, setRemaining] = useState(seconds);
  
  useEffect(() => {
    if (remaining === 60 || remaining === 30 || remaining === 10) {
      announce(`${remaining} seconds remaining`);
    } else if (remaining === 0) {
      announce('Time is up', 'assertive');
    }
  }, [remaining, announce]);
  
  return (
    <>
      <LiveRegion />
      <div>{remaining} seconds</div>
    </>
  );
}

// Shopping cart update
function Cart({ items }) {
  const { announce, LiveRegion } = useAnnouncer();
  const prevItemCount = useRef(items.length);
  
  useEffect(() => {
    const currentCount = items.length;
    const prevCount = prevItemCount.current;
    
    if (currentCount > prevCount) {
      announce(`Item added to cart. ${currentCount} items in cart`);
    } else if (currentCount < prevCount) {
      announce(`Item removed from cart. ${currentCount} items in cart`);
    }
    
    prevItemCount.current = currentCount;
  }, [items, announce]);
  
  return (
    <>
      <LiveRegion />
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

// aria-atomic: Announce entire region or just changes
<div role="status" aria-live="polite" aria-atomic="true">
  {/* Entire content announced */}
  Score: {score}
</div>

<div role="status" aria-live="polite" aria-atomic="false">
  {/* Only changes announced */}
  Score: {score}
</div>

// aria-relevant: What changes to announce
<div
  role="log"
  aria-live="polite"
  aria-relevant="additions" {/* Only new items announced */}
>
  {messages.map(msg => (
    <div key={msg.id}>{msg.text}</div>
  ))}
</div>

// Possible values:
// - additions: New nodes
// - removals: Removed nodes
// - text: Text changes
// - all: All changes (default)

// Chat messages with live region
function Chat({ messages }) {
  return (
    <div
      role="log"
      aria-live="polite"
      aria-relevant="additions"
      aria-label="Chat messages"
    >
      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.author}:</strong> {msg.text}
        </div>
      ))}
    </div>
  );
}

// Progress bar with announcements
function ProgressBar({ value, max = 100 }) {
  const { announce, LiveRegion } = useAnnouncer();
  const prevValue = useRef(value);
  
  useEffect(() => {
    const percentage = Math.round((value / max) * 100);
    const prevPercentage = Math.round((prevValue.current / max) * 100);
    
    // Announce every 10%
    if (percentage % 10 === 0 && percentage !== prevPercentage) {
      announce(`${percentage}% complete`);
    }
    
    if (percentage === 100) {
      announce('Upload complete', 'assertive');
    }
    
    prevValue.current = value;
  }, [value, max, announce]);
  
  return (
    <>
      <LiveRegion />
      <div
        role="progressbar"
        aria-valuenow={value}
        aria-valuemin={0}
        aria-valuemax={max}
        aria-label="Upload progress"
      >
        <div style={{ width: `${(value / max) * 100}%` }} />
      </div>
    </>
  );
}

// Combobox with results announcement
function Autocomplete() {
  const [results, setResults] = useState([]);
  const { announce, LiveRegion } = useAnnouncer();
  
  useEffect(() => {
    if (results.length > 0) {
      announce(`${results.length} suggestions available`);
    }
  }, [results, announce]);
  
  return (
    <>
      <LiveRegion />
      <input
        role="combobox"
        aria-autocomplete="list"
        aria-controls="suggestions"
      />
      <ul id="suggestions" role="listbox">
        {results.map(result => (
          <li key={result.id} role="option">
            {result.label}
          </li>
        ))}
      </ul>
    </>
  );
}

Live Region Best Practices

  • ✅ Use role="status" for status updates
  • ✅ Use role="alert" for errors
  • ✅ Keep messages concise
  • ✅ Use aria-atomic="true" for full message
  • ✅ Debounce rapid updates
  • ✅ Don't overuse assertive
  • ⚠️ Live region must exist in DOM before updates
  • ⚠️ Empty and refill to re-announce same message

Common Use Cases

  • Form errors: role="alert"
  • Loading states: role="status"
  • Search results: role="status"
  • Cart updates: role="status"
  • Chat messages: role="log"
  • Timer alerts: role="timer"
  • Progress updates: progressbar + status

Accessibility Implementation Summary

  • WCAG 2.1 AA: Legal requirement in US/EU. 50 criteria covering images, contrast, keyboard, forms, focus, labels
  • Screen Readers: Test with NVDA (Windows), VoiceOver (Mac/iOS). 70%+ users use these. Use semantic HTML and ARIA
  • Automated Testing: axe-core catches ~57% of issues. Integrate with Jest, Cypress, Storybook, CI/CD
  • Focus Management: Roving tabindex for toolbars/menus, focus trap in modals, restore focus after actions
  • Color Contrast: 4.5:1 for normal text, 3:1 for large text and UI. Use WebAIM checker, Chrome DevTools
  • Live Regions: aria-live="polite" for status, "assertive" for errors. Announce dynamic content for screen readers
Legal Risk: Over 10,000 ADA lawsuits filed in 2023. Fines up to $75,000. Make accessibility a priority from day one, not an afterthought.

13. Error Handling Resilience Implementation

13.1 React Error Boundaries Fallback UI

Concept Scope Catches Doesn't Catch
Error Boundary Component tree below it Render errors, lifecycle methods, constructors Event handlers, async code, SSR, errors in boundary itself
componentDidCatch Class component method Error object, error info with stack Used for side effects (logging)
getDerivedStateFromError Static lifecycle method Returns state to render fallback Pure function, no side effects
react-error-boundary BEST Third-party library Hooks support, reset, FallbackComponent More features than built-in

Example: Error Boundaries Implementation

// Basic Error Boundary (class component)
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    // Update state to render fallback UI
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Log error to service
    console.error('Error caught by boundary:', error, errorInfo);
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// react-error-boundary library (recommended)
npm install react-error-boundary

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>Something went wrong</h2>
      <pre style={{ color: 'red' }}>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // Reset app state
        window.location.reload();
      }}
      onError={(error, errorInfo) => {
        // Log to error reporting service
        logErrorToSentry(error, errorInfo);
      }}
    >
      <MyApp />
    </ErrorBoundary>
  );
}

// Multiple error boundaries (granular)
function App() {
  return (
    <ErrorBoundary FallbackComponent={AppErrorFallback}>
      <Header />
      
      <ErrorBoundary FallbackComponent={SidebarErrorFallback}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={MainErrorFallback}>
        <MainContent />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

// Error boundary with reset keys
function UserProfile({ userId }) {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      resetKeys={[userId]} // Reset when userId changes
    >
      <UserData userId={userId} />
    </ErrorBoundary>
  );
}

// Custom error boundary hook
function useErrorHandler() {
  const [error, setError] = useState(null);
  
  useEffect(() => {
    if (error) {
      throw error; // Will be caught by Error Boundary
    }
  }, [error]);
  
  return setError;
}

// Usage in async code
function MyComponent() {
  const handleError = useErrorHandler();
  
  const fetchData = async () => {
    try {
      const data = await api.getData();
      setData(data);
    } catch (error) {
      handleError(error); // Throw to Error Boundary
    }
  };
  
  return <button onClick={fetchData}>Fetch Data</button>;
}

// Error boundary with different fallbacks per error type
function ErrorFallback({ error, resetErrorBoundary }) {
  if (error.name === 'ChunkLoadError') {
    return (
      <div>
        <h2>New version available</h2>
        <button onClick={() => window.location.reload()}>
          Reload to update
        </button>
      </div>
    );
  }
  
  if (error.message.includes('Network')) {
    return (
      <div>
        <h2>Network Error</h2>
        <p>Check your internet connection</p>
        <button onClick={resetErrorBoundary}>Retry</button>
      </div>
    );
  }
  
  return (
    <div>
      <h2>Application Error</h2>
      <details>
        <summary>Error details</summary>
        <pre>{error.stack}</pre>
      </details>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

// Nested error boundaries with context
const ErrorContext = createContext();

function RootErrorBoundary({ children }) {
  const [errors, setErrors] = useState([]);
  
  const logError = (error, errorInfo) => {
    setErrors(prev => [...prev, { error, errorInfo, timestamp: Date.now() }]);
  };
  
  return (
    <ErrorContext.Provider value={{ errors, logError }}>
      <ErrorBoundary
        FallbackComponent={RootErrorFallback}
        onError={logError}
      >
        {children}
      </ErrorBoundary>
    </ErrorContext.Provider>
  );
}

// Testing error boundaries
// MyComponent.test.tsx
import { render, screen } from '@testing-library/react';
import { ErrorBoundary } from 'react-error-boundary';

const ThrowError = () => {
  throw new Error('Test error');
};

test('shows error fallback', () => {
  const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
  
  render(
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <ThrowError />
    </ErrorBoundary>
  );
  
  expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
  
  consoleSpy.mockRestore();
});

// Global error handler for unhandled errors
window.addEventListener('error', (event) => {
  console.error('Unhandled error:', event.error);
  logErrorToService(event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  logErrorToService(event.reason);
});

// Suspense with Error Boundary
function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Suspense fallback={<Loading />}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

// Error boundary for specific routes (React Router)
function AppRoutes() {
  return (
    <Routes>
      <Route
        path="/"
        element={
          <ErrorBoundary FallbackComponent={HomeErrorFallback}>
            <Home />
          </ErrorBoundary>
        }
      />
      <Route
        path="/dashboard"
        element={
          <ErrorBoundary FallbackComponent={DashboardErrorFallback}>
            <Dashboard />
          </ErrorBoundary>
        }
      />
    </Routes>
  );
}

Error Boundary Placement

  • Root: Catch all app errors
  • Route: Per-route error handling
  • Component: Isolate feature errors
  • Widget: Third-party widget failures
  • Lazy: Code splitting errors
  • Form: Form submission errors

Error Boundary Checklist

Limitation: Error boundaries don't catch errors in event handlers, async code, SSR, or the boundary itself. Use try-catch for those.

13.2 Sentry LogRocket Error Monitoring

Tool Type Features Use Case
Sentry BEST Error tracking Stack traces, breadcrumbs, releases, performance Real-time error monitoring, source maps, alerts
LogRocket Session replay Video replay, console logs, network, Redux Debug user sessions, understand context
Rollbar Error tracking Real-time alerts, telemetry, people tracking Similar to Sentry, alternative option
Bugsnag Error monitoring Stability scoring, release health Mobile + web error tracking

Example: Error Monitoring Setup

// Sentry setup
npm install @sentry/react @sentry/tracing

// index.tsx
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  integrations: [
    new BrowserTracing(),
    new Sentry.Replay({
      maskAllText: false,
      blockAllMedia: false,
    })
  ],
  
  // Performance monitoring
  tracesSampleRate: 1.0, // 100% in dev, 0.1 (10%) in prod
  
  // Session replay
  replaysSessionSampleRate: 0.1, // 10% of sessions
  replaysOnErrorSampleRate: 1.0, // 100% of errors
  
  // Release tracking
  release: process.env.REACT_APP_VERSION,
  
  // Filter errors
  beforeSend(event, hint) {
    // Don't send to Sentry in dev
    if (process.env.NODE_ENV === 'development') {
      return null;
    }
    
    // Filter out specific errors
    if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
      return null;
    }
    
    return event;
  }
});

// Wrap app with Sentry
const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
    <App />
  </Sentry.ErrorBoundary>
);

// React Router integration
import {
  createRoutesFromChildren,
  matchRoutes,
  useLocation,
  useNavigationType,
} from 'react-router-dom';

Sentry.init({
  integrations: [
    new BrowserTracing({
      routingInstrumentation: Sentry.reactRouterV6Instrumentation(
        React.useEffect,
        useLocation,
        useNavigationType,
        createRoutesFromChildren,
        matchRoutes
      )
    })
  ]
});

// Manual error reporting
try {
  somethingThatMightFail();
} catch (error) {
  Sentry.captureException(error);
}

// Add context to errors
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

Sentry.setTag('page_locale', 'en-us');
Sentry.setContext('character', {
  name: 'Mighty Fighter',
  level: 19,
  gold: 12300
});

// Breadcrumbs (automatic + manual)
Sentry.addBreadcrumb({
  category: 'auth',
  message: 'User logged in',
  level: 'info'
});

// Capture custom messages
Sentry.captureMessage('Something important happened', 'warning');

// Performance monitoring
const transaction = Sentry.startTransaction({
  name: 'API Request',
  op: 'http.client'
});

fetch('/api/data')
  .then(response => response.json())
  .finally(() => transaction.finish());

// React profiler integration
import { Profiler } from '@sentry/react';

function App() {
  return (
    <Profiler name="App">
      <MyComponent />
    </Profiler>
  );
}

// LogRocket setup
npm install logrocket logrocket-react

import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

LogRocket.init('YOUR_APP_ID', {
  console: {
    shouldAggregateConsoleErrors: true
  },
  network: {
    requestSanitizer: (request) => {
      // Remove sensitive data
      if (request.headers['Authorization']) {
        request.headers['Authorization'] = '[REDACTED]';
      }
      return request;
    }
  },
  dom: {
    inputSanitizer: true // Mask input values
  }
});

setupLogRocketReact(LogRocket);

// Identify user
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: 'pro'
});

// Track custom events
LogRocket.track('Checkout Completed', {
  orderId: '12345',
  total: 99.99
});

// Sentry + LogRocket integration
LogRocket.getSessionURL((sessionURL) => {
  Sentry.configureScope((scope) => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Add LogRocket to Sentry events
Sentry.init({
  beforeSend(event) {
    if (event.exception) {
      const sessionURL = LogRocket.sessionURL;
      event.extra = {
        ...event.extra,
        LogRocket: sessionURL
      };
    }
    return event;
  }
});

// Redux integration
import LogRocket from 'logrocket';

const store = createStore(
  reducer,
  applyMiddleware(LogRocket.reduxMiddleware())
);

// Source maps for Sentry
// package.json
{
  "scripts": {
    "build": "react-scripts build && sentry-cli releases files upload-sourcemaps ./build"
  }
}

// .sentryclirc
[defaults]
url=https://sentry.io/
org=your-org
project=your-project

[auth]
token=YOUR_AUTH_TOKEN

// Next.js Sentry config
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');

module.exports = withSentryConfig(
  {
    // Next.js config
  },
  {
    silent: true,
    org: 'your-org',
    project: 'your-project'
  }
);

// Error monitoring best practices
// 1. Set user context
Sentry.setUser({ id: user.id, email: user.email });

// 2. Add tags for filtering
Sentry.setTag('environment', 'production');
Sentry.setTag('feature', 'checkout');

// 3. Add breadcrumbs
Sentry.addBreadcrumb({
  message: 'Button clicked',
  category: 'ui.click',
  level: 'info'
});

// 4. Filter noise
beforeSend(event) {
  // Ignore known third-party errors
  if (event.exception?.values?.[0]?.value?.includes('third-party-script')) {
    return null;
  }
  return event;
}

// 5. Sample rates
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

// Custom error class
class ApplicationError extends Error {
  constructor(message, code, context) {
    super(message);
    this.name = 'ApplicationError';
    this.code = code;
    this.context = context;
  }
}

// Report with context
try {
  throw new ApplicationError('Payment failed', 'PAYMENT_ERROR', {
    userId: user.id,
    amount: 99.99
  });
} catch (error) {
  Sentry.captureException(error);
}

Sentry Benefits

  • ✅ Real-time error tracking
  • ✅ Stack traces with source maps
  • ✅ Breadcrumbs (user actions)
  • ✅ Release tracking
  • ✅ Performance monitoring
  • ✅ Alerts & notifications
  • ✅ User context & tags
  • ✅ Free tier: 5K errors/month

LogRocket Benefits

  • ✅ Session replay (video)
  • ✅ Console logs capture
  • ✅ Network requests/responses
  • ✅ Redux state timeline
  • ✅ User interactions
  • ✅ Performance metrics
  • ✅ Integration with Sentry
  • 💰 Paid ($99+/month)
Industry Standard: 85% of top companies use Sentry. 1M+ developers. Free tier covers small projects. Essential for production apps.

13.3 Try-Catch Async Error Handling

Pattern Use Case Pros Cons
Try-Catch Sync code, async/await Simple, catches synchronous errors Verbose, doesn't catch all async errors
Promise.catch() Promise chains Chainable, handles async rejections Can be verbose with multiple catches
Global handlers Unhandled errors/rejections Catches everything as last resort Should not be primary error handling
Error wrapper Consistent API error handling DRY, centralized logic Requires setup

Example: Async Error Handling Patterns

// Basic try-catch with async/await
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error; // Re-throw or handle
  }
}

// Promise chain with catch
function fetchUserData(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return response.json();
    })
    .catch(error => {
      console.error('Fetch failed:', error);
      throw error;
    });
}

// React component with error handling
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const data = await fetchUserData(userId);
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name}</div>;
}

// Custom error classes
class APIError extends Error {
  constructor(message, statusCode, response) {
    super(message);
    this.name = 'APIError';
    this.statusCode = statusCode;
    this.response = response;
  }
}

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ValidationError extends Error {
  constructor(message, fields) {
    super(message);
    this.name = 'ValidationError';
    this.fields = fields;
  }
}

// API wrapper with error handling
async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      throw new APIError(
        data.message || 'API request failed',
        response.status,
        data
      );
    }
    
    return data;
  } catch (error) {
    if (error instanceof APIError) {
      throw error;
    }
    
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      throw new NetworkError('Network connection failed');
    }
    
    throw error;
  }
}

// Usage with specific error handling
async function login(email, password) {
  try {
    const data = await apiRequest('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    });
    return data;
  } catch (error) {
    if (error instanceof APIError) {
      if (error.statusCode === 401) {
        throw new Error('Invalid credentials');
      }
      if (error.statusCode === 429) {
        throw new Error('Too many attempts. Try again later.');
      }
    }
    
    if (error instanceof NetworkError) {
      throw new Error('No internet connection');
    }
    
    throw new Error('Login failed. Please try again.');
  }
}

// React Query error handling
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data, error, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserData(userId),
    retry: (failureCount, error) => {
      // Don't retry on 404
      if (error.statusCode === 404) return false;
      return failureCount < 3;
    },
    onError: (error) => {
      console.error('Query failed:', error);
      toast.error(error.message);
    }
  });
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{data.name}</div>;
}

// Axios interceptor for error handling
import axios from 'axios';

const api = axios.create({
  baseURL: '/api'
});

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Redirect to login
      window.location.href = '/login';
    }
    
    if (error.response?.status === 403) {
      toast.error('Permission denied');
    }
    
    if (error.response?.status >= 500) {
      toast.error('Server error. Please try again.');
    }
    
    return Promise.reject(error);
  }
);

// Global error handlers
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error);
  
  // Report to monitoring service
  Sentry.captureException(event.error);
  
  // Show user-friendly message
  toast.error('Something went wrong');
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  
  Sentry.captureException(event.reason);
  toast.error('An unexpected error occurred');
});

// Async error wrapper utility
function asyncHandler(fn) {
  return async (...args) => {
    try {
      return await fn(...args);
    } catch (error) {
      console.error('Async handler caught error:', error);
      throw error;
    }
  };
}

// Usage
const safeFunction = asyncHandler(async (userId) => {
  const data = await fetchUserData(userId);
  return data;
});

// Error handling in event handlers
function MyComponent() {
  const handleClick = async () => {
    try {
      await performAction();
      toast.success('Action completed');
    } catch (error) {
      console.error('Action failed:', error);
      toast.error(error.message);
    }
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

// Parallel requests with error handling
async function fetchAllData() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetchUsers(),
      fetchPosts(),
      fetchComments()
    ]);
    
    return { users, posts, comments };
  } catch (error) {
    // If any request fails, all fail
    console.error('One or more requests failed:', error);
    throw error;
  }
}

// Parallel with individual error handling
async function fetchAllDataSafe() {
  const results = await Promise.allSettled([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
  
  const data = {
    users: results[0].status === 'fulfilled' ? results[0].value : [],
    posts: results[1].status === 'fulfilled' ? results[1].value : [],
    comments: results[2].status === 'fulfilled' ? results[2].value : []
  };
  
  const errors = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
  
  if (errors.length > 0) {
    console.error('Some requests failed:', errors);
  }
  
  return data;
}

// TypeScript error handling
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E };

async function safeFetch<T>(url: string): Promise<Result<T>> {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return { success: true, data };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

// Usage
const result = await safeFetch<User>('/api/user');
if (result.success) {
  console.log(result.data.name);
} else {
  console.error(result.error.message);
}

Error Handling Best Practices

  • ✅ Always handle async errors
  • ✅ Use try-catch with async/await
  • ✅ Create custom error classes
  • ✅ Centralize API error handling
  • ✅ Log errors to monitoring
  • ✅ Show user-friendly messages
  • ✅ Handle network errors separately
  • ✅ Use global handlers as fallback

Common Error Types

Error Cause
TypeError Undefined access, null reference
ReferenceError Variable not defined
SyntaxError Invalid code syntax
NetworkError Fetch failed, offline
APIError HTTP 4xx/5xx responses
Error Recovery: 80% of errors are recoverable with retry logic. Implement exponential backoff for transient failures.

13.4 Toast Notifications User Feedback

Library Bundle Size Features Use Case
react-hot-toast ~5KB Lightweight, customizable, promise API Modern, minimal, best DX
react-toastify ~15KB Feature-rich, progress bars, auto-close Most popular, full-featured
sonner HOT ~3KB Radix UI based, accessible, beautiful New, modern, best design
notistack ~20KB Material-UI integration, stacking MUI projects, advanced features

Example: Toast Notifications Implementation

// react-hot-toast
npm install react-hot-toast

import toast, { Toaster } from 'react-hot-toast';

function App() {
  return (
    <>
      <Toaster position="top-right" />
      <MyApp />
    </>
  );
}

// Basic usage
const notify = () => toast('Hello World');
const success = () => toast.success('Saved successfully');
const error = () => toast.error('Failed to save');
const loading = () => toast.loading('Loading...');

// Promise-based toast
const saveData = async () => {
  toast.promise(
    api.saveData(),
    {
      loading: 'Saving...',
      success: 'Saved successfully',
      error: 'Failed to save'
    }
  );
};

// Custom duration
toast.success('Saved', { duration: 5000 });

// Custom styling
toast.success('Success', {
  style: {
    background: '#28a745',
    color: '#fff'
  },
  icon: '✅'
});

// Dismissible toast with action
toast((t) => (
  <div>
    <p>Item deleted</p>
    <button onClick={() => {
      undoDelete();
      toast.dismiss(t.id);
    }}>
      Undo
    </button>
  </div>
), {
  duration: 5000
});

// Custom toast component
toast.custom((t) => (
  <div
    className={`custom-toast ${t.visible ? 'animate-enter' : 'animate-leave'}`}
  >
    Custom content
  </div>
));

// Sonner (modern, accessible)
npm install sonner

import { Toaster, toast } from 'sonner';

function App() {
  return (
    <>
      <Toaster position="bottom-right" richColors />
      <MyApp />
    </>
  );
}

// Usage
toast('Event created');
toast.success('Successfully saved');
toast.error('Something went wrong');
toast.warning('Be careful');
toast.info('Update available');

// With description
toast.success('Success', {
  description: 'Your changes have been saved'
});

// With action button
toast('Event created', {
  action: {
    label: 'Undo',
    onClick: () => undoAction()
  }
});

// Promise with loading state
toast.promise(fetchData(), {
  loading: 'Loading...',
  success: (data) => `${data.name} loaded`,
  error: 'Failed to load'
});

// react-toastify
npm install react-toastify

import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function App() {
  return (
    <>
      <ToastContainer
        position="top-right"
        autoClose={5000}
        hideProgressBar={false}
        newestOnTop
        closeOnClick
        pauseOnHover
      />
      <MyApp />
    </>
  );
}

// Usage
toast.success('Success message');
toast.error('Error message');
toast.warn('Warning message');
toast.info('Info message');

// With custom options
toast.success('Saved', {
  position: 'bottom-center',
  autoClose: 3000,
  hideProgressBar: true
});

// Update existing toast
const toastId = toast.loading('Uploading...');

// Later
toast.update(toastId, {
  render: 'Upload complete',
  type: 'success',
  isLoading: false,
  autoClose: 3000
});

// Custom component
const CustomToast = ({ closeToast, message }) => (
  <div>
    <p>{message}</p>
    <button onClick={closeToast}>Close</button>
  </div>
);

toast(<CustomToast message="Hello" />);

// Toast notification patterns
// Success toast after API call
const handleSave = async () => {
  try {
    await api.saveData(data);
    toast.success('Data saved successfully');
  } catch (error) {
    toast.error('Failed to save data');
  }
};

// Loading state with promise
const handleSubmit = async () => {
  const promise = api.submitForm(formData);
  
  toast.promise(promise, {
    loading: 'Submitting...',
    success: 'Form submitted successfully',
    error: (err) => `Error: ${err.message}`
  });
};

// Undo action toast
const handleDelete = (itemId) => {
  const deletedItem = items.find(i => i.id === itemId);
  
  // Optimistically remove
  setItems(items.filter(i => i.id !== itemId));
  
  // Show undo toast
  toast((t) => (
    <div>
      <span>Item deleted</span>
      <button
        onClick={() => {
          setItems([...items, deletedItem]);
          toast.dismiss(t.id);
        }}
      >
        Undo
      </button>
    </div>
  ), {
    duration: 5000
  });
  
  // Permanently delete after timeout
  setTimeout(() => {
    api.deleteItem(itemId);
  }, 5000);
};

// Multiple toasts with limit
const MAX_TOASTS = 3;

function showToast(message) {
  if (toast.visibleToasts().length >= MAX_TOASTS) {
    return;
  }
  toast(message);
}

// Toast with React Query
import { useMutation } from '@tanstack/react-query';

function MyComponent() {
  const mutation = useMutation({
    mutationFn: saveData,
    onSuccess: () => {
      toast.success('Saved successfully');
    },
    onError: (error) => {
      toast.error(error.message);
    }
  });
  
  return (
    <button onClick={() => mutation.mutate(data)}>
      Save
    </button>
  );
}

// Global toast wrapper
export const notify = {
  success: (message) => toast.success(message),
  error: (message) => toast.error(message),
  info: (message) => toast.info(message),
  warning: (message) => toast.warning(message),
  promise: (promise, messages) => toast.promise(promise, messages)
};

// Usage throughout app
import { notify } from './utils/toast';

notify.success('Operation completed');

// Toast accessibility
<Toaster
  position="top-right"
  toastOptions={{
    role: 'status',
    ariaLive: 'polite'
  }}
/>

// Custom toast with close button
toast.custom((t) => (
  <div role="alert" aria-live="assertive">
    <p>{message}</p>
    <button
      onClick={() => toast.dismiss(t.id)}
      aria-label="Close notification"
    >
      ×
    </button>
  </div>
));

Toast Best Practices

  • ✅ Use for temporary feedback
  • ✅ Auto-dismiss after 3-5 seconds
  • ✅ Success = green, Error = red
  • ✅ Allow manual dismiss
  • ✅ Limit to 3 toasts max
  • ✅ Position: top-right or bottom-center
  • ✅ Add undo for destructive actions
  • ⚠️ Don't use for critical errors

When to Use Toasts

Use Case Type
Form saved Success
Item deleted Success + Undo
API error Error
Network offline Warning
Update available Info
User Experience: Toast notifications provide non-intrusive feedback. Don't overuse - limit to 3 visible at once.

13.5 Retry Logic Exponential Backoff

Strategy Wait Time Use Case Example
Fixed Delay Same delay between retries Simple, non-critical operations 1s, 1s, 1s
Linear Backoff Linearly increasing delay Moderate load scenarios 1s, 2s, 3s
Exponential Backoff Exponentially increasing delay Rate-limited APIs, best practice 1s, 2s, 4s, 8s
Exponential + Jitter Exponential with randomization Prevent thundering herd, production 1s, 2.3s, 4.7s

Example: Retry Logic Implementation

// Simple retry function
async function retry(fn, maxAttempts = 3, delay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const data = await retry(() => fetchData(), 3, 1000);

// Exponential backoff
async function retryWithExponentialBackoff(
  fn,
  maxAttempts = 5,
  baseDelay = 1000,
  maxDelay = 30000
) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      // Exponential: 1s, 2s, 4s, 8s, 16s
      const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
      
      console.log(`Attempt ${attempt} failed, waiting ${delay}ms before retry`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Exponential backoff with jitter (prevents thundering herd)
function calculateBackoffWithJitter(attempt, baseDelay = 1000, maxDelay = 30000) {
  const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
  const jitter = Math.random() * exponentialDelay * 0.3; // ±30% jitter
  return exponentialDelay + jitter;
}

async function retryWithJitter(fn, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      const delay = calculateBackoffWithJitter(attempt);
      console.log(`Retry in ${Math.round(delay)}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Conditional retry (only for specific errors)
async function retryOnError(fn, shouldRetry, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts || !shouldRetry(error)) {
        throw error;
      }
      
      const delay = 1000 * Math.pow(2, attempt - 1);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage: Only retry on network errors
const data = await retryOnError(
  fetchData,
  (error) => error.name === 'NetworkError' || error.statusCode >= 500,
  3
);

// React Query with retry
import { useQuery } from '@tanstack/react-query';

function MyComponent() {
  const { data, error, isLoading } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
  });
}

// Axios with retry interceptor
npm install axios-retry

import axios from 'axios';
import axiosRetry from 'axios-retry';

const api = axios.create({
  baseURL: '/api'
});

axiosRetry(api, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
           error.response?.status === 429; // Rate limit
  }
});

// SWR with retry
import useSWR from 'swr';

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher, {
    onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
      // Don't retry on 404
      if (error.status === 404) return;
      
      // Max 5 retries
      if (retryCount >= 5) return;
      
      // Exponential backoff
      setTimeout(() => revalidate({ retryCount }), 1000 * Math.pow(2, retryCount));
    }
  });
}

// Fetch with retry wrapper
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const { retry = maxRetries, retryDelay = 1000, ...fetchOptions } = options;
  
  for (let attempt = 0; attempt <= retry; attempt++) {
    try {
      const response = await fetch(url, fetchOptions);
      
      // Retry on 5xx errors
      if (response.status >= 500 && attempt < retry) {
        throw new Error(`Server error: ${response.status}`);
      }
      
      return response;
    } catch (error) {
      if (attempt === retry) {
        throw error;
      }
      
      const delay = retryDelay * Math.pow(2, attempt);
      console.log(`Retry ${attempt + 1}/${retry} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const response = await fetchWithRetry('/api/data', {
  retry: 5,
  retryDelay: 1000
});

// Retry with abort controller (cancellable)
async function retryWithAbort(fn, signal, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    if (signal?.aborted) {
      throw new Error('Request aborted');
    }
    
    try {
      return await fn(signal);
    } catch (error) {
      if (attempt === maxAttempts || signal?.aborted) {
        throw error;
      }
      
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

// Usage
const controller = new AbortController();

try {
  const data = await retryWithAbort(
    (signal) => fetch('/api/data', { signal }),
    controller.signal,
    3
  );
} catch (error) {
  console.error('Failed after retries:', error);
}

// Cancel if needed
controller.abort();

// React hook for retry
function useRetry(fn, options = {}) {
  const { maxAttempts = 3, baseDelay = 1000 } = options;
  const [attempt, setAttempt] = useState(0);
  const [isRetrying, setIsRetrying] = useState(false);
  
  const execute = async () => {
    for (let i = 1; i <= maxAttempts; i++) {
      try {
        setAttempt(i);
        setIsRetrying(i > 1);
        
        const result = await fn();
        setIsRetrying(false);
        return result;
      } catch (error) {
        if (i === maxAttempts) {
          setIsRetrying(false);
          throw error;
        }
        
        const delay = baseDelay * Math.pow(2, i - 1);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  };
  
  return { execute, attempt, isRetrying };
}

// Usage
function MyComponent() {
  const { execute, attempt, isRetrying } = useRetry(fetchData, {
    maxAttempts: 5,
    baseDelay: 1000
  });
  
  const handleClick = async () => {
    try {
      const data = await execute();
      console.log('Success:', data);
    } catch (error) {
      console.error('Failed after retries:', error);
    }
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={isRetrying}>
        Fetch Data
      </button>
      {isRetrying && <p>Retrying... (Attempt {attempt})</p>}
    </div>
  );
}

// Retry with progress callback
async function retryWithProgress(fn, onProgress, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      onProgress({ attempt, status: 'trying' });
      const result = await fn();
      onProgress({ attempt, status: 'success' });
      return result;
    } catch (error) {
      onProgress({ attempt, status: 'failed', error });
      
      if (attempt === maxAttempts) {
        throw error;
      }
      
      const delay = 1000 * Math.pow(2, attempt - 1);
      onProgress({ attempt, status: 'waiting', delay });
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Retry Strategy Comparison

Attempt Fixed Exponential
1 1s 1s
2 1s 2s
3 1s 4s
4 1s 8s
5 1s 16s

When to Retry

  • ✅ Network errors (timeout, connection)
  • ✅ 5xx server errors (500-599)
  • ✅ 429 rate limit exceeded
  • ✅ 503 service unavailable
  • ❌ 4xx client errors (except 429)
  • ❌ 401 unauthorized
  • ❌ 404 not found
  • ❌ 400 bad request
AWS Best Practice: Use exponential backoff with jitter to prevent thundering herd. AWS SDKs implement this by default.

13.6 Circuit Breaker Pattern Frontend

State Behavior Transition Purpose
Closed Normal operation, requests pass through Open after N failures Allow traffic when healthy
Open Fail fast, reject requests immediately Half-Open after timeout Prevent cascading failures
Half-Open Test with limited requests Closed on success, Open on failure Gradual recovery testing

Example: Circuit Breaker Implementation

// Circuit breaker class
class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.successThreshold = options.successThreshold || 2;
    this.timeout = options.timeout || 60000; // 60s
    
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    this.nextAttempt = Date.now();
  }
  
  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      
      // Try half-open
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      
      if (this.successCount >= this.successThreshold) {
        this.state = 'CLOSED';
        this.successCount = 0;
      }
    }
  }
  
  onFailure() {
    this.failureCount++;
    this.successCount = 0;
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
  
  getState() {
    return this.state;
  }
  
  reset() {
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
  }
}

// Usage
const breaker = new CircuitBreaker({
  failureThreshold: 5,
  successThreshold: 2,
  timeout: 60000
});

async function fetchData() {
  try {
    return await breaker.execute(() => fetch('/api/data'));
  } catch (error) {
    if (error.message === 'Circuit breaker is OPEN') {
      // Use cached data or show error
      return getCachedData();
    }
    throw error;
  }
}

// React hook for circuit breaker
function useCircuitBreaker(options) {
  const breakerRef = useRef(new CircuitBreaker(options));
  const [state, setState] = useState('CLOSED');
  
  const execute = useCallback(async (fn) => {
    try {
      const result = await breakerRef.current.execute(fn);
      setState(breakerRef.current.getState());
      return result;
    } catch (error) {
      setState(breakerRef.current.getState());
      throw error;
    }
  }, []);
  
  const reset = useCallback(() => {
    breakerRef.current.reset();
    setState('CLOSED');
  }, []);
  
  return { execute, state, reset };
}

// Usage in component
function DataFetcher() {
  const { execute, state, reset } = useCircuitBreaker({
    failureThreshold: 3,
    timeout: 30000
  });
  
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  const fetchData = async () => {
    try {
      const result = await execute(() => api.getData());
      setData(result);
      setError(null);
    } catch (err) {
      setError(err.message);
    }
  };
  
  return (
    <div>
      <p>Circuit State: {state}</p>
      
      {state === 'OPEN' && (
        <div>
          <p>Service unavailable. Using cached data.</p>
          <button onClick={reset}>Reset Circuit</button>
        </div>
      )}
      
      <button onClick={fetchData} disabled={state === 'OPEN'}>
        Fetch Data
      </button>
      
      {error && <p>Error: {error}</p>}
    </div>
  );
}

// Circuit breaker with fallback
class CircuitBreakerWithFallback extends CircuitBreaker {
  constructor(options = {}) {
    super(options);
    this.fallback = options.fallback || (() => null);
  }
  
  async execute(fn) {
    try {
      return await super.execute(fn);
    } catch (error) {
      if (this.state === 'OPEN') {
        return this.fallback();
      }
      throw error;
    }
  }
}

// Usage with fallback
const breaker = new CircuitBreakerWithFallback({
  failureThreshold: 5,
  timeout: 60000,
  fallback: () => getCachedData()
});

// API service with circuit breaker
class APIService {
  constructor() {
    this.breakers = new Map();
  }
  
  getBreaker(endpoint) {
    if (!this.breakers.has(endpoint)) {
      this.breakers.set(endpoint, new CircuitBreaker({
        failureThreshold: 3,
        timeout: 30000
      }));
    }
    return this.breakers.get(endpoint);
  }
  
  async request(endpoint, options) {
    const breaker = this.getBreaker(endpoint);
    
    return breaker.execute(async () => {
      const response = await fetch(endpoint, options);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return response.json();
    });
  }
}

// Usage
const api = new APIService();

try {
  const data = await api.request('/api/users');
} catch (error) {
  console.error('Request failed:', error);
}

// Circuit breaker with metrics
class CircuitBreakerWithMetrics extends CircuitBreaker {
  constructor(options = {}) {
    super(options);
    this.metrics = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      rejectedRequests: 0
    };
  }
  
  async execute(fn) {
    this.metrics.totalRequests++;
    
    if (this.state === 'OPEN' && Date.now() < this.nextAttempt) {
      this.metrics.rejectedRequests++;
      throw new Error('Circuit breaker is OPEN');
    }
    
    try {
      const result = await super.execute(fn);
      this.metrics.successfulRequests++;
      return result;
    } catch (error) {
      this.metrics.failedRequests++;
      throw error;
    }
  }
  
  getMetrics() {
    return {
      ...this.metrics,
      successRate: this.metrics.successfulRequests / this.metrics.totalRequests
    };
  }
}

// Monitoring dashboard
function CircuitBreakerDashboard({ breaker }) {
  const [metrics, setMetrics] = useState(breaker.getMetrics());
  
  useEffect(() => {
    const interval = setInterval(() => {
      setMetrics(breaker.getMetrics());
    }, 1000);
    
    return () => clearInterval(interval);
  }, [breaker]);
  
  return (
    <div>
      <h3>Circuit Breaker Status</h3>
      <p>State: {breaker.getState()}</p>
      <p>Total Requests: {metrics.totalRequests}</p>
      <p>Success Rate: {(metrics.successRate * 100).toFixed(2)}%</p>
      <p>Rejected: {metrics.rejectedRequests}</p>
    </div>
  );
}

// Circuit breaker pattern with multiple services
const breakers = {
  userService: new CircuitBreaker({ failureThreshold: 3 }),
  paymentService: new CircuitBreaker({ failureThreshold: 5 }),
  notificationService: new CircuitBreaker({ failureThreshold: 2 })
};

async function fetchUser(id) {
  return breakers.userService.execute(() => api.getUser(id));
}

async function processPayment(data) {
  return breakers.paymentService.execute(() => api.processPayment(data));
}

// Global circuit breaker state management
const CircuitBreakerContext = createContext();

function CircuitBreakerProvider({ children }) {
  const [breakers] = useState(() => ({
    api: new CircuitBreakerWithMetrics({ failureThreshold: 5 })
  }));
  
  return (
    <CircuitBreakerContext.Provider value={breakers}>
      {children}
    </CircuitBreakerContext.Provider>
  );
}

function useCircuitBreakerAPI() {
  const breakers = useContext(CircuitBreakerContext);
  return breakers.api;
}

Circuit Breaker States

State Action
Closed Allow all requests
Open Reject all requests
Half-Open Test with limited requests

Circuit Breaker Benefits

  • ✅ Prevent cascading failures
  • ✅ Fail fast instead of waiting
  • ✅ Give services time to recover
  • ✅ Improve user experience
  • ✅ Reduce server load
  • ✅ Enable graceful degradation

Error Handling & Resilience Summary

  • Error Boundaries: Catch React render errors. Use react-error-boundary library. Granular boundaries per feature. Don't catch async/event errors
  • Monitoring: Sentry for error tracking (5K free/month). LogRocket for session replay. Source maps for production debugging
  • Try-Catch: Wrap async/await, create custom error classes, centralize API errors, use global handlers as fallback
  • Toast Notifications: react-hot-toast (5KB) or Sonner (3KB). Auto-dismiss 3-5s. Limit to 3 toasts. Add undo for destructive actions
  • Retry Logic: Exponential backoff with jitter (1s, 2s, 4s, 8s). Retry 5xx errors, network failures, 429 rate limits. Don't retry 4xx
  • Circuit Breaker: Fail fast when service down. 3 states: Closed→Open→Half-Open. Prevent cascading failures, enable fallbacks
Production Ready: Implement all 6 patterns for production apps. 99.9% uptime requires resilience. Users expect 200ms response or fallback.

14. Modern Deployment Delivery Implementation

14.1 Vercel Netlify Jamstack Deployment

Platform Features Configuration Best Practices
Vercel NEW Zero-config, Edge Functions, Preview URLs, Analytics, DDoS protection, 100+ global edge vercel.json: builds, routes, headers, redirects, env vars. Auto-detect framework Optimized for Next.js. Free 100GB bandwidth. Use Edge Config for feature flags. ISR for dynamic
Netlify Edge Functions, Split Testing, Form handling, Identity auth, Deploy previews, Rollbacks netlify.toml: build command, publish dir, redirects, headers, plugins, functions Free 100GB bandwidth. Use Netlify Functions for serverless. Deploy contexts (prod/preview)
Jamstack Architecture Pre-rendered HTML, API-driven, Git-based workflow, CDN distribution, Decoupled frontend SSG with Next/Gatsby/Astro. APIs for dynamic data. Headless CMS. Build-time data fetching 99.99% uptime. 10x faster than SSR. SEO-optimized. Use webhooks for content updates
Build Optimization Incremental builds, Build cache, Parallel builds, Dependency caching, On-demand ISR Cache node_modules, .next, .cache. Turbopack (Vercel). Use buildCommand Reduce build time 50-80%. Cache restoration 10-30s. Parallel builds for monorepos
Deploy Previews PR preview URLs, Branch deploys, Unique URLs per commit, Shareable links, E2E testing Auto-deploy on PR. Comment preview URL. Delete on merge. Use for QA testing Test before production. Share with stakeholders. Run E2E tests on preview. Instant rollback
Edge Functions Serverless at edge, Low latency, Auto-scaling, A/B testing, Geo-location, Middleware Vercel Edge: /api/edge.ts. Netlify Edge: netlify/edge-functions/ Sub-10ms latency. Use for auth, redirects, A/B tests. 50ms execution limit. Lightweight only

Example: Vercel deployment configuration

// vercel.json
{
  "buildCommand": "pnpm build",
  "outputDirectory": "dist",
  "framework": "vite",
  "rewrites": [
    { "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-Content-Type-Options", "value": "nosniff" }
      ]
    }
  ],
  "env": {
    "API_URL": "@api_url_production"
  }
}

Example: Netlify configuration

# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"
  
[build.environment]
  NODE_VERSION = "20"
  NPM_FLAGS = "--legacy-peer-deps"

[[redirects]]
  from = "/api/*"
  to = "https://api.example.com/:splat"
  status = 200

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    
[[plugins]]
  package = "@netlify/plugin-lighthouse"

14.2 GitHub Actions CI/CD Pipeline

Component Purpose Configuration Best Practices
Workflow Triggers push, pull_request, schedule, workflow_dispatch, release, manual trigger on: [push, pull_request]. Filter branches: branches: [main] Trigger on PR for tests. Deploy on main push. Schedule nightly builds. Use paths filter
Build Job Install deps, lint, test, build, cache dependencies, parallel execution runs-on: ubuntu-latest. Use actions/setup-node@v4. Cache npm/pnpm Cache node_modules (5-10x faster). Use matrix for multi-version. Fail fast on errors
Testing Jobs Unit tests, integration tests, E2E tests, coverage, security scans, lighthouse Jest/Vitest for unit. Playwright for E2E. Upload coverage to Codecov. Parallel tests Run unit tests first (fast). E2E on preview deploy. 80%+ coverage. Security scan dependencies
Deploy Job Production deploy, staging deploy, preview environments, rollback capability needs: [build, test]. Use secrets for credentials. Conditional on branch Deploy after tests pass. Use environment protection rules. Auto-rollback on failure
Caching Strategy Dependencies, build artifacts, node_modules, .next cache, test cache actions/cache@v4 with key: ${{ runner.os }}-node-${{ hashFiles }} Cache node_modules (2-5 min → 30s). Restore build cache. Use restore-keys fallback
Security & Secrets Encrypted secrets, OIDC tokens, environment secrets, secret scanning, Dependabot Store in GitHub Secrets. Use ${{ secrets.API_KEY }}. Never log secrets Rotate secrets quarterly. Use OIDC instead of tokens. Enable secret scanning. Audit access

Example: Complete CI/CD pipeline

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      
      - name: Lint
        run: pnpm lint
      
      - name: Type check
        run: pnpm type-check
      
      - name: Unit tests
        run: pnpm test:unit --coverage
      
      - name: Build
        run: pnpm build
        
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/coverage-final.json

  e2e-tests:
    needs: build-and-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm playwright install --with-deps
      - run: pnpm test:e2e
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

  deploy:
    needs: [build-and-test, e2e-tests]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Vercel
        run: vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
        env:
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

14.3 Docker Container Frontend Apps

Aspect Implementation Configuration Best Practices
Multi-stage Build Build stage, Production stage, Nginx serving, Minimize image size, Security hardening Stage 1: node:20-alpine build. Stage 2: nginx:alpine-slim copy artifacts. 50-100MB final Use alpine images. Copy only dist/. Remove source maps. Non-root user. Scan vulnerabilities
Nginx Configuration Static file serving, SPA routing, Gzip compression, Cache headers, Security headers nginx.conf: try_files, gzip on, cache-control, add_header. Port 80/8080 Enable gzip (70% reduction). Cache static assets. SPA fallback index.html. HTTPS redirect
Build Optimization Layer caching, .dockerignore, Dependency caching, Parallel builds, Build args Copy package.json first. Install deps. Copy source. Build. Use BuildKit cache mounts Cache npm install layer. .dockerignore node_modules. Use --mount=type=cache. 10x faster
Environment Variables Runtime config, Build-time vars, ARG vs ENV, Config injection, 12-factor app ARG for build. ENV for runtime. Use window.__ENV__ or env.js. No secrets in image Inject at runtime (not build). Use ConfigMaps in K8s. Template env.js file. Never commit secrets
Health Checks Container health, Startup probe, Readiness probe, Liveness probe, Graceful shutdown HEALTHCHECK CMD curl -f http://localhost/ || exit 1. 30s interval Return 200 on /health. Check dependencies. Fail if not ready. K8s probes required
Security Hardening Non-root user, Minimal base image, Vulnerability scanning, No secrets, Read-only FS USER node (1000). RUN chmod. Trivy/Snyk scan. Secrets from vault. COPY --chown Scan images weekly. Update base images. No RUN as root. Mount secrets. Least privilege

Example: Production Dockerfile with multi-stage build

# Dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json pnpm-lock.yaml ./

# Install dependencies with cache mount
RUN --mount=type=cache,target=/root/.npm \
    npm install -g pnpm && \
    pnpm install --frozen-lockfile

# Copy source
COPY . .

# Build application
RUN pnpm build

# Stage 2: Production
FROM nginx:alpine-slim

# Copy nginx config
COPY nginx.conf /etc/nginx/nginx.conf

# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html

# Create non-root user
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser && \
    chown -R appuser:appuser /usr/share/nginx/html && \
    chown -R appuser:appuser /var/cache/nginx

# Switch to non-root
USER appuser

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1

EXPOSE 8080

CMD ["nginx", "-g", "daemon off;"]

Example: Nginx configuration for SPA

# nginx.conf
events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    
    server {
        listen 8080;
        server_name _;
        root /usr/share/nginx/html;
        index index.html;
        
        # Security headers
        add_header X-Frame-Options "DENY" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        
        # Cache static assets
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # SPA routing - fallback to index.html
        location / {
            try_files $uri $uri/ /index.html;
        }
        
        # API proxy (optional)
        location /api/ {
            proxy_pass http://backend:3000/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

14.4 CloudFront S3 Static Hosting

Component Configuration Features Best Practices
S3 Bucket Setup Static website hosting, Bucket policy, CORS config, Versioning, Lifecycle rules Host index.html, Public read access, Website endpoint, 99.99% durability Enable versioning. Lifecycle delete old versions. Block public write. Use IAM roles
CloudFront CDN Global edge network, SSL/TLS, Custom domain, Origin access, Cache behavior 450+ edge locations, Sub-50ms latency, DDoS protection, HTTP/2, Brotli compression Use CloudFront OAI. Custom error pages. Cache everything. Invalidate on deploy
Cache Strategy Cache-Control headers, TTL settings, Query string caching, Version/hash filenames Max-age 31536000 for static. No-cache for HTML. ETags for validation Hash filenames for cache-busting. Cache static 1yr. HTML no-cache. Use CloudFront Functions
SSL Certificate ACM certificate, HTTPS redirect, TLS 1.2+, Custom domain, DNS validation Free SSL via ACM, Auto-renewal, SNI support, Modern cipher suites Use us-east-1 for ACM. Enable HTTPS only. HTTP→HTTPS redirect. Enable HSTS
Error Handling Custom error pages, 404 fallback, 403 to index, SPA routing support Return index.html for 404/403, Custom error pages, Status code mapping 404→index.html for SPA. Custom 500 page. Monitor 4xx/5xx rates. Log errors to S3
Security Headers CSP, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy CloudFront Functions or Lambda@Edge for headers. WAF for DDoS. Geo-blocking Add security headers via CF Functions. Enable WAF. Block bots. Rate limiting

Example: Deploy script for S3 + CloudFront

#!/bin/bash
# deploy.sh

# Build application
npm run build

# Sync to S3 with cache headers
aws s3 sync dist/ s3://my-app-bucket/ \
  --delete \
  --cache-control "max-age=31536000,public,immutable" \
  --exclude "index.html" \
  --exclude "*.html"

# Upload HTML with no-cache
aws s3 sync dist/ s3://my-app-bucket/ \
  --cache-control "no-cache,no-store,must-revalidate" \
  --exclude "*" \
  --include "*.html"

# Invalidate CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id E1234ABCD5678 \
  --paths "/*"

echo "Deployment complete!"

Example: CloudFront Functions for security headers

// cloudfront-function.js
function handler(event) {
    var response = event.response;
    var headers = response.headers;

    // Security headers
    headers['strict-transport-security'] = { 
        value: 'max-age=31536000; includeSubDomains; preload' 
    };
    headers['x-content-type-options'] = { value: 'nosniff' };
    headers['x-frame-options'] = { value: 'DENY' };
    headers['x-xss-protection'] = { value: '1; mode=block' };
    headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' };
    headers['content-security-policy'] = { 
        value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" 
    };

    return response;
}

14.5 Blue-Green Canary Deployment

Strategy Description Implementation Use Cases & Benefits
Blue-Green Deployment Two identical environments. Deploy to green. Switch traffic. Instant rollback Load balancer routes to blue. Deploy green. Test. Switch DNS/LB. Keep blue for rollback Zero downtime. Instant rollback. Test production environment. Database challenge
Canary Deployment Gradual rollout. 5%→25%→50%→100% traffic. Monitor metrics. Rollback on issues Deploy canary. Route 5% traffic. Monitor errors/latency. Increase if healthy. Full rollout Risk mitigation. Early issue detection. Gradual rollout. Metrics-driven
A/B Testing Multiple versions. Split traffic by user attributes. Compare metrics. Data-driven decisions Deploy variants. Route by user ID/cookie. Track conversion. Statistical significance Feature testing. UI experiments. Business metrics. User segmentation
Feature Flags Toggle features runtime. Gradual rollout. Kill switch. No redeployment needed LaunchDarkly, Unleash, Split. Boolean/percentage rollout. User targeting. Override UI Decouple deploy from release. Instant kill switch. User targeting. Beta testing
Rolling Deployment Update servers one-by-one. Always available. Gradual migration. Version coexistence K8s RollingUpdate. Update 25% pods at a time. Health checks. Rollback on failure No downtime. Incremental. Resource efficient. Slower than blue-green
Rollback Strategy Instant traffic switch. Version pinning. Database migrations. State management Keep previous version. DNS/LB switch. Feature flag disable. DB backward compatible Sub-1min rollback. No data loss. Audit trail. Automated rollback on errors

Example: Kubernetes canary deployment with Flagger

# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-app
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-app
  service:
    port: 80
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 1m
  # Traffic routing
  canaryAnalysis:
    # Start with 10% traffic
    # Increase by 10% every minute if healthy
    # Rollback if 5 failures

Example: Feature flag implementation

// featureFlags.ts
import { LaunchDarkly } from '@launchdarkly/node-server-sdk';

const ldClient = LaunchDarkly.init(process.env.LD_SDK_KEY);

export async function checkFeature(
  userId: string, 
  flagKey: string
): Promise<boolean> {
  const user = { key: userId };
  const flagValue = await ldClient.variation(flagKey, user, false);
  return flagValue;
}

// React component usage
function NewFeature() {
  const { flags } = useLDClient();
  
  if (!flags.newCheckoutFlow) {
    return <OldCheckout />;
  }
  
  return <NewCheckout />;
}

// Gradual rollout configuration in LaunchDarkly:
// - 0-5%: Internal users only
// - 5-25%: Beta users
// - 25-50%: Random 50% of users
// - 50-100%: All users
// Rollback: Set to 0% instantly

14.6 Environment Variables Config Management

Aspect Implementation Tools & Patterns Security & Best Practices
12-Factor Config Separate config from code. Store in environment. Different per deploy. No secrets in repo .env files locally. Platform env vars in prod. Process.env access. Validation at startup Never commit .env. Use .env.example template. Validate required vars. Fail fast if missing
Frontend Env Vars Build-time injection. Runtime config. Public vs private. VITE_*, NEXT_PUBLIC_*, REACT_APP_* Vite: VITE_ prefix. Next: NEXT_PUBLIC_. CRA: REACT_APP_. Expose via import.meta.env Never expose secrets to frontend. Use prefixes for public vars. Runtime config for sensitive
Secrets Management Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Encrypted at rest Store API keys, DB creds, tokens. Rotate regularly. Access control. Audit logs Never log secrets. Rotate every 90 days. Least privilege access. Use temporary credentials
Multi-Environment dev, staging, production. Different config per env. Promote config through pipeline .env.development, .env.production. Platform env vars. Config service. Feature flags Production parity. Explicit env switching. No dev secrets in prod. Test staging config
Validation & Type Safety Zod/Joi validation. TypeScript env types. Fail at startup. Required vs optional Zod schema validation. @t3-oss/env-nextjs. Env.d.ts types. Runtime validation Validate on app start. Type-safe access. Clear error messages. Document all variables
Config Injection Runtime config. window.__ENV__. Config endpoint. Dynamic without rebuild Serve config.js from server. Template replacement. K8s ConfigMaps. Consul/etcd No rebuild for config changes. Separate config deployment. Versioned config. Rollback support

Example: Environment validation with Zod

// env.ts
import { z } from 'zod';

const envSchema = z.object({
  // Public variables (exposed to frontend)
  VITE_API_URL: z.string().url(),
  VITE_APP_ENV: z.enum(['development', 'staging', 'production']),
  VITE_SENTRY_DSN: z.string().optional(),
  
  // Server-only variables (not exposed)
  DATABASE_URL: z.string().min(1),
  API_SECRET_KEY: z.string().min(32),
  JWT_SECRET: z.string().min(32),
  
  // Optional with defaults
  PORT: z.string().default('3000'),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});

// Validate at startup
const env = envSchema.parse(process.env);

// Type-safe access
export { env };

// Usage
import { env } from './env';
console.log(env.VITE_API_URL); // Type-safe, validated

Example: Runtime config injection for Docker

// public/config.js template
window.__ENV__ = {
  API_URL: '${API_URL}',
  ENVIRONMENT: '${ENVIRONMENT}',
  FEATURE_FLAGS: '${FEATURE_FLAGS}'
};

// entrypoint.sh - Replace at container start
#!/bin/sh
envsubst < /usr/share/nginx/html/config.js.template \
  > /usr/share/nginx/html/config.js
nginx -g 'daemon off;'

// Access in React
const config = (window as any).__ENV__;
const API_URL = config.API_URL || 'http://localhost:3000';

// Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: frontend-config
data:
  API_URL: "https://api.production.com"
  ENVIRONMENT: "production"
  FEATURE_FLAGS: "new-ui,beta-checkout"
Environment Best Practices:
  • Local Development: Use .env files with dotenv. Never commit .env, only .env.example
  • CI/CD: Store secrets in GitHub Secrets, GitLab CI/CD variables, or CircleCI contexts
  • Production: Use platform env vars (Vercel, Netlify) or secrets managers (AWS Secrets Manager)
  • Validation: Validate all env vars at startup. Fail fast with clear errors. Use Zod/Joi
  • Documentation: Maintain .env.example. Document required vs optional. Include in README

Deployment & Delivery Summary

  • Jamstack Platforms: Vercel/Netlify for zero-config. Edge functions for low latency. Preview deploys for testing. Free tier 100GB
  • CI/CD Pipeline: GitHub Actions for automation. Cache dependencies (10x faster). Parallel tests. Deploy after tests pass
  • Docker: Multi-stage builds (50-100MB). Nginx for serving. Non-root user. Layer caching. Security scanning
  • CloudFront + S3: Global CDN. Cache static 1yr. HTML no-cache. CloudFront Functions for headers. $0.085/GB
  • Deployment Strategies: Blue-green for instant rollback. Canary for gradual rollout. Feature flags for toggle. A/B for testing
  • Config Management: 12-factor app. Separate per environment. Zod validation. Never commit secrets. Runtime injection
Production Checklist: Implement CI/CD automation, enable preview deploys, use canary deployments, validate environment variables, add health checks, enable monitoring, and maintain rollback capability. Target <5min deployment time, 99.9% uptime.

15. Code Quality Maintainability Implementation

15.1 ESLint Prettier Code Formatting

Tool Purpose & Features Configuration Best Practices
ESLint NEW Static analysis, Code quality, Bug detection, Style enforcement, Plugin ecosystem .eslintrc.json: extends, plugins, rules, parser. TypeScript: @typescript-eslint Use recommended configs. Add project-specific rules. Run in CI. Fix auto-fixable. 0 warnings
Prettier Code formatter, Opinionated, Consistent style, Language-agnostic, IDE integration .prettierrc: printWidth 80, semi true, singleQuote, trailingComma es5 Integrate with ESLint. Format on save. Pre-commit hook. No style debates. Team consistency
ESLint Plugins React hooks rules, import order, a11y checks, security rules, performance hints eslint-plugin-react, -react-hooks, -jsx-a11y, -import, -security, -sonarjs react-hooks: exhaustive-deps. import: order, no-duplicates. jsx-a11y: recommended
ESLint + Prettier Integration Resolve conflicts, Turn off style rules, Use eslint-config-prettier, Run both Install eslint-config-prettier. Add to extends last. Prettier formats, ESLint lints Prettier last in extends. Use editor.formatOnSave. Separate concerns: format vs lint
Custom Rules Project conventions, Naming patterns, Architecture boundaries, Import restrictions ESLint custom rules. no-restricted-imports. Naming conventions. Max complexity Enforce folder structure. Prevent circular deps. Naming: camelCase vars, PascalCase components
Performance Optimization Cache results, Parallel execution, Ignore files, Lint staged, Incremental linting --cache flag. .eslintignore for build/. lint-staged for changed files only Cache in CI. Lint only staged files locally. Ignore dist/, node_modules/. 10x faster

Example: ESLint + Prettier configuration

// .eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:import/recommended",
    "plugin:import/typescript",
    "prettier" // Must be last to override style rules
  ],
  "plugins": ["@typescript-eslint", "react", "react-hooks", "import"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2023,
    "sourceType": "module",
    "ecmaFeatures": { "jsx": true },
    "project": "./tsconfig.json"
  },
  "rules": {
    // React
    "react/react-in-jsx-scope": "off",
    "react-hooks/exhaustive-deps": "error",
    "react/prop-types": "off",
    
    // TypeScript
    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "warn",
    
    // Imports
    "import/order": ["error", {
      "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
      "newlines-between": "always",
      "alphabetize": { "order": "asc" }
    }],
    "import/no-duplicates": "error",
    
    // Code quality
    "no-console": ["warn", { "allow": ["warn", "error"] }],
    "complexity": ["warn", 10],
    "max-lines": ["warn", 300]
  },
  "settings": {
    "react": { "version": "detect" },
    "import/resolver": { "typescript": {} }
  }
}

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "trailingComma": "es5",
  "arrowParens": "always",
  "endOfLine": "lf"
}

Example: Package.json scripts

// package.json
{
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx --cache",
    "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
    "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "type-check": "tsc --noEmit"
  }
}

15.2 Husky Pre-commit Git Hooks

Tool Purpose Configuration Use Cases
Husky Git hooks management, Automate quality checks, Pre-commit/push hooks, Team consistency npx husky install. Add hooks: npx husky add .husky/pre-commit Prevent bad commits. Run linters. Check types. Validate messages. Block force-push
lint-staged Run linters on staged files only, Fast feedback, Incremental linting, Auto-fix .lintstagedrc.json: Map file patterns to commands. eslint --fix, prettier --write Lint only changed files. Format on commit. 10x faster than full lint. Auto-fix issues
Pre-commit Hook Lint staged files, Format code, Type check, Run unit tests, Validate imports lint-staged, prettier, tsc --noEmit, jest --findRelatedTests. Exit 1 on failure Quality gate before commit. Catch errors early. Consistent formatting. Fast (2-10s)
Commit Message Lint Conventional commits, Enforce format, Changelog generation, Semantic versioning @commitlint/config-conventional. Format: type(scope): subject. feat/fix/chore Consistent history. Auto-changelog. Semantic release. CI/CD integration. Git log search
Pre-push Hook Full tests, E2E tests, Build verification, Branch protection, Prevent WIP npm test, npm run build, check branch name. Prevent push to main. Longer checks Final validation. Run full test suite. Prevent broken builds. Check for TODO/FIXME
Hook Management Install hooks, Share with team, CI compatibility, Skip hooks, Debug hooks husky install in postinstall. Commit .husky/ to git. Skip: --no-verify. CI: skip hooks Auto-install for new devs. Version control hooks. Emergency skip option. Debug with set -x

Example: Complete Husky + lint-staged setup

// package.json
{
  "scripts": {
    "prepare": "husky install",
    "lint-staged": "lint-staged"
  },
  "devDependencies": {
    "husky": "^8.0.0",
    "lint-staged": "^15.0.0",
    "@commitlint/cli": "^18.0.0",
    "@commitlint/config-conventional": "^18.0.0"
  }
}

// .lintstagedrc.json
{
  "*.{ts,tsx,js,jsx}": [
    "eslint --fix",
    "prettier --write",
    "jest --bail --findRelatedTests --passWithNoTests"
  ],
  "*.{json,css,md}": ["prettier --write"],
  "*.{ts,tsx}": ["bash -c 'tsc --noEmit'"]
}

// .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "🔍 Running pre-commit checks..."
npx lint-staged

// .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no -- commitlint --edit $1

// .husky/pre-push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "🧪 Running pre-push checks..."

# Check for console.log
if git diff origin/main --name-only | xargs grep -n "console.log"; then
  echo "❌ Found console.log in code"
  exit 1
fi

# Run full tests
npm test -- --coverage --watchAll=false

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style', 'refactor', 
      'perf', 'test', 'chore', 'revert', 'ci'
    ]],
    'subject-case': [2, 'always', 'sentence-case'],
    'subject-max-length': [2, 'always', 72],
    'body-max-line-length': [2, 'always', 100]
  }
};

15.3 SonarQube Code Analysis Quality Gates

Feature Description Metrics & Thresholds Implementation
Quality Gates Pass/fail criteria, Code coverage, Code smells, Security hotspots, Duplication Coverage ≥80%, Duplications <3%, Maintainability A, Security A, Reliability A SonarCloud or self-hosted. Integrate with CI. Block merge if failed. Daily scans
Code Coverage Line coverage, Branch coverage, Function coverage, Uncovered lines, Coverage trends Target: 80% overall, 60% new code minimum. Track trends. Fail if coverage drops Jest/Vitest lcov report. Upload to SonarQube. PR decoration. Coverage badge in README
Code Smells Maintainability issues, Cognitive complexity, Duplicated code, Large functions/classes Complexity <15, File <300 lines, Function <50 lines, Duplication <3%, Debt <5% Auto-detect patterns. Refactor suggestions. Technical debt tracking. Time to fix estimates
Security Analysis Vulnerabilities, Security hotspots, OWASP Top 10, Injection flaws, Sensitive data Zero vulnerabilities (High/Critical). Review all hotspots. OWASP compliance. Secrets scan Detect XSS, SQL injection, hardcoded secrets. Taint analysis. Dependency vulnerabilities
Technical Debt Debt ratio, Debt time, Effort estimation, Issue prioritization, Remediation cost Debt ratio <5%, Max 30min debt per 1h dev time. Track in sprints. Pay down weekly Calculate from issues. SQALE rating. Prioritize by severity. Sprint debt budget
CI Integration PR decoration, Quality gate status, Branch analysis, Comment on PRs, Block merge Run on every PR. Comment results. Block if failed. Main branch daily. Release scan GitHub Action/GitLab CI. sonar-scanner. Token auth. Webhook for PR comments

Example: SonarQube CI integration

// .github/workflows/sonarqube.yml
name: SonarQube Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Full history for better analysis
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Run tests with coverage
        run: pnpm test -- --coverage --watchAll=false
      
      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
      
      - name: Quality Gate Check
        uses: SonarSource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

// sonar-project.properties
sonar.projectKey=my-frontend-app
sonar.organization=my-org
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.test.ts,**/*.test.tsx,**/*.spec.ts
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.coverage.exclusions=**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.stories.tsx
sonar.typescript.tsconfigPath=tsconfig.json
sonar.qualitygate.wait=true

# Quality Gate Thresholds
sonar.qualitygate.coverage=80
sonar.qualitygate.duplications=3
sonar.qualitygate.maintainability_rating=A
sonar.qualitygate.reliability_rating=A
sonar.qualitygate.security_rating=A

15.4 TypeScript Strict Mode Type Safety

Feature Purpose Configuration Best Practices
Strict Mode Maximum type safety, Catch errors, No implicit any, Null safety, Strict checks "strict": true in tsconfig. Enables all strict flags. Gradual adoption Enable for new projects. Migrate incrementally. Use @ts-expect-error. Zero any goal
No Implicit Any Explicit types, No inferred any, Function params, Object properties, Return types "noImplicitAny": true. Forces explicit types. Catch untyped code Type all function params. Explicit return types. No any shortcuts. Use unknown instead
Strict Null Checks Prevent null/undefined errors, Optional chaining, Nullish coalescing, Type guards "strictNullChecks": true. null/undefined not assignable to types Use optional chaining ?. Nullish coalescing ??. Type guards: if (x) {}. Non-null assertion !
Strict Function Types Contravariance, Function param checking, Callback types, Event handlers "strictFunctionTypes": true. Stricter function type compatibility Proper event handler types. Callback param types. Generic constraints. No bivariance
No Unused Locals/Params Dead code detection, Cleanup unused vars, Prevent errors, Code cleanliness "noUnusedLocals": true, "noUnusedParameters": true. Compile errors Prefix with _ for intentional unused. Remove dead code. Refactor parameter order
Advanced Type Safety Type guards, Discriminated unions, Const assertions, Template literals, Branded types Use as const, type predicates, exhaustive checks, strict equality. Utility types Type guards for narrowing. Exhaustive switch. as const for immutability. Brand IDs

Example: Strict TypeScript configuration

// tsconfig.json
{
  "compilerOptions": {
    // Strict Mode - Enable ALL
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    
    // Additional Strictness
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "allowUnusedLabels": false,
    "allowUnreachableCode": false,
    
    // Module Resolution
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    
    // Emit
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": true,
    
    // Type Checking
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "build"]
}

Example: Advanced type safety patterns

// Type guards
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

// Discriminated unions
type Result<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

function handleResult(result: Result<User>) {
  if (result.success) {
    console.log(result.data.name); // Type-safe
  } else {
    console.error(result.error);
  }
}

// Const assertions
const CONFIG = {
  API_URL: 'https://api.example.com',
  TIMEOUT: 5000
} as const;
// Type: { readonly API_URL: "https://api.example.com", readonly TIMEOUT: 5000 }

// Branded types for IDs
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };

function getUser(id: UserId): User { /* ... */ }
// getUser('123' as UserId); // Explicit casting required

// Exhaustive checks
type Status = 'idle' | 'loading' | 'success' | 'error';

function handleStatus(status: Status) {
  switch (status) {
    case 'idle': return 'Idle';
    case 'loading': return 'Loading';
    case 'success': return 'Success';
    case 'error': return 'Error';
    default:
      const exhaustive: never = status;
      throw new Error(`Unhandled status: ${exhaustive}`);
  }
}

// Utility types
type PartialUser = Partial<User>; // All optional
type RequiredUser = Required<User>; // All required
type ReadonlyUser = Readonly<User>; // Immutable
type UserKeys = keyof User; // Union of keys
type UserValues = User[keyof User]; // Union of values

15.5 JSDoc Documentation Generation

Aspect Purpose Syntax & Tags Best Practices
JSDoc Comments API documentation, Type hints, IDE tooltips, Auto-completion, Examples /** Description @param {type} name @returns {type} */. Above declarations Document public APIs. Describe purpose, params, returns. Add examples. TypeScript types
Common Tags @param, @returns, @throws, @example, @deprecated, @see, @since, @author @param {string} name - Description. @returns {Promise<User>} The user. @example code Type with TypeScript. Describe behavior. Show examples. Mark deprecated. Link related
TypeScript Integration Type checking from JSDoc, Gradual typing, JS to TS migration, Tooling support @type, @typedef, @template. TS reads JSDoc. checkJs: true in tsconfig Use TS types in JSDoc. checkJs for validation. Migrate to TS incrementally. Type imports
Documentation Generation TypeDoc, JSDoc, API docs, Markdown output, HTML site, CI automation typedoc --out docs src/. Configure typedoc.json. Generate on deploy Auto-generate docs. Deploy to GitHub Pages. Version docs. Include in CI. Search enabled
Component Documentation React props, Component purpose, Usage examples, Storybook integration, Props table Document props interface. @example with JSX. Storybook for visual. PropTypes/TS Document all public components. Show usage. Link to Storybook. Accessibility notes
Maintenance Keep docs updated, Review in PRs, Deprecation notices, Changelog, Version docs Lint docs with eslint-plugin-jsdoc. Update with code. Archive old versions Docs in PR checklist. Lint for completeness. Deprecate gracefully. Semantic versioning

Example: Comprehensive JSDoc documentation

/**
 * Fetches user data from the API with retry logic
 * 
 * @param {string} userId - The unique user identifier
 * @param {Object} options - Configuration options
 * @param {number} [options.maxRetries=3] - Maximum retry attempts
 * @param {number} [options.timeout=5000] - Request timeout in ms
 * @returns {Promise<User>} The user object
 * @throws {NetworkError} When network request fails after retries
 * @throws {ValidationError} When userId is invalid
 * 
 * @example
 * // Fetch user with default options
 * const user = await fetchUser('123');
 * 
 * @example
 * // Fetch with custom options
 * const user = await fetchUser('123', {
 *   maxRetries: 5,
 *   timeout: 10000
 * });
 * 
 * @see {@link https://api.example.com/docs API Documentation}
 * @since 2.0.0
 */
async function fetchUser(
  userId: string,
  options: FetchOptions = {}
): Promise<User> {
  // Implementation
}

/**
 * User profile component with avatar and bio
 * 
 * @component
 * @param {Object} props - Component props
 * @param {User} props.user - User object to display
 * @param {boolean} [props.showBio=true] - Whether to show user bio
 * @param {Function} [props.onEdit] - Callback when edit button clicked
 * @returns {JSX.Element} Rendered component
 * 
 * @example
 * <UserProfile 
 *   user={currentUser} 
 *   showBio={false}
 *   onEdit={handleEdit}
 * />
 */
export function UserProfile({ user, showBio = true, onEdit }: Props) {
  // Component implementation
}

/**
 * Custom hook for managing form state with validation
 * 
 * @template T - Form values type
 * @param {T} initialValues - Initial form values
 * @param {ValidationSchema<T>} schema - Validation schema
 * @returns {FormState<T>} Form state and handlers
 * 
 * @example
 * const { values, errors, handleChange, handleSubmit } = useForm({
 *   email: '',
 *   password: ''
 * }, loginSchema);
 */
function useForm<T>(
  initialValues: T,
  schema: ValidationSchema<T>
): FormState<T> {
  // Hook implementation
}

Example: TypeDoc configuration

// typedoc.json
{
  "entryPoints": ["src/index.ts"],
  "out": "docs",
  "name": "My Frontend Library",
  "includeVersion": true,
  "excludePrivate": true,
  "excludeProtected": false,
  "excludeExternals": true,
  "readme": "README.md",
  "plugin": ["typedoc-plugin-markdown"],
  "theme": "default",
  "categorizeByGroup": true,
  "defaultCategory": "Other",
  "categoryOrder": [
    "Components",
    "Hooks",
    "Utilities",
    "Types",
    "*"
  ]
}

// package.json
{
  "scripts": {
    "docs": "typedoc",
    "docs:serve": "typedoc --watch --serve"
  }
}

// .github/workflows/docs.yml
name: Generate Docs
on:
  push:
    branches: [main]
jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm install
      - run: npm run docs
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs

15.6 Dependency Management Renovate Dependabot

Tool Features Configuration Best Practices
Renovate Bot NEW Auto PRs, Grouped updates, Custom schedules, Automerge, Vulnerability alerts renovate.json: automerge, schedule, grouping, semantic commits. 100% customizable Group minor/patch. Automerge patch. Weekly schedule. Pin versions. Test before merge
Dependabot GitHub native, Security alerts, Version updates, Auto PRs, Compatibility score .github/dependabot.yml: package-ecosystem, schedule, reviewers, labels, groups Enable security updates. Daily security, weekly deps. Group by type. Auto-assign reviewers
Update Strategies Major/minor/patch, Lockfile-only, Ranged versions, Pinned versions, Monorepo support Patch: automerge. Minor: review. Major: manual. Pin in prod. Range in lib Pin exact versions apps. Use ^ in libraries. Lock file committed. Test all updates
Vulnerability Scanning npm audit, Snyk, OWASP dependency-check, CVE alerts, Severity scoring, Auto-fix npm audit fix, Snyk test, dependabot security updates. Weekly scans. Block high CVEs Fix critical immediately. Weekly audit. Use Snyk in CI. Monitor transitive deps. Quarantine
Monorepo Management Workspace updates, Hoisting, Dedupe, Peer deps, Version sync, Selective updates pnpm/yarn workspaces. Renovate/Dependabot workspace support. Group workspace deps Sync versions across workspace. Dedupe regularly. Hoist common deps. Test full workspace
Automation & CI Auto-approve, Auto-merge, CI passing, Semantic commits, Changelog, Release notes Merge if CI passes + patch. Group PRs by type. Test coverage. Semantic commits Automerge patch only. Review minor. Manual major. Check changelogs. Test compatibility

Example: Renovate configuration

// renovate.json
{
  "extends": ["config:base"],
  "schedule": ["after 10pm on sunday"],
  "timezone": "America/New_York",
  "labels": ["dependencies"],
  "assignees": ["@team-lead"],
  "semanticCommits": "enabled",
  "rangeStrategy": "bump",
  "packageRules": [
    {
      "matchUpdateTypes": ["patch", "pin", "digest"],
      "automerge": true,
      "automergeType": "branch"
    },
    {
      "matchUpdateTypes": ["minor"],
      "groupName": "minor dependencies",
      "automerge": false
    },
    {
      "matchUpdateTypes": ["major"],
      "groupName": "major dependencies",
      "labels": ["breaking-change"],
      "automerge": false
    },
    {
      "matchPackagePatterns": ["^@types/"],
      "groupName": "type definitions",
      "automerge": true
    },
    {
      "matchPackagePatterns": ["^eslint", "^prettier"],
      "groupName": "linting tools",
      "schedule": ["before 3am on Monday"]
    },
    {
      "matchDepTypes": ["devDependencies"],
      "extends": ["schedule:weekly"]
    }
  ],
  "vulnerabilityAlerts": {
    "enabled": true,
    "labels": ["security"],
    "assignees": ["@security-team"]
  }
}

Example: Dependabot configuration

# .github/dependabot.yml
version: 2
updates:
  # npm dependencies
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "sunday"
      time: "03:00"
    open-pull-requests-limit: 10
    reviewers:
      - "team-lead"
    assignees:
      - "developer"
    labels:
      - "dependencies"
      - "automated"
    commit-message:
      prefix: "chore"
      prefix-development: "chore"
      include: "scope"
    
    # Group updates
    groups:
      react:
        patterns:
          - "react*"
          - "@types/react*"
      testing:
        patterns:
          - "jest"
          - "@testing-library/*"
          - "vitest"
      build-tools:
        patterns:
          - "vite"
          - "webpack"
          - "babel"
    
    # Ignore specific packages
    ignore:
      - dependency-name: "legacy-package"
        update-types: ["version-update:semver-major"]
  
  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
    labels:
      - "ci"
      - "dependencies"

  # Docker
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"
Dependency Management Best Practices:
  • Security First: Enable automated security updates. Fix critical CVEs within 24h. Weekly npm audit
  • Version Strategy: Pin exact versions in apps. Use ^ ranges in libraries. Commit lock files
  • Update Schedule: Patch: immediate/automerge. Minor: weekly review. Major: manual testing
  • Testing: Run full test suite before merge. Check breaking changes. Test in staging
  • Monitoring: Track update success rate. Monitor bundle size. Check performance impact

Code Quality & Maintainability Summary

  • ESLint + Prettier: Enforce code style. 300+ rules. Auto-fix on save. Run in CI. 0 warnings policy
  • Git Hooks: Husky + lint-staged. Lint on commit (2-5s). Format automatically. Conventional commits
  • Code Analysis: SonarQube quality gates. 80% coverage. Zero critical issues. Technical debt <5%
  • TypeScript Strict: Enable all strict flags. No implicit any. Null safety. Type guards. 100% type coverage
  • Documentation: JSDoc for all public APIs. TypeDoc generation. Examples. Deploy to GitHub Pages
  • Dependencies: Renovate/Dependabot automation. Weekly updates. Automerge patch. Security alerts
Quality Standards: Maintain 80%+ test coverage, zero ESLint errors, SonarQube Quality Gate passing, TypeScript strict mode, all public APIs documented, and dependencies updated weekly. Quality is non-negotiable for production code.

16. Progressive Web App Implementation Stack

16.1 Service Worker Workbox Caching Strategies

Strategy Description & Use Cases Implementation Best Practices
Cache First (CacheFirst) Check cache first, network fallback. Best for: static assets, fonts, images, CSS, JS bundles Workbox: new CacheFirst({ cacheName, plugins }). Max age 1 year. Cache on install Use for immutable assets. Set max-age 1yr. Cache bust with hashes. Limit cache size 50MB
Network First (NetworkFirst) Try network, cache fallback. Best for: API calls, dynamic content, news feeds, user data new NetworkFirst({ cacheName, networkTimeoutSeconds: 3 }). Timeout to cache Use for fresh data priority. 3s timeout. Cache as backup. Good for offline experience
Stale While Revalidate Return cache, update background. Best for: avatars, non-critical content, analytics new StaleWhileRevalidate({ cacheName }). Instant response + background update Best UX for semi-fresh data. Fast response. Update silently. Use for profile pics, icons
Network Only Always network, never cache. Best for: POST/PUT/DELETE, payments, auth, real-time data new NetworkOnly(). No caching. Fail if offline. Critical data only Use for mutations. Payment APIs. Auth endpoints. Never cache sensitive data
Cache Only Only from cache, no network. Best for: offline-first, precached shells, fallback pages new CacheOnly({ cacheName }). Must precache. Offline page. App shell Precache on install. Offline fallback page. App shell. Never stale content
Workbox Strategies Plugin system, expiration, cache size, broadcast updates, background sync ExpirationPlugin (maxAge, maxEntries). CacheableResponsePlugin. BroadcastUpdatePlugin Limit cache 50MB. Expire after 30d. Cache 200 responses only. Broadcast cache updates

Example: Workbox service worker with caching strategies

// service-worker.js
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { 
  CacheFirst, 
  NetworkFirst, 
  StaleWhileRevalidate,
  NetworkOnly 
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Precache app shell and static assets
precacheAndRoute(self.__WB_MANIFEST);

// Cache static assets (images, fonts, CSS, JS)
registerRoute(
  ({ request }) => ['image', 'font', 'style', 'script'].includes(request.destination),
  new CacheFirst({
    cacheName: 'static-assets-v1',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
        purgeOnQuotaError: true,
      }),
    ],
  })
);

// Cache API responses with network first
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api-cache-v1',
    networkTimeoutSeconds: 3,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [200],
      }),
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 5 * 60, // 5 minutes
      }),
    ],
  })
);

// Stale-while-revalidate for avatars and images
registerRoute(
  ({ url }) => url.pathname.match(/\.(jpg|jpeg|png|gif|webp)$/),
  new StaleWhileRevalidate({
    cacheName: 'images-v1',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
      }),
    ],
  })
);

// Network only for authentication and mutations
registerRoute(
  ({ url, request }) => 
    url.pathname.startsWith('/api/auth') || 
    ['POST', 'PUT', 'DELETE'].includes(request.method),
  new NetworkOnly()
);

// Offline fallback page
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('offline-v1').then((cache) => cache.add(OFFLINE_URL))
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => 
        caches.match(OFFLINE_URL)
      )
    );
  }
});

Example: Register service worker in React

// src/serviceWorkerRegistration.ts
export function register() {
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker
        .register('/service-worker.js')
        .then((registration) => {
          console.log('SW registered:', registration);
          
          // Check for updates every hour
          setInterval(() => {
            registration.update();
          }, 60 * 60 * 1000);
          
          // Listen for updates
          registration.addEventListener('updatefound', () => {
            const newWorker = registration.installing;
            newWorker?.addEventListener('statechange', () => {
              if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
                // New content available, show update prompt
                if (confirm('New version available! Reload to update?')) {
                  newWorker.postMessage({ type: 'SKIP_WAITING' });
                  window.location.reload();
                }
              }
            });
          });
        })
        .catch((error) => {
          console.error('SW registration failed:', error);
        });
    });
  }
}

// src/main.tsx
import { register } from './serviceWorkerRegistration';

register();

16.2 Web App Manifest PWA Installation

Property Description Example Values Best Practices
name Full app name shown on splash screen and app list. 45 characters max "My Awesome PWA Application" Descriptive, brandable. Same as page title. Avoid generic names
short_name Short name for home screen. 12 characters max fits all devices "MyPWA" Keep under 12 chars. Still recognizable. Used as icon label. Test on devices
icons App icons for home screen, splash screen. Multiple sizes required 192x192, 512x512 (maskable), 180x180 (iOS) Provide 192x192 (Android), 512x512 (splash), maskable icons. PNG format. Transparent
start_url URL loaded when app launches. Track PWA installs with query param "/?source=pwa" Add UTM params for analytics. Must be in scope. Relative path preferred
display Display mode: standalone, fullscreen, minimal-ui, browser "standalone" Use standalone for app-like. fullscreen for games. minimal-ui for tools. Browser fallback
theme_color & background_color Theme: status bar color. Background: splash screen while loading "#1976d2", "#ffffff" Match brand colors. Theme for browser UI. Background for splash. Update meta tags

Example: Complete manifest.json

// public/manifest.json
{
  "name": "My Progressive Web App",
  "short_name": "MyPWA",
  "description": "A modern progressive web application with offline support",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait-primary",
  "theme_color": "#1976d2",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-maskable-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/icons/icon-maskable-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/desktop-1.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide"
    },
    {
      "src": "/screenshots/mobile-1.png",
      "sizes": "750x1334",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ],
  "categories": ["productivity", "utilities"],
  "lang": "en-US",
  "dir": "ltr",
  "prefer_related_applications": false
}

// index.html - Link manifest and meta tags
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <!-- PWA Manifest -->
  <link rel="manifest" href="/manifest.json">
  
  <!-- Theme colors -->
  <meta name="theme-color" content="#1976d2">
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#121212">
  
  <!-- iOS specific -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="apple-mobile-web-app-title" content="MyPWA">
  <link rel="apple-touch-icon" href="/icons/icon-180x180.png">
  
  <!-- Windows specific -->
  <meta name="msapplication-TileColor" content="#1976d2">
  <meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
</head>
</html>

Example: Install prompt handler

// React component for install prompt
import { useState, useEffect } from 'react';

export function InstallPWA() {
  const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
  const [showInstall, setShowInstall] = useState(false);

  useEffect(() => {
    const handler = (e: Event) => {
      e.preventDefault();
      setDeferredPrompt(e);
      setShowInstall(true);
    };

    window.addEventListener('beforeinstallprompt', handler);
    
    // Check if already installed
    if (window.matchMedia('(display-mode: standalone)').matches) {
      setShowInstall(false);
    }

    return () => window.removeEventListener('beforeinstallprompt', handler);
  }, []);

  const handleInstall = async () => {
    if (!deferredPrompt) return;

    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    
    console.log(`User ${outcome === 'accepted' ? 'accepted' : 'dismissed'} install`);
    
    setDeferredPrompt(null);
    setShowInstall(false);
  };

  if (!showInstall) return null;

  return (
    <div className="install-banner">
      <p>Install our app for better experience!</p>
      <button onClick={handleInstall}>Install</button>
      <button onClick={() => setShowInstall(false)}>Dismiss</button>
    </div>
  );
}

16.3 Push Notifications Web Push Protocol

Component Purpose Implementation Best Practices
VAPID Keys Voluntary Application Server Identification. Public/private key pair for auth Generate: web-push generate-vapid-keys. Store private securely. Public in client Never expose private key. Rotate yearly. Store in env vars. Use same keys across envs
Push Subscription User permission, get subscription endpoint, send to server, store in DB registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) Request permission contextually. Explain value. Fallback if denied. Resubscribe on expire
Push Server Send notifications via web-push library. Payload max 4KB. Encryption automatic Node: webpush.sendNotification(subscription, payload). Python: pywebpush Batch sends for efficiency. Retry on failure. Remove invalid subscriptions. Rate limit
Notification Payload Title, body, icon, badge, image, actions, tag, data, silent notifications { title, body, icon, badge, image, actions: [{ action, title }], data } Title 50 chars, body 120 chars. Icon 192x192. Actions max 2. Tag for grouping. Rich image
Service Worker Handler Listen for push event. Show notification. Handle click. Open app or URL self.addEventListener('push', event). registration.showNotification() Always show notification (userVisibleOnly). Handle click. Track engagement. Badge count
User Experience Timely, relevant, precise. Opt-in, not spam. Allow disable. Track click-through Contextual permission prompt. Settings to manage. Unsubscribe easy. Analytics Max 1-2 per day. Personalized. Actionable. Test before sending. A/B test messages

Example: Client-side push subscription

// utils/pushNotifications.ts
const PUBLIC_VAPID_KEY = 'BNXz...'; // Your public VAPID key

export async function subscribeToPush(): Promise<PushSubscription | null> {
  try {
    // Check permission
    const permission = await Notification.requestPermission();
    if (permission !== 'granted') {
      console.log('Notification permission denied');
      return null;
    }

    // Get service worker registration
    const registration = await navigator.serviceWorker.ready;

    // Check existing subscription
    let subscription = await registration.pushManager.getSubscription();

    if (!subscription) {
      // Create new subscription
      subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY),
      });
    }

    // Send subscription to server
    await fetch('/api/push/subscribe', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(subscription),
    });

    return subscription;
  } catch (error) {
    console.error('Push subscription failed:', error);
    return null;
  }
}

function urlBase64ToUint8Array(base64String: string): Uint8Array {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');
  const rawData = window.atob(base64);
  return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}

// React component
export function NotificationButton() {
  const [isSubscribed, setIsSubscribed] = useState(false);

  const handleSubscribe = async () => {
    const subscription = await subscribeToPush();
    setIsSubscribed(!!subscription);
  };

  return (
    <button onClick={handleSubscribe} disabled={isSubscribed}>
      {isSubscribed ? 'Notifications Enabled' : 'Enable Notifications'}
    </button>
  );
}

Example: Service worker push handler and server

// service-worker.js - Handle push events
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const { title, body, icon, badge, image, actions, url } = data;

  const options = {
    body,
    icon: icon || '/icons/icon-192x192.png',
    badge: badge || '/icons/badge-72x72.png',
    image,
    actions: actions || [],
    data: { url },
    tag: data.tag || 'notification',
    requireInteraction: false,
    vibrate: [200, 100, 200],
  };

  event.waitUntil(
    self.registration.showNotification(title, options)
  );
});

// Handle notification click
self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  const urlToOpen = event.notification.data?.url || '/';

  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true })
      .then((clientList) => {
        // Focus existing window if available
        for (const client of clientList) {
          if (client.url === urlToOpen && 'focus' in client) {
            return client.focus();
          }
        }
        // Open new window
        if (clients.openWindow) {
          return clients.openWindow(urlToOpen);
        }
      })
  );
});

// server.js - Send push notification
import webpush from 'web-push';

webpush.setVapidDetails(
  'mailto:admin@example.com',
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY
);

// Store subscriptions in database
const subscriptions = new Map();

app.post('/api/push/subscribe', (req, res) => {
  const subscription = req.body;
  subscriptions.set(subscription.endpoint, subscription);
  res.status(201).json({ success: true });
});

// Send notification
app.post('/api/push/send', async (req, res) => {
  const { title, body, url } = req.body;
  
  const payload = JSON.stringify({ title, body, url });
  
  const promises = Array.from(subscriptions.values()).map((subscription) =>
    webpush.sendNotification(subscription, payload)
      .catch((error) => {
        if (error.statusCode === 410) {
          // Subscription expired, remove it
          subscriptions.delete(subscription.endpoint);
        }
        console.error('Send failed:', error);
      })
  );

  await Promise.all(promises);
  res.json({ sent: promises.length });
});

16.4 Background Sync Offline Queue

Feature Use Cases Implementation Best Practices
Background Sync API Retry failed requests, Offline form submissions, Send analytics, Upload files when online registration.sync.register('sync-tag'). Service worker listens to sync event Use for non-urgent data. Retry failed requests. Queue operations. Expire after 24h
Workbox Background Sync Automatic retry queue, Exponential backoff, Replay requests, Plugin system new BackgroundSyncPlugin('queue-name', { maxRetentionTime: 24 * 60 }) Use workbox for simplicity. Configure retry logic. Max retention 24h. Monitor queue size
Offline Queue Store failed requests, Retry when online, Show pending operations, User feedback IndexedDB for queue. Online/offline events. Retry on network restore. UI indicators Show pending badge. Allow user to cancel. Preserve order. Handle conflicts. Sync on restore
Periodic Background Sync Fetch fresh content, Update cache, Background uploads, Data synchronization registration.periodicSync.register('tag', { minInterval: 24 * 60 * 60 * 1000 }) Min 12h interval. User engagement required. Battery-conscious. Update content proactively
Conflict Resolution Handle stale data, Merge changes, Last-write-wins, Operational transforms Version vectors, timestamps, CRDTs for complex merges. Server validates Timestamp all changes. Server arbitrates conflicts. User chooses on conflict. Preserve both
User Feedback Show sync status, Pending operations count, Retry notifications, Error handling Badge for pending. Toast on sync. Retry button. Show errors with details Visual feedback essential. Allow manual retry. Show progress. Clear success/failure

Example: Background sync with Workbox

// service-worker.js
import { BackgroundSyncPlugin } from 'workbox-background-sync';
import { registerRoute } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';

// Create background sync plugin
const bgSyncPlugin = new BackgroundSyncPlugin('api-queue', {
  maxRetentionTime: 24 * 60, // 24 hours in minutes
  onSync: async ({ queue }) => {
    let entry;
    while ((entry = await queue.shiftRequest())) {
      try {
        await fetch(entry.request.clone());
        console.log('Replay successful:', entry.request.url);
      } catch (error) {
        console.error('Replay failed:', error);
        await queue.unshiftRequest(entry);
        throw error;
      }
    }
  },
});

// Register route for API mutations
registerRoute(
  ({ url, request }) => 
    url.pathname.startsWith('/api/') &&
    ['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method),
  new NetworkOnly({
    plugins: [bgSyncPlugin],
  }),
  'POST' // Method
);

// Listen to sync event (native Background Sync API)
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-posts') {
    event.waitUntil(syncPosts());
  }
});

async function syncPosts() {
  const db = await openDatabase();
  const pendingPosts = await db.getAll('pending-posts');
  
  for (const post of pendingPosts) {
    try {
      const response = await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(post.data),
      });
      
      if (response.ok) {
        await db.delete('pending-posts', post.id);
      }
    } catch (error) {
      console.error('Sync failed:', error);
    }
  }
}

Example: Client-side offline queue manager

// offlineQueue.ts
import { openDB, DBSchema } from 'idb';

interface QueueItem {
  id: string;
  url: string;
  method: string;
  headers: Record<string, string>;
  body: string;
  timestamp: number;
  retries: number;
}

interface QueueDB extends DBSchema {
  'queue': {
    key: string;
    value: QueueItem;
    indexes: { 'timestamp': number };
  };
}

class OfflineQueue {
  private db!: IDBPDatabase<QueueDB>;

  async init() {
    this.db = await openDB<QueueDB>('offline-queue', 1, {
      upgrade(db) {
        const store = db.createObjectStore('queue', { keyPath: 'id' });
        store.createIndex('timestamp', 'timestamp');
      },
    });

    // Listen for online event
    window.addEventListener('online', () => this.processQueue());
  }

  async add(url: string, options: RequestInit): Promise<void> {
    const item: QueueItem = {
      id: crypto.randomUUID(),
      url,
      method: options.method || 'GET',
      headers: options.headers as Record<string, string>,
      body: options.body as string,
      timestamp: Date.now(),
      retries: 0,
    };

    await this.db.add('queue', item);
    
    // Try to process immediately
    if (navigator.onLine) {
      this.processQueue();
    }
  }

  async processQueue(): Promise<void> {
    const items = await this.db.getAll('queue');
    
    for (const item of items) {
      try {
        const response = await fetch(item.url, {
          method: item.method,
          headers: item.headers,
          body: item.body,
        });

        if (response.ok) {
          await this.db.delete('queue', item.id);
          console.log('Synced:', item.url);
        } else if (response.status >= 400 && response.status < 500) {
          // Client error, don't retry
          await this.db.delete('queue', item.id);
        } else {
          // Server error, retry
          item.retries++;
          if (item.retries > 5) {
            await this.db.delete('queue', item.id);
          } else {
            await this.db.put('queue', item);
          }
        }
      } catch (error) {
        console.error('Queue processing failed:', error);
      }
    }
  }

  async getPendingCount(): Promise<number> {
    return (await this.db.getAll('queue')).length;
  }
}

export const offlineQueue = new OfflineQueue();

// Usage in React
export function useOfflineQueue() {
  const [pendingCount, setPendingCount] = useState(0);

  useEffect(() => {
    const updateCount = async () => {
      const count = await offlineQueue.getPendingCount();
      setPendingCount(count);
    };

    updateCount();
    const interval = setInterval(updateCount, 5000);
    return () => clearInterval(interval);
  }, []);

  return { pendingCount };
}

16.5 Cache API Storage Management

Feature Purpose API & Usage Best Practices
Cache Storage API Store Request/Response pairs, Multiple named caches, Version control, Programmatic control caches.open(name), .put(), .match(), .delete() Version cache names. Clean old versions. Use separate caches by type. Max 50MB per origin
Cache Versioning Update strategies, Migrate data, Remove old caches, Prevent conflicts Cache names: app-v1, app-v2. Delete old on activate Version in name. Delete old on SW activate. Atomic updates. Test migration path
Storage Quota Check available space, Request persistent storage, Monitor usage, Eviction policy navigator.storage.estimate(), .persist(), quota exceeded events Check quota before caching. Request persist for critical data. Handle quota errors gracefully
IndexedDB Structured data storage, Large datasets, Complex queries, Transactions, Indexes idb library for promises. Stores, indexes, cursors. 50MB+ storage Use idb wrapper. Version schema. Index frequently queried. Transaction for consistency
LocalStorage & SessionStorage Simple key-value, Synchronous, 5-10MB limit, String only, Tab/session scope localStorage.setItem(key, value), .getItem(). JSON stringify objects Use for small data. Not for sensitive data. Synchronous blocks. Prefer IndexedDB/Cache
Storage Management Clear old data, Handle quota errors, Prioritize important data, Eviction strategy LRU eviction. Timestamp entries. Clear on version update. Monitor usage Set expiration. Clear stale data weekly. Prioritize app shell. User control to clear

Example: Cache management in service worker

// service-worker.js
const CACHE_VERSION = 'v3';
const CACHE_NAMES = {
  static: `static-${CACHE_VERSION}`,
  dynamic: `dynamic-${CACHE_VERSION}`,
  images: `images-${CACHE_VERSION}`,
};

const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/manifest.json',
  '/offline.html',
];

// Install - precache static assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAMES.static)
      .then((cache) => cache.addAll(STATIC_ASSETS))
      .then(() => self.skipWaiting())
  );
});

// Activate - clean up old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => !Object.values(CACHE_NAMES).includes(name))
          .map((name) => {
            console.log('Deleting old cache:', name);
            return caches.delete(name);
          })
      );
    }).then(() => self.clients.claim())
  );
});

// Check and manage storage quota
async function checkStorageQuota() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const { usage, quota } = await navigator.storage.estimate();
    const percentUsed = (usage / quota) * 100;
    
    console.log(`Storage: ${usage} / ${quota} bytes (${percentUsed.toFixed(2)}%)`);
    
    if (percentUsed > 80) {
      // Clean up old caches
      await cleanupOldEntries();
    }
  }
}

async function cleanupOldEntries() {
  const cache = await caches.open(CACHE_NAMES.dynamic);
  const requests = await cache.keys();
  
  // Delete oldest 25% of entries
  const toDelete = Math.floor(requests.length * 0.25);
  for (let i = 0; i < toDelete; i++) {
    await cache.delete(requests[i]);
  }
}

Example: Storage quota monitoring

// storageManager.ts
export class StorageManager {
  async getStorageInfo() {
    if (!('storage' in navigator)) {
      return { usage: 0, quota: 0, percentage: 0, isPersisted: false };
    }

    const estimate = await navigator.storage.estimate();
    const usage = estimate.usage || 0;
    const quota = estimate.quota || 0;
    const percentage = (usage / quota) * 100;
    const isPersisted = await navigator.storage.persisted();

    return { usage, quota, percentage, isPersisted };
  }

  async requestPersistentStorage(): Promise<boolean> {
    if ('storage' in navigator && 'persist' in navigator.storage) {
      return await navigator.storage.persist();
    }
    return false;
  }

  formatBytes(bytes: number): string {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
  }

  async clearAllStorage() {
    // Clear all caches
    const cacheNames = await caches.keys();
    await Promise.all(cacheNames.map(name => caches.delete(name)));

    // Clear IndexedDB
    const databases = await indexedDB.databases();
    databases.forEach(db => indexedDB.deleteDatabase(db.name!));

    // Clear localStorage
    localStorage.clear();
    sessionStorage.clear();

    console.log('All storage cleared');
  }
}

// React component to show storage info
export function StorageMonitor() {
  const [info, setInfo] = useState<any>(null);

  useEffect(() => {
    const manager = new StorageManager();
    
    const update = async () => {
      const storageInfo = await manager.getStorageInfo();
      setInfo(storageInfo);
    };

    update();
    const interval = setInterval(update, 30000); // Update every 30s
    return () => clearInterval(interval);
  }, []);

  if (!info) return null;

  const manager = new StorageManager();

  return (
    <div className="storage-monitor">
      <h3>Storage Usage</h3>
      <p>{manager.formatBytes(info.usage)} / {manager.formatBytes(info.quota)}</p>
      <p>{info.percentage.toFixed(2)}% used</p>
      <p>Persistent: {info.isPersisted ? 'Yes' : 'No'}</p>
      {info.percentage > 80 && (
        <p className="warning">Storage almost full!</p>
      )}
    </div>
  );
}

16.6 App Shell Architecture Performance

Concept Description Implementation Performance Benefits
App Shell Pattern Minimal HTML/CSS/JS for UI shell. Cache shell, load content dynamically. Instant load Precache shell assets. Critical CSS inline. Lazy load content. Route-based code split Sub-1s load. FCP <1s. TTI <3s. 90+ Lighthouse score. Perceived performance boost
Critical Rendering Path Inline critical CSS, Defer non-critical JS, Async fonts, Preload key resources <style> critical CSS. <link rel="preload">. async/defer scripts. font-display: swap FCP <1s. No render-blocking. Progressive rendering. Fast first paint. Better UX
Code Splitting Split by route, feature, vendor. Lazy load on demand. Reduce initial bundle React.lazy, dynamic import(), route-based splitting. Webpack chunks. Vite code split Initial bundle <200KB. 50-70% reduction. Faster TTI. Load only what's needed
Resource Hints Preload, prefetch, preconnect, dns-prefetch. Guide browser optimization <link rel="preload|prefetch|preconnect|dns-prefetch" href="..." /> Preload critical (fonts, CSS). Prefetch next page. Preconnect APIs. Faster resource load
Skeleton Screens Show UI structure while loading. Better perceived performance than spinners CSS placeholder. Animate pulse. Match real content layout. Replace with real data Perceived load 30% faster. Reduce bounce. Professional UX. No jarring layout shifts
Performance Budget Set limits: bundle size, load time, metrics. Enforce in CI. Track over time Lighthouse CI. webpack-bundle-analyzer. size-limit. Fail build if exceeded JS <200KB. FCP <1s. TTI <3s. LCP <2.5s. Maintain performance discipline

Example: App shell HTML structure

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PWA App Shell</title>
  
  <!-- Critical CSS inlined -->
  <style>
    /* App shell critical styles - inline for instant render */
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; }
    .shell-header { height: 56px; background: #1976d2; color: white; }
    .shell-nav { width: 200px; background: #f5f5f5; }
    .shell-content { flex: 1; }
    .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); }
  </style>
  
  <!-- Resource hints -->
  <link rel="preconnect" href="https://api.example.com">
  <link rel="dns-prefetch" href="https://cdn.example.com">
  <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/app.js" as="script">
  
  <!-- Non-critical CSS -->
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  
  <link rel="manifest" href="/manifest.json">
  <meta name="theme-color" content="#1976d2">
</head>
<body>
  <!-- App Shell - loads instantly from cache -->
  <div id="app-shell">
    <header class="shell-header">
      <h1>My PWA</h1>
    </header>
    
    <div class="shell-layout">
      <nav class="shell-nav">
        <!-- Navigation skeleton -->
        <div class="skeleton" style="height: 40px; margin: 10px;"></div>
        <div class="skeleton" style="height: 40px; margin: 10px;"></div>
        <div class="skeleton" style="height: 40px; margin: 10px;"></div>
      </nav>
      
      <main class="shell-content">
        <!-- Content skeleton -->
        <div class="skeleton" style="height: 200px; margin: 20px;"></div>
        <div class="skeleton" style="height: 100px; margin: 20px;"></div>
      </main>
    </div>
  </div>
  
  <!-- React root - content loaded dynamically -->
  <div id="root"></div>
  
  <!-- Defer non-critical JavaScript -->
  <script src="/app.js" defer></script>
  <script>
    // Register service worker
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js');
      });
    }
  </script>
</body>
</html>

Example: React app shell with code splitting

// App.tsx - Route-based code splitting
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AppShell } from './components/AppShell';
import { SkeletonLoader } from './components/SkeletonLoader';

// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

export function App() {
  return (
    <BrowserRouter>
      <AppShell>
        <Suspense fallback={<SkeletonLoader />}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/dashboard" element={<Dashboard />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="/settings" element={<Settings />} />
          </Routes>
        </Suspense>
      </AppShell>
    </BrowserRouter>
  );
}

// components/AppShell.tsx - Persistent shell
export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Header />
      <div className="layout">
        <Navigation />
        <main className="content">{children}</main>
      </div>
      <Footer />
    </>
  );
}

// components/SkeletonLoader.tsx
export function SkeletonLoader() {
  return (
    <div className="skeleton-container">
      <div className="skeleton skeleton-title" />
      <div className="skeleton skeleton-text" />
      <div className="skeleton skeleton-text" />
      <div className="skeleton skeleton-card" />
    </div>
  );
}

// vite.config.ts - Manual chunks for optimal splitting
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'ui-components': ['./src/components/Button', './src/components/Input'],
        },
      },
    },
  },
});
PWA Performance Checklist:
  • First Load: FCP <1s, LCP <2.5s, TTI <3s. Lighthouse score 90+
  • Bundle Size: Initial JS <200KB. Total <500KB. Use code splitting
  • Caching: Cache app shell. Stale-while-revalidate for content. 50MB cache limit
  • Offline: All routes work offline. Background sync for mutations. Show network status
  • Install: Manifest.json complete. Icons 192x192, 512x512. Install prompt UX

Progressive Web App Summary

  • Service Workers: Workbox for caching. CacheFirst for static. NetworkFirst for API. Offline fallback
  • Manifest: Complete manifest.json. Icons all sizes. Maskable icons. Install prompt. theme_color
  • Push Notifications: VAPID keys. User permission. Payload <4KB. Handle click. 1-2 per day max
  • Background Sync: Queue failed requests. Workbox BackgroundSyncPlugin. Retry on online. User feedback
  • Storage: Cache API for responses. IndexedDB for data. Monitor quota. LRU eviction. Persistent storage
  • App Shell: Instant load shell. Inline critical CSS. Code split routes. Skeleton screens. Sub-1s FCP
PWA Production Standards: Achieve Lighthouse PWA score 100, offline-capable, installable, sub-3s TTI, push notifications (opt-in), and background sync for resilience. PWA = native-like experience on web.

17. Browser Compatibility Modern Implementation

17.1 Babel Polyfill Core-js ES6+ Support

Tool/Feature Purpose Configuration Best Practices
Babel Transpile modern JS to ES5. Transform JSX, TypeScript. Plugin ecosystem. Source maps .babelrc or babel.config.js. Presets: @babel/preset-env, -react, -typescript Use preset-env with browserslist. Enable modules: false for tree-shaking. Source maps in dev
@babel/preset-env Smart transpilation based on target browsers. Only polyfill what's needed. Auto browser detection targets: { browsers: ['>0.5%', 'not dead'] }. useBuiltIns: 'usage' or 'entry' Use useBuiltIns: 'usage' for optimal size. Set browserslist in package.json. Update targets yearly
core-js Polyfill library. ES6+ features, Promise, Symbol, Array methods, Object methods, 90KB minified npm install core-js@3. Import: import 'core-js/stable' or auto with useBuiltIns Use core-js@3. Automatic imports with babel useBuiltIns: 'usage'. Only polyfill used features
Polyfill Strategies Entry point, Usage-based, Feature detection, Polyfill.io service, Conditional loading Usage: smallest bundle. Entry: explicit control. Polyfill.io: CDN-based, dynamic Usage-based for apps. Entry for libraries. Polyfill.io for multi-browser. Test without polyfills
Modern Features Support async/await, Promises, Class properties, Optional chaining, Nullish coalescing, BigInt Babel transforms syntax. core-js polyfills APIs. regenerator-runtime for async/await Check caniuse.com. Feature detection before use. Graceful degradation. Progressive enhancement
Bundle Size Impact Polyfills add 20-50KB. Target modern browsers. Use differential loading. Monitor size webpack-bundle-analyzer. Monitor polyfill size. Exclude from modern bundle Target last 2 versions. 20KB polyfills for legacy. 0KB for modern. Use type="module"

Example: Babel configuration with optimal polyfilling

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // Target browsers from browserslist
        targets: {
          browsers: [
            '>0.5%',
            'not dead',
            'not op_mini all',
            'not IE 11'
          ]
        },
        
        // Automatic polyfill injection based on usage
        useBuiltIns: 'usage',
        
        // Use core-js version 3
        corejs: { version: 3, proposals: true },
        
        // Preserve ES modules for tree-shaking
        modules: false,
        
        // Include regenerator for async/await
        exclude: ['transform-regenerator'],
      }
    ],
    '@babel/preset-react',
    '@babel/preset-typescript'
  ],
  
  plugins: [
    // Optional chaining and nullish coalescing (if not in preset-env)
    '@babel/plugin-proposal-optional-chaining',
    '@babel/plugin-proposal-nullish-coalescing-operator',
    
    // Class properties
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    
    // Runtime helpers (reduce duplication)
    ['@babel/plugin-transform-runtime', {
      corejs: false, // Don't polyfill (handled by useBuiltIns)
      helpers: true, // Extract helpers
      regenerator: true,
      useESModules: true
    }]
  ],
  
  env: {
    production: {
      plugins: [
        // Remove console.log in production
        'transform-remove-console',
        // Dead code elimination
        'transform-remove-undefined'
      ]
    }
  }
};

// package.json - browserslist
{
  "browserslist": {
    "production": [
      ">0.5%",
      "not dead",
      "not op_mini all",
      "not IE 11"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "legacy": [
      "IE 11"
    ]
  }
}

Example: Feature detection and polyfill loading

// polyfills.ts - Conditional polyfill loading
export async function loadPolyfills() {
  const polyfills: Promise<void>[] = [];

  // Check for Promise support
  if (!window.Promise) {
    polyfills.push(import('core-js/features/promise'));
  }

  // Check for fetch support
  if (!window.fetch) {
    polyfills.push(import('whatwg-fetch'));
  }

  // Check for IntersectionObserver
  if (!('IntersectionObserver' in window)) {
    polyfills.push(import('intersection-observer'));
  }

  // Check for ResizeObserver
  if (!('ResizeObserver' in window)) {
    polyfills.push(import('@juggle/resize-observer').then(m => {
      window.ResizeObserver = m.ResizeObserver;
    }));
  }

  // Wait for all polyfills to load
  await Promise.all(polyfills);
}

// main.tsx - Load polyfills before app
import { loadPolyfills } from './polyfills';

loadPolyfills().then(() => {
  // Import and render app after polyfills loaded
  import('./App').then(({ App }) => {
    const root = document.getElementById('root');
    ReactDOM.createRoot(root!).render(<App />);
  });
});

// Alternative: Polyfill.io service (CDN-based)
// index.html
<script src="https://polyfill.io/v3/polyfill.min.js?features=default,fetch,IntersectionObserver,ResizeObserver"></script>

17.2 PostCSS Autoprefixer Vendor Prefixes

Tool Purpose Configuration Best Practices
PostCSS CSS transformation pipeline. Plugin architecture. Autoprefixer, CSS modules, nesting postcss.config.js. Plugins array. Integrate with webpack/Vite/Next Use with bundler. Enable source maps. Chain plugins efficiently. Cache in production
Autoprefixer Auto vendor prefixes (-webkit, -moz, -ms). Based on browserslist. Remove outdated prefixes autoprefixer({ grid: 'autoplace' }). Uses browserslist config Always use with browserslist. Enable CSS Grid support. Update regularly. No manual prefixes
CSS Modern Features Grid, Flexbox, Custom Properties, calc(), clamp(), aspect-ratio, container queries Autoprefixer handles most. Polyfills for custom properties (IE11). Progressive enhancement Write modern CSS. Autoprefixer adds prefixes. Test in target browsers. Use @supports
postcss-preset-env Use future CSS today. Stage 3 features. Nesting, custom media queries, color functions postcss-preset-env({ stage: 3, features: { 'nesting-rules': true } }) Stage 3 is stable. Enable specific features. More than autoprefixer. Use with caution
CSS Modules Scoped CSS, Local class names, Composition, No global conflicts, Type-safe with TS postcss-modules. Import: import styles from './App.module.css' Use for component styles. Global for resets. Compose common styles. Generate types
PostCSS Plugins cssnano (minify), postcss-nesting, postcss-custom-properties, postcss-import Chain plugins. Order matters. cssnano last. Import first. Optimize for prod Minimal plugins. cssnano in prod. Import for @import. Nesting for cleaner CSS

Example: Complete PostCSS configuration

// postcss.config.js
module.exports = {
  plugins: [
    // Import support
    require('postcss-import')({
      path: ['src/styles']
    }),
    
    // Nesting (like Sass)
    require('postcss-nesting'),
    
    // Modern CSS features with autoprefixer included
    require('postcss-preset-env')({
      stage: 3,
      features: {
        'nesting-rules': true,
        'custom-media-queries': true,
        'custom-properties': false, // Use native CSS variables
      },
      autoprefixer: {
        grid: 'autoplace',
        flexbox: 'no-2009'
      }
    }),
    
    // Or standalone autoprefixer
    // require('autoprefixer'),
    
    // Minification in production
    process.env.NODE_ENV === 'production' ? require('cssnano')({
      preset: ['default', {
        discardComments: { removeAll: true },
        normalizeWhitespace: true,
        reduceIdents: false, // Keep animation names
        zindex: false // Don't optimize z-index
      }]
    }) : false
  ].filter(Boolean)
};

// Example CSS that will be prefixed
/* Input CSS */
.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  
  & .item {
    display: flex;
    align-items: center;
    
    &:hover {
      transform: scale(1.05);
    }
  }
}

.modern {
  aspect-ratio: 16 / 9;
  container-type: inline-size;
  
  @supports (aspect-ratio: 1) {
    /* Modern browsers */
  }
}

/* Output CSS (with autoprefixer) */
.container {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: (minmax(250px, 1fr))[auto-fit];
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

.container .item {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
}

.container .item:hover {
  -webkit-transform: scale(1.05);
  -ms-transform: scale(1.05);
  transform: scale(1.05);
}

17.3 Can I Use Browser Feature Detection

Tool/Approach Purpose Implementation Best Practices
caniuse.com Browser support database. Check feature support. Usage statistics. Known issues Search feature. Check target browsers. Review known issues. Export to browserslist Check before using new features. 95%+ support is safe. Check mobile support. Review notes
@supports CSS Feature queries in CSS. Progressive enhancement. Fallbacks. Test property support @supports (display: grid) { }. Test property:value pairs. Combine with and/or Provide fallbacks first. Enhance with @supports. Test one property. Use for critical features
JavaScript Feature Detection Check API availability. if ('feature' in object). Modernizr library. Dynamic polyfilling if ('IntersectionObserver' in window) { }. Check before use. Load polyfill if missing Always check APIs. Provide fallbacks. Load polyfills conditionally. Test in target browsers
Modernizr Feature detection library. 150+ tests. Custom builds. HTML classes. JS API Custom build at modernizr.com. Adds classes to <html>. JS: Modernizr.feature Custom build only (5-10KB). HTML classes for CSS. JS API for conditionals. Complement polyfills
browserslist Define target browsers. Shared config. Used by Babel, Autoprefixer, ESLint .browserslistrc or package.json. Queries: '>0.5%', 'last 2 versions', 'not dead' Single source of truth. Update yearly. Separate prod/dev. Use caniuse-lite data
User Agent Detection Last resort. Browser/version detection. Mobile vs desktop. Known browser bugs navigator.userAgent. Libraries: ua-parser-js, bowser. Server-side better Avoid if possible. Feature detection preferred. Use for specific bug workarounds. Server-side

Example: Feature detection patterns

// CSS @supports for progressive enhancement
/* Fallback for older browsers */
.grid-container {
  display: flex;
  flex-wrap: wrap;
}

/* Enhanced for browsers with Grid support */
@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 2rem;
  }
}

/* Container queries with fallback */
.card {
  padding: 1rem;
}

@supports (container-type: inline-size) {
  .container {
    container-type: inline-size;
  }
  
  @container (min-width: 400px) {
    .card {
      padding: 2rem;
    }
  }
}

// JavaScript feature detection
class FeatureDetector {
  static hasIntersectionObserver(): boolean {
    return 'IntersectionObserver' in window;
  }

  static hasServiceWorker(): boolean {
    return 'serviceWorker' in navigator;
  }

  static hasWebGL(): boolean {
    try {
      const canvas = document.createElement('canvas');
      return !!(
        canvas.getContext('webgl') || 
        canvas.getContext('experimental-webgl')
      );
    } catch {
      return false;
    }
  }

  static hasLocalStorage(): boolean {
    try {
      localStorage.setItem('test', 'test');
      localStorage.removeItem('test');
      return true;
    } catch {
      return false;
    }
  }

  static supportsWebP(): Promise<boolean> {
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => resolve(img.width === 1);
      img.onerror = () => resolve(false);
      img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=';
    });
  }

  static getCSSSupport(): Record<string, boolean> {
    return {
      grid: CSS.supports('display', 'grid'),
      flexbox: CSS.supports('display', 'flex'),
      customProperties: CSS.supports('--custom', 'value'),
      aspectRatio: CSS.supports('aspect-ratio', '16/9'),
      containerQueries: CSS.supports('container-type', 'inline-size'),
      backdrop: CSS.supports('backdrop-filter', 'blur(10px)'),
    };
  }
}

// Usage in React
export function useFeatureDetection() {
  const [features, setFeatures] = useState({
    intersectionObserver: false,
    serviceWorker: false,
    webp: false,
    webgl: false,
    cssGrid: false,
  });

  useEffect(() => {
    const detect = async () => {
      setFeatures({
        intersectionObserver: FeatureDetector.hasIntersectionObserver(),
        serviceWorker: FeatureDetector.hasServiceWorker(),
        webp: await FeatureDetector.supportsWebP(),
        webgl: FeatureDetector.hasWebGL(),
        cssGrid: CSS.supports('display', 'grid'),
      });
    };
    detect();
  }, []);

  return features;
}

// Conditional rendering based on features
function ImageGallery() {
  const { webp } = useFeatureDetection();
  const imageExt = webp ? 'webp' : 'jpg';
  
  return <img src={`/images/photo.${imageExt}`} alt="Photo" />;
}

17.4 Progressive Enhancement Feature Support

Strategy Description Implementation Benefits
Progressive Enhancement Start with basic HTML/CSS. Layer JavaScript. Feature detection. Graceful degradation Core content in HTML. CSS for presentation. JS for enhancement. Works without JS Accessible by default. SEO-friendly. Resilient. Fast baseline. Works everywhere
Content First HTML semantic markup. No JS required for content. Forms work without JS. Links navigate Use <form action="/submit">. <a href="/page">. Server-side rendering. Semantic HTML5 Accessibility. SEO. No-JS users. Slow connections. Screen readers. Search crawlers
CSS Enhancement Basic layout without modern features. @supports for enhancement. Fallback styles first Flexbox fallback for Grid. Basic colors before gradients. Simple animations Works on all browsers. Better with modern features. Graceful degradation. No breaking
JavaScript Enhancement Check feature before use. Load polyfills conditionally. Provide no-JS alternative <noscript> fallback. Progressive loading. Feature detection. Error boundaries Works without JS. Better with JS. Faster for modern browsers. Resilient to failures
Mobile First Design for mobile baseline. Enhance for desktop. Touch-first interactions. Responsive Min-width media queries. Mobile layout default. Touch events. Responsive images Better mobile UX. Faster on mobile. Progressive enhancement. Desktop gets extras
Network Resilience Work offline. Queue requests. Cache content. Show stale data. Background sync Service workers. IndexedDB. Offline indicators. Retry logic. Optimistic UI Works on slow/offline. Better UX. Resilient. Mobile-friendly. Emerging markets

Example: Progressive enhancement patterns

// HTML - Works without JavaScript
<!-- Form with server-side fallback -->
<form action="/api/submit" method="POST" class="contact-form">
  <label for="email">Email:</label>
  <input type="email" id="email" name="email" required>
  
  <label for="message">Message:</label>
  <textarea id="message" name="message" required></textarea>
  
  <button type="submit">Send Message</button>
</form>

<!-- No-JS fallback -->
<noscript>
  <p>This site works best with JavaScript enabled, but core functionality is available without it.</p>
</noscript>

// CSS - Progressive enhancement with @supports
/* Basic layout (works everywhere) */
.gallery {
  margin: 0 auto;
  max-width: 1200px;
}

.gallery-item {
  margin-bottom: 20px;
}

/* Enhanced with Flexbox */
@supports (display: flex) {
  .gallery {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
  }
  
  .gallery-item {
    flex: 0 0 calc(33.333% - 20px);
    margin-bottom: 0;
  }
}

/* Further enhanced with Grid */
@supports (display: grid) {
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
  }
  
  .gallery-item {
    flex: unset;
  }
}

// JavaScript - Progressive enhancement
class ContactForm {
  constructor(formElement: HTMLFormElement) {
    this.form = formElement;
    
    // Only enhance if fetch is available
    if ('fetch' in window) {
      this.enhanceForm();
    }
    // Otherwise, form submits normally to server
  }

  private enhanceForm() {
    this.form.addEventListener('submit', async (e) => {
      e.preventDefault(); // Only prevent if JS works
      
      const formData = new FormData(this.form);
      
      try {
        const response = await fetch(this.form.action, {
          method: 'POST',
          body: formData,
        });

        if (response.ok) {
          this.showSuccess();
        } else {
          // Fallback to normal form submit on error
          this.form.submit();
        }
      } catch (error) {
        // Network error - fallback to normal submit
        this.form.submit();
      }
    });
  }

  private showSuccess() {
    // Enhanced UI feedback (only with JS)
    this.form.innerHTML = '<p>Thank you! Message sent.</p>;
  }
}

// Initialize only if DOM is ready and JS enabled
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initForms);
} else {
  initForms();
}

function initForms() {
  const forms = document.querySelectorAll<HTMLFormElement>('.contact-form');
  forms.forEach(form => new ContactForm(form));
}

// React - Progressive enhancement with hydration
// Server-side render first, then hydrate
import { hydrateRoot } from 'react-dom/client';

function App() {
  return (
    <form action="/api/submit" method="POST">
      {/* Server renders working form */}
      {/* Client enhances with React */}
    </form>
  );
}

// Hydrate only if browser supports modules
if ('noModule' in HTMLScriptElement.prototype) {
  const root = document.getElementById('root')!;
  hydrateRoot(root, <App />);
}
// Otherwise, server-rendered HTML works as-is

17.5 Cross-browser Testing BrowserStack

Tool/Service Features Use Cases Best Practices
BrowserStack NEW 3000+ browsers, Real devices, Live testing, Automated tests, Screenshots, Network throttling Manual testing, Selenium/Playwright integration, Visual regression, Mobile device testing Test critical flows. Automate common paths. Test on real devices. Check latest browsers monthly
Sauce Labs Browser cloud, Mobile testing, CI/CD integration, Parallel tests, Analytics, Video recording Automated E2E tests, Cross-browser CI, Performance testing, Mobile app testing Integrate with CI. Run tests in parallel. Video for failures. Test matrix optimization
LambdaTest Live testing, Screenshot testing, Automated testing, Local tunnel, Responsive testing Visual testing, Responsive layouts, Browser compatibility, Geolocation testing Bulk screenshots. Responsive testing. Local tunnel for dev. Compare screenshots
Playwright Chromium, Firefox, WebKit. Headless testing. Auto-wait. Network interception. Tracing Cross-browser E2E tests. Component testing. Visual regression. CI automation Test in 3 engines. Parallel execution. Trace for debugging. Screenshots on failure. Retry flaky
Testing Strategy Test pyramid. Critical flows manual. Smoke tests automated. Visual regression. Performance Login, checkout, forms. Core user journeys. Different screen sizes. Network conditions Automate repetitive. Manual for UX. Test on real devices. Check accessibility. Performance budget
Browser DevTools Chrome DevTools, Firefox DevTools, Safari Web Inspector. Device emulation. Network throttling Debug issues. Performance profiling. Mobile emulation. Console logging. Network analysis Use native DevTools first. Device emulation for quick checks. Network throttling for slow connections

Example: Playwright cross-browser tests

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html'],
    ['junit', { outputFile: 'results.xml' }],
    ['json', { outputFile: 'results.json' }]
  ],
  
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  projects: [
    // Desktop browsers
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    
    // Mobile browsers
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 13'] },
    },
    
    // Tablet
    {
      name: 'iPad',
      use: { ...devices['iPad Pro'] },
    },
  ],

  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

// e2e/critical-flow.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Critical User Flow', () => {
  test('should complete checkout process', async ({ page }) => {
    await page.goto('/');
    
    // Add to cart
    await page.click('[data-testid="add-to-cart"]');
    await expect(page.locator('.cart-badge')).toHaveText('1');
    
    // Go to checkout
    await page.click('[data-testid="cart-icon"]');
    await page.click('[data-testid="checkout-button"]');
    
    // Fill form
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="card"]', '4242424242424242');
    
    // Submit
    await page.click('[data-testid="submit-payment"]');
    
    // Verify success
    await expect(page.locator('.success-message')).toBeVisible();
  });
  
  test('should work offline', async ({ page, context }) => {
    // Go offline
    await context.setOffline(true);
    await page.goto('/');
    
    // Should show offline indicator
    await expect(page.locator('.offline-indicator')).toBeVisible();
    
    // Content should still be accessible
    await expect(page.locator('h1')).toBeVisible();
  });
});

// BrowserStack integration
// wdio.conf.js for BrowserStack
exports.config = {
  user: process.env.BROWSERSTACK_USERNAME,
  key: process.env.BROWSERSTACK_ACCESS_KEY,
  
  capabilities: [
    {
      browserName: 'Chrome',
      browserVersion: 'latest',
      'bstack:options': {
        os: 'Windows',
        osVersion: '11',
      }
    },
    {
      browserName: 'Safari',
      browserVersion: 'latest',
      'bstack:options': {
        os: 'OS X',
        osVersion: 'Ventura',
      }
    },
    {
      browserName: 'Chrome',
      'bstack:options': {
        deviceName: 'Samsung Galaxy S23',
        realMobile: true,
      }
    }
  ],
  
  services: ['browserstack']
};

17.6 ES Module Legacy Bundle Differential Loading

Strategy Description Implementation Benefits
ES Modules (ESM) Native browser modules. type="module". Dynamic imports. Tree-shaking. Modern browsers <script type="module" src="app.js">. import/export. 95%+ browser support Smaller bundles. No transpilation. Faster. Native tree-shaking. Parallel loading
Legacy Bundle ES5 transpiled. Polyfills included. nomodule attribute. IE11 and old browsers <script nomodule src="app-legacy.js">. Babel transpilation. Full polyfills Support old browsers. Fallback for no-ESM. Separate bundle. Larger size acceptable
Differential Loading Serve modern bundle to modern browsers. Legacy to old browsers. Automatic selection Both module and nomodule scripts. Browser chooses. Vite/Angular CLI built-in 20-40% smaller for modern. No polyfills for 95%. Better performance. Same codebase
Module/Nomodule Pattern Modern browsers ignore nomodule. Old browsers ignore type="module". Automatic fallback Both scripts in HTML. Modern loads module. Old loads nomodule. No JS detection needed Zero config. Automatic. No feature detection. Works everywhere. Easy implementation
Build Configuration Two build outputs. Modern target ES2020. Legacy target ES5. Vite, Angular, custom webpack Vite: build.target = 'esnext'. Legacy plugin. Webpack: dual configs. Different browserslist Automate builds. Single source. Optimize each. Monitor both sizes. Update targets yearly
Bundle Size Comparison Modern: 100KB (no polyfills). Legacy: 150KB (polyfills). 33% savings for modern Analyze both bundles. Modern no core-js. Legacy full polyfills. Separate chunks Modern users get faster. Legacy users still work. Cost-effective. Better metrics

Example: Differential loading setup

// HTML - Module/nomodule pattern
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Differential Loading</title>
  
  <!-- Preload modern bundle for modern browsers -->
  <link rel="modulepreload" href="/assets/app.js">
  
  <!-- Preload legacy bundle for old browsers -->
  <link rel="preload" href="/assets/app-legacy.js" as="script" crossorigin>
</head>
<body>
  <div id="root"></div>
  
  <!-- Modern browsers: ES2020+, no polyfills, smaller -->
  <script type="module" src="/assets/app.js"></script>
  
  <!-- Legacy browsers: ES5, full polyfills, larger -->
  <script nomodule src="/assets/app-legacy.js"></script>
  
  <!-- Safari 10.1 fix (downloads both) -->
  <script>
    !function(){var e=document,t=e.createElement("script");
    if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;
    e.addEventListener("beforeload",function(e){
      if(e.target===t)n=!0;
      else if(!e.target.hasAttribute("nomodule")||!n)return;
      e.preventDefault()},!0),
    t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();
  </script>
</body>
</html>

// vite.config.ts - Differential loading with Vite
import { defineConfig } from 'vite';
import legacy from '@vitejs/plugin-legacy';

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
      modernPolyfills: true,
      renderLegacyChunks: true,
      
      // Additional legacy targets
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    })
  ],
  
  build: {
    // Modern target
    target: 'esnext',
    
    // Ensure code splitting
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        }
      }
    }
  }
});

// Webpack - Manual differential loading
// webpack.modern.config.js
module.exports = {
  output: {
    filename: '[name].js',
    environment: {
      // Modern browser features
      arrowFunction: true,
      const: true,
      destructuring: true,
      forOf: true,
      dynamicImport: true,
      module: true,
    }
  },
  
  target: ['web', 'es2020'],
  
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                targets: { esmodules: true },
                bugfixes: true,
                modules: false,
              }]
            ]
          }
        }
      }
    ]
  }
};

// webpack.legacy.config.js
module.exports = {
  output: {
    filename: '[name]-legacy.js',
  },
  
  target: ['web', 'es5'],
  
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                targets: { ie: 11 },
                useBuiltIns: 'usage',
                corejs: 3,
              }]
            ]
          }
        }
      }
    ]
  }
};

// Build script to generate both bundles
// package.json
{
  "scripts": {
    "build": "npm run build:modern && npm run build:legacy",
    "build:modern": "webpack --config webpack.modern.config.js",
    "build:legacy": "webpack --config webpack.legacy.config.js"
  }
}
Browser Compatibility Strategy:
  • Target Browsers: Last 2 versions, >0.5% usage, not dead. Update yearly. Mobile first
  • Polyfills: Use preset-env with useBuiltIns: 'usage'. Conditional loading. 20-50KB for legacy
  • CSS: Autoprefixer with browserslist. @supports for enhancement. Fallbacks first
  • Testing: Playwright for 3 engines. BrowserStack for real devices. Visual regression
  • Differential Loading: Modern ES2020 bundle. Legacy ES5 bundle. 30% size savings

Browser Compatibility Summary

  • Babel + core-js: Transpile to ES5. Polyfill APIs. useBuiltIns: 'usage'. 20KB modern, 50KB legacy
  • PostCSS + Autoprefixer: Auto vendor prefixes. Based on browserslist. Grid support. CSS nesting
  • Feature Detection: caniuse.com for checking. @supports in CSS. JavaScript detection. Modernizr
  • Progressive Enhancement: HTML first. CSS enhancement. JS layer. Works without JS. Mobile first
  • Cross-browser Testing: BrowserStack/Sauce Labs. Playwright 3 engines. Real devices. Automate CI
  • Differential Loading: type="module" for modern. nomodule for legacy. 30% smaller. Auto selection
Compatibility Checklist: Define browserslist targets, enable Babel preset-env, configure Autoprefixer, implement feature detection, use progressive enhancement, test in 3+ browsers, and ship differential bundles. Support 95%+ users efficiently.