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
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
// 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
Navigation
// 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 >
Navigation Methods
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
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
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)
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' ;
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
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.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
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
// 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 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.
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
// 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 */
}
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
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();
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 = {
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : '''
};
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
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.
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
Cookie Attributes
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.
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)
// 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 = {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'/' : '/'
};
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 >
);
}
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;
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;
}
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
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
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.
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
// 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)
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
URL parameter (?lang=es)
Subdomain (es.example.com)
Path prefix (/es/page)
Cookie (persistent)
localStorage (persistent)
Accept-Language header
navigator.language
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 >
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 >
);
}
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 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
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 >
Legal Requirements
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 >
)
};
}
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
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
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
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!"
// 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
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 >
);
}
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.