1. Component Architecture Implementation Patterns
1.1 React Functional Components Hooks
Hook
Syntax
Description
Use Case
useState
const [state, setState] = useState(initial)
Manages component local state with immutable updates
Form inputs, toggles, counters
useEffect
useEffect(() => {}, [deps])
Handles side effects, subscriptions, data fetching
API calls, DOM manipulation, subscriptions
useContext
const value = useContext(Context)
Consumes React context without wrapper components
Theme, auth, global state access
useReducer
const [state, dispatch] = useReducer(reducer, init)
Complex state logic with predictable state transitions
Form validation, multi-step wizards
useMemo
const value = useMemo(() => compute(), [deps])
Memoizes expensive computations between renders
Large list filtering, complex calculations
useCallback
const fn = useCallback(() => {}, [deps])
Memoizes function references to prevent re-renders
Event handlers in optimized children
useRef
const ref = useRef(initialValue)
Persists mutable value without triggering re-renders
DOM access, storing previous values
useLayoutEffect
useLayoutEffect(() => {}, [deps])
Synchronous effect before browser paint
DOM measurements, animations
Example: Complete functional component with hooks
import { useState, useEffect, useCallback, useMemo } from 'react' ;
function UserProfile ({ userId }) {
const [ user , setUser ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
const fetchUser = async () => {
setLoading ( true );
const response = await fetch ( `/api/users/${ userId }` );
const data = await response. json ();
setUser (data);
setLoading ( false );
};
fetchUser ();
}, [userId]);
const handleUpdate = useCallback (( updates ) => {
setUser ( prev => ({ ... prev, ... updates }));
}, []);
const displayName = useMemo (() =>
user ? `${ user . firstName } ${ user . lastName }` : 'Unknown' ,
[user]
);
if (loading) return < div >Loading...</ div >;
return (
< div >
< h1 >{displayName}</ h1 >
< button onClick = {() => handleUpdate ({ active: true })}>
Activate
</ button >
</ div >
);
}
1.2 Vue 3 Composition API Setup
Function
Syntax
Description
Use Case
ref
const count = ref(0)
Creates reactive primitive value with .value access
Simple reactive state
reactive
const state = reactive({})
Creates deeply reactive object proxy
Complex nested state
computed
const doubled = computed(() => count.value * 2)
Cached derived state recalculated on dependency change
Derived values, filters
watch
watch(source, (newVal, oldVal) => {})
Performs side effects on reactive state changes
API calls on input change
watchEffect
watchEffect(() => console.log(count.value))
Automatically tracks dependencies and runs immediately
Logging, analytics
onMounted
onMounted(() => {})
Lifecycle hook after component DOM insertion
Initialize third-party libraries
provide/inject
provide('key', value); const val = inject('key')
Dependency injection without prop drilling
Plugin systems, themes
Example: Vue 3 Composition API component
< script setup >
import { ref, reactive, computed, watch, onMounted } from 'vue' ;
const count = ref ( 0 );
const user = reactive ({
name: 'John' ,
email: 'john@example.com'
});
const displayName = computed (() =>
user.name. toUpperCase ()
);
watch (count, ( newCount , oldCount ) => {
console. log ( `Count changed from ${ oldCount } to ${ newCount }` );
});
onMounted (() => {
console. log ( 'Component mounted' );
});
const increment = () => count.value ++ ;
</ script >
< template >
< div >
< h1 >{{ displayName }}</ h1 >
< p >Count: {{ count }}</ p >
< button @click = "increment" >Increment</ button >
</ div >
</ template >
1.3 Angular 17 Standalone Components NEW
Feature
Syntax
Description
Use Case
@Component
@Component({ standalone: true })
Self-contained component without NgModule
Modern Angular architecture
imports
imports: [CommonModule, FormsModule]
Direct dependency declaration in component
Explicit dependency management
Signal
count = signal(0)
Fine-grained reactive primitive for change detection
Performance optimization
computed
doubled = computed(() => count() * 2)
Derived signal automatically updated
Calculated values
effect
effect(() => console.log(count()))
Side effects triggered by signal changes
Logging, analytics
Input/Output
@Input() data; @Output() changed = new EventEmitter()
Component communication interface
Parent-child data flow
Example: Angular 17 standalone component with signals
import { Component, signal, computed } from '@angular/core' ;
import { CommonModule } from '@angular/common' ;
import { FormsModule } from '@angular/forms' ;
@ Component ({
selector: 'app-counter' ,
standalone: true ,
imports: [CommonModule, FormsModule],
template: `
<div>
<h2>Counter: {{ count() }}</h2>
<p>Doubled: {{ doubled() }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
</div>
`
})
export class CounterComponent {
count = signal ( 0 );
doubled = computed (() => this . count () * 2 );
increment () {
this .count. update ( n => n + 1 );
}
decrement () {
this .count. update ( n => n - 1 );
}
}
1.4 Atomic Design System Storybook
Level
Component Type
Description
Examples
Atoms
Basic building blocks
Smallest functional units, not divisible
Button, Input, Label, Icon
Molecules
Simple groups
Combinations of atoms functioning together
SearchBar, FormField, Card
Organisms
Complex sections
Complex UI sections with distinct functionality
Header, Footer, ProductCard
Templates
Page layouts
Page-level structure without real content
HomePageLayout, DashboardLayout
Pages
Instances
Templates with real content and data
HomePage, UserProfilePage
Example: Storybook configuration and component story
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react' ;
import { Button } from './Button' ;
const meta : Meta < typeof Button> = {
title: 'Atoms/Button' ,
component: Button,
tags: [ 'autodocs' ],
argTypes: {
variant: {
control: 'select' ,
options: [ 'primary' , 'secondary' , 'danger' ]
},
size: {
control: 'radio' ,
options: [ 'sm' , 'md' , 'lg' ]
}
}
};
export default meta;
type Story = StoryObj < typeof Button>;
export const Primary : Story = {
args: {
variant: 'primary' ,
children: 'Click Me' ,
size: 'md'
}
};
export const Secondary : Story = {
args: {
variant: 'secondary' ,
children: 'Cancel' ,
size: 'md'
}
};
// SearchBar.stories.tsx (Molecule)
import { SearchBar } from './SearchBar' ;
export default {
title: 'Molecules/SearchBar' ,
component: SearchBar
};
export const Default = () => < SearchBar placeholder = "Search..." />;
Note: Storybook v7+ uses CSF3 format with improved
TypeScript support and automatic documentation generation.
1.5 Props Interface TypeScript Definitions
Pattern
Syntax
Description
Use Case
Basic Interface
interface Props { name: string }
Type-safe prop definitions
Component contracts
Optional Props
title?: string
Props that can be undefined
Non-required attributes
Default Props
name = 'Guest'
Fallback values for props
Default configurations
Union Types
variant: 'primary' | 'secondary'
Restricted string literal values
Variant options
Generic Props
interface Props<T> { data: T }
Reusable type-safe components
List, Table components
Children Prop
children: React.ReactNode
Type for child elements
Container components
Event Handlers
onClick: (e: MouseEvent) => void
Type-safe event callbacks
Interactive elements
Extending Props
interface Props extends HTMLAttributes<T>
Inherit native HTML props
Wrapper components
Example: Comprehensive TypeScript component interfaces
// Basic Props Interface
interface ButtonProps {
variant : 'primary' | 'secondary' | 'danger' ;
size ?: 'sm' | 'md' | 'lg' ;
disabled ?: boolean ;
onClick ?: ( event : React . MouseEvent < HTMLButtonElement >) => void ;
children : React . ReactNode ;
}
function Button ({
variant ,
size = 'md' ,
disabled = false ,
onClick ,
children
} : ButtonProps ) {
return (
< button
className = { `btn btn-${ variant } btn-${ size }` }
disabled = {disabled}
onClick = {onClick}
>
{ children }
</ button >
);
}
// Generic Props with Type Parameter
interface ListProps < T > {
items : T [];
renderItem : ( item : T ) => React . ReactNode ;
keyExtractor : ( item : T ) => string | number ;
}
function List < T >({ items , renderItem , keyExtractor } : ListProps < T >) {
return (
< ul >
{ items . map ( item => (
< li key = { keyExtractor ( item )} >
{ renderItem ( item )}
</ li >
))}
</ ul >
);
}
// Extending HTML Attributes
interface InputProps extends React . InputHTMLAttributes < HTMLInputElement > {
label : string ;
error ?: string ;
}
function Input ({ label , error , ... props } : InputProps ) {
return (
< div >
< label >{label} </ label >
< input { ... props } />
{ error && < span className = "error" > {error} </ span > }
</ div >
);
}
1.6 Component Composition Higher-Order Components
Pattern
Description
Use Case
Example
HOC Pattern
Function that takes component, returns enhanced component
Cross-cutting concerns, auth
withAuth(Component)
Render Props
Component shares code via function prop
Dynamic rendering logic
<Toggle render={(on) => ...} />
Compound Components
Multiple components work together as single unit
Flexible APIs like Select/Option
<Select><Option/></Select>
Container/Presentational
Separate data logic from UI rendering
Reusable UI components
UserContainer + UserView
Custom Hooks
Reusable stateful logic extraction
Shared behavior across components
useAuth(), useFetch()
Example: HOC for authentication and loading states
// Higher-Order Component Pattern
function withAuth < P extends object >(
Component : React . ComponentType < P >
) {
return function AuthenticatedComponent ( props : P ) {
const { user , loading } = useAuth ();
if (loading) return < div >Loading...</ div >;
if ( ! user) return < Navigate to = "/login" />;
return < Component { ... props} user = {user} />;
};
}
// Usage
const ProtectedDashboard = withAuth (Dashboard);
// Render Props Pattern
function DataFetcher ({ url , render }) {
const [ data , setData ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
fetch (url)
. then ( res => res. json ())
. then ( data => {
setData (data);
setLoading ( false );
});
}, [url]);
return render ({ data, loading });
}
// Usage
< DataFetcher
url = "/api/users"
render = {({ data , loading }) =>
loading ? < Spinner /> : < UserList users = {data} />
}
/>
// Compound Components Pattern
const Select = ({ children , value , onChange }) => {
return (
< div className = "select" >
{React.Children. map (children, child =>
React. cloneElement (child, { selected: value, onSelect: onChange })
)}
</ div >
);
};
Select. Option = ({ value , selected , onSelect , children }) => (
< div
className = {selected === value ? 'selected' : '' }
onClick = {() => onSelect (value)}
>
{children}
</ div >
);
// Usage
< Select value = {selected} onChange = {setSelected}>
< Select.Option value = "1" >Option 1</ Select.Option >
< Select.Option value = "2" >Option 2</ Select.Option >
</ Select >
Component Architecture Best Practices
Hooks preferred over class components in modern React for better
composition
Use TypeScript interfaces for type-safe prop validation
Follow Atomic Design for consistent component hierarchy
Storybook essential for component development and documentation
Choose composition pattern based on reusability needs: HOC for cross-cutting, Render Props for dynamic
logic, Compound for flexible APIs
2. Modern State Management Implementation
Feature
Syntax
Description
Use Case
configureStore
configureStore({ reducer: {} })
Simplifies store setup with good defaults, DevTools
Store initialization
createSlice
createSlice({ name, initialState, reducers })
Generates action creators and reducers automatically
State domain logic
createAsyncThunk
createAsyncThunk('type', async () => {})
Handles async logic with pending/fulfilled/rejected actions
API calls, side effects
createApi
createApi({ baseQuery, endpoints })
RTK Query API service with auto-generated hooks
Data fetching & caching
useSelector
const data = useSelector(state => state.data)
Extracts state with automatic re-render on changes
Access store state
useDispatch
const dispatch = useDispatch()
Returns dispatch function to trigger actions
Update state
// store.ts
import { configureStore } from '@reduxjs/toolkit' ;
import { setupListeners } from '@reduxjs/toolkit/query' ;
import { api } from './api' ;
import counterReducer from './counterSlice' ;
export const store = configureStore ({
reducer: {
counter: counterReducer,
[api.reducerPath]: api.reducer,
},
middleware : ( getDefaultMiddleware ) =>
getDefaultMiddleware (). concat (api.middleware),
});
setupListeners (store.dispatch);
// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit' ;
interface CounterState {
value : number ;
status : 'idle' | 'loading' ;
}
const counterSlice = createSlice ({
name: 'counter' ,
initialState: { value: 0 , status: 'idle' } as CounterState ,
reducers: {
increment : ( state ) => {
state.value += 1 ;
},
decrement : ( state ) => {
state.value -= 1 ;
},
incrementByAmount : ( state , action : PayloadAction < number >) => {
state.value += action.payload;
},
},
});
export const { increment , decrement , incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// api.ts - RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' ;
export const api = createApi ({
reducerPath: 'api' ,
baseQuery: fetchBaseQuery ({ baseUrl: '/api' }),
tagTypes: [ 'User' , 'Post' ],
endpoints : ( builder ) => ({
getUsers: builder. query < User [], void >({
query : () => '/users' ,
providesTags: [ 'User' ],
}),
getUserById: builder. query < User , string >({
query : ( id ) => `/users/${ id }` ,
providesTags : ( result , error , id ) => [{ type: 'User' , id }],
}),
createUser: builder. mutation < User , Partial < User >>({
query : ( body ) => ({
url: '/users' ,
method: 'POST' ,
body,
}),
invalidatesTags: [ 'User' ],
}),
}),
});
export const { useGetUsersQuery , useGetUserByIdQuery , useCreateUserMutation } = api;
// Component usage
function UserList () {
const { data : users , isLoading , error } = useGetUsersQuery ();
const [ createUser ] = useCreateUserMutation ();
if (isLoading) return < div >Loading ...</ div > ;
if (error) return < div >Error loading users </ div > ;
return (
< div >
{ users ?. map ( user => < div key ={ user . id }>{user.name} </ div > )}
< button onClick = {() => createUser ({ name : 'New User' })} >
Add User
</ button >
</ div >
);
}
2.2 Zustand Lightweight Store Creation
Feature
Syntax
Description
Use Case
create
create((set) => ({ ...state }))
Creates store with state and actions in single function
Simple global state
set
set((state) => ({ count: state.count + 1 }))
Updates state immutably, triggers re-renders
State mutations
get
get().propertyName
Reads current state without subscription
Access state in actions
subscribe
store.subscribe((state) => {})
Listen to state changes outside components
Logging, persistence
persist
persist(store, { name: 'key' })
Middleware for localStorage/sessionStorage persistence
State persistence
immer
immer((state) => { state.value++ })
Allows mutable syntax with Immer library
Nested state updates
Example: Zustand store with middleware and actions
import { create } from 'zustand' ;
import { persist, devtools } from 'zustand/middleware' ;
import { immer } from 'zustand/middleware/immer' ;
// Basic store
const useCountStore = create (( set ) => ({
count: 0 ,
increment : () => set (( state ) => ({ count: state.count + 1 })),
decrement : () => set (( state ) => ({ count: state.count - 1 })),
reset : () => set ({ count: 0 }),
}));
// Advanced store with TypeScript, persist, and devtools
interface BearState {
bears : number ;
fish : number ;
actions : {
addBear : () => void ;
removeBear : () => void ;
addFish : ( amount : number ) => void ;
};
}
const useBearStore = create < BearState >()(
devtools (
persist (
immer (( set , get ) => ({
bears: 0 ,
fish: 0 ,
actions: {
addBear : () => set (( state ) => { state.bears += 1 ; }),
removeBear : () => set (( state ) => { state.bears -= 1 ; }),
addFish : ( amount ) => set (( state ) => {
state.fish += amount;
}),
},
})),
{ name: 'bear-storage' }
)
)
);
// Separate selectors for optimization
const useBears = () => useBearStore (( state ) => state.bears);
const useActions = () => useBearStore (( state ) => state.actions);
// Component usage
function BearCounter () {
const bears = useBears ();
const { addBear , removeBear } = useActions ();
return (
< div >
< h1 >Bears: {bears}</ h1 >
< button onClick = {addBear}>Add Bear</ button >
< button onClick = {removeBear}>Remove Bear</ button >
</ div >
);
}
// Async actions
const useUserStore = create (( set , get ) => ({
user: null ,
loading: false ,
fetchUser : async ( id ) => {
set ({ loading: true });
const response = await fetch ( `/api/users/${ id }` );
const user = await response. json ();
set ({ user, loading: false });
},
}));
2.3 Context API Provider Pattern
Feature
Syntax
Description
Use Case
createContext
const Context = createContext(defaultValue)
Creates context object for sharing data
Global state initialization
Provider
<Context.Provider value={data}>
Makes context value available to descendants
Wrapping component tree
useContext
const value = useContext(Context)
Consumes context value in child components
Access shared state
Custom Hook Pattern
const useAuth = () => useContext(AuthContext)
Encapsulates context with validation and helpers
Better DX, type safety
Multiple Contexts
<Theme><Auth><App/></Auth></Theme>
Compose multiple context providers
Separation of concerns
Example: Context API with TypeScript and custom hook
// AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react' ;
interface User {
id : string ;
name : string ;
email : string ;
}
interface AuthContextType {
user : User | null ;
login : ( email : string , password : string ) => Promise < void >;
logout : () => void ;
isAuthenticated : boolean ;
}
const AuthContext = createContext < AuthContextType | undefined >( undefined );
// Custom hook with validation
export function useAuth () {
const context = useContext (AuthContext);
if ( ! context) {
throw new Error ( 'useAuth must be used within AuthProvider' );
}
return context;
}
// Provider component
export function AuthProvider ({ children } : { children : ReactNode }) {
const [ user , setUser ] = useState < User | null >( null );
const login = async ( email : string , password : string ) => {
const response = await fetch ( '/api/login' , {
method: 'POST' ,
body: JSON . stringify ({ email, password }),
});
const userData = await response. json ();
setUser (userData);
};
const logout = () => {
setUser ( null );
};
const value = {
user,
login,
logout,
isAuthenticated: !! user,
};
return < AuthContext . Provider value ={ value }>{children} </ AuthContext.Provider > ;
}
// Usage in component
function Profile () {
const { user , logout , isAuthenticated } = useAuth ();
if ( ! isAuthenticated) {
return < div >Please login </ div > ;
}
return (
< div >
< h1 > Welcome , { user ?. name } </ h1 >
< button onClick = {logout} > Logout </ button >
</ div >
);
}
// App.tsx with provider
function App () {
return (
< AuthProvider >
< Profile />
</ AuthProvider >
);
}
Note: Context API re-renders all consumers when value changes. For performance-critical apps,
split contexts or use useMemo on the value object.
2.4 Recoil Atom Selector Implementation
Feature
Syntax
Description
Use Case
atom
atom({ key: 'id', default: value })
Unit of state that components can subscribe to
Shared state pieces
selector
selector({ key: 'id', get: ({ get }) => {} })
Derived state from atoms or other selectors
Computed values
useRecoilState
const [val, setVal] = useRecoilState(atom)
Read and write atom state (like useState)
Read/write state
useRecoilValue
const val = useRecoilValue(atom)
Read-only access to atom/selector value
Read-only state
useSetRecoilState
const setVal = useSetRecoilState(atom)
Write-only access without subscribing to changes
Write-only operations
atomFamily
atomFamily({ key: 'id', default: (param) => {} })
Creates dynamic atoms based on parameters
List items, dynamic data
selectorFamily
selectorFamily({ key: 'id', get: (param) => {} })
Creates parameterized selectors
Filtered/transformed data
Example: Recoil atoms, selectors, and families
import {
atom,
selector,
atomFamily,
selectorFamily,
useRecoilState,
useRecoilValue
} from 'recoil' ;
// Basic atom
const textState = atom ({
key: 'textState' ,
default: '' ,
});
// Selector for derived state
const charCountState = selector ({
key: 'charCountState' ,
get : ({ get }) => {
const text = get (textState);
return text. length ;
},
});
// Async selector
const currentUserQuery = selector ({
key: 'currentUserQuery' ,
get : async ({ get }) => {
const userId = get (currentUserIdState);
const response = await fetch ( `/api/users/${ userId }` );
return response. json ();
},
});
// Atom family for dynamic state
const todoState = atomFamily ({
key: 'todoState' ,
default : ( id ) => ({
id,
text: '' ,
completed: false ,
}),
});
// Selector family for filtered data
const filteredTodosSelector = selectorFamily ({
key: 'filteredTodosSelector' ,
get : ( filter ) => ({ get }) => {
const todos = get (todoListState);
switch (filter) {
case 'completed' :
return todos. filter ( todo => todo.completed);
case 'active' :
return todos. filter ( todo => ! todo.completed);
default :
return todos;
}
},
});
// Component usage
function TextInput () {
const [ text , setText ] = useRecoilState (textState);
const charCount = useRecoilValue (charCountState);
return (
< div >
< input value = {text} onChange = {( e ) => setText (e.target.value)} />
< p >Character Count: {charCount}</ p >
</ div >
);
}
// App with RecoilRoot
import { RecoilRoot } from 'recoil' ;
function App () {
return (
< RecoilRoot >
< TextInput />
</ RecoilRoot >
);
}
2.5 MobX Observable State Trees
Feature
Syntax
Description
Use Case
makeObservable
makeObservable(this, { prop: observable })
Makes class properties reactive
Class-based stores
makeAutoObservable
makeAutoObservable(this)
Automatically makes all properties observable
Simplified store setup
observable
observable({ prop: value })
Creates observable object/array/map
Object state
computed
get computed() { return this.x * 2 }
Derived values automatically recalculated
Calculated properties
action
@action method() { this.value = x }
Functions that modify state
State mutations
reaction
reaction(() => data, (data) => {})
Side effects triggered by observable changes
Logging, persistence
observer
observer(Component)
Makes React component reactive to observables
Component observation
Example: MobX store with observer components
import { makeAutoObservable, runInAction } from 'mobx' ;
import { observer } from 'mobx-react-lite' ;
// Store class
class TodoStore {
todos = [];
filter = 'all' ;
constructor () {
makeAutoObservable ( this );
}
// Computed property
get filteredTodos () {
switch ( this .filter) {
case 'active' :
return this .todos. filter ( todo => ! todo.completed);
case 'completed' :
return this .todos. filter ( todo => todo.completed);
default :
return this .todos;
}
}
get completedCount () {
return this .todos. filter ( todo => todo.completed). length ;
}
// Actions
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;
}
}
setFilter ( filter ) {
this .filter = filter;
}
// Async action
async fetchTodos () {
const response = await fetch ( '/api/todos' );
const todos = await response. json ();
runInAction (() => {
this .todos = todos;
});
}
}
// Create store instance
const todoStore = new TodoStore ();
// Observer component - automatically re-renders on observable changes
const TodoList = observer (() => {
const { filteredTodos , addTodo , toggleTodo } = todoStore;
return (
< div >
< ul >
{filteredTodos. map ( todo => (
< li key = {todo.id}>
< input
type = "checkbox"
checked = {todo.completed}
onChange = {() => toggleTodo (todo.id)}
/>
{todo.text}
</ li >
))}
</ ul >
< button onClick = {() => addTodo ( 'New Todo' )}>Add</ button >
</ div >
);
});
// React Context for store
import { createContext, useContext } from 'react' ;
const StoreContext = createContext (todoStore);
export const useStore = () => useContext (StoreContext);
2.6 SWR Data Fetching Caching
Feature
Syntax
Description
Use Case
useSWR
const { data, error } = useSWR(key, fetcher)
Hook for data fetching with built-in caching
API data fetching
mutate
mutate(key, data, options)
Manually update cache and revalidate
Optimistic updates
useSWRMutation
const { trigger } = useSWRMutation(key, fetcher)
Hook for mutations (POST, PUT, DELETE)
Data modifications
SWRConfig
<SWRConfig value={{ options }}>
Global configuration provider
Default settings
Revalidation
{ revalidateOnFocus: true }
Auto-refetch on window focus, reconnect, interval
Fresh data strategy
Pagination
useSWRInfinite(getKey, fetcher)
Infinite loading/pagination hook
List pagination
Example: SWR with mutations and optimistic updates
import useSWR, { mutate, useSWRConfig } from 'swr' ;
import useSWRMutation from 'swr/mutation' ;
// Fetcher function
const fetcher = ( url ) => fetch (url). then ( res => res. json ());
// Basic usage
function UserProfile ({ userId }) {
const { data , error , isLoading , mutate } = useSWR (
`/api/users/${ userId }` ,
fetcher,
{
revalidateOnFocus: true ,
revalidateOnReconnect: true ,
dedupingInterval: 5000 ,
}
);
if (isLoading) return < div >Loading...</ div >;
if (error) return < div >Error: {error.message}</ div >;
return (
< div >
< h1 >{data.name}</ h1 >
< button onClick = {() => mutate ()}>Refresh</ button >
</ div >
);
}
// Mutation with optimistic update
async function updateUser ( url , { arg }) {
await fetch (url, {
method: 'PUT' ,
body: JSON . stringify (arg),
});
}
function EditProfile ({ userId }) {
const { trigger } = useSWRMutation ( `/api/users/${ userId }` , updateUser);
const handleUpdate = async ( updates ) => {
// Optimistic update
mutate (
`/api/users/${ userId }` ,
( current ) => ({ ... current, ... updates }),
false // Don't revalidate yet
);
try {
await trigger (updates);
} catch (error) {
// Revert on error
mutate ( `/api/users/${ userId }` );
}
};
return < button onClick = {() => handleUpdate ({ name: 'New Name' })}>Update</ button >;
}
// Global configuration
import { SWRConfig } from 'swr' ;
function App () {
return (
< SWRConfig
value = {{
refreshInterval: 30000 ,
fetcher : ( url ) => fetch (url). then ( res => res. json ()),
onError : ( error ) => console. error (error),
}}
>
< UserProfile userId = "123" />
</ SWRConfig >
);
}
// Infinite loading
import useSWRInfinite from 'swr/infinite' ;
function UserList () {
const getKey = ( pageIndex , previousPageData ) => {
if (previousPageData && ! previousPageData.hasMore) return null ;
return `/api/users?page=${ pageIndex }&limit=10` ;
};
const { data , size , setSize , isLoading } = useSWRInfinite (getKey, fetcher);
const users = data ? data. flatMap ( page => page.users) : [];
const hasMore = data?.[data. length - 1 ]?.hasMore;
return (
< div >
{users. map ( user => < div key = {user.id}>{user.name}</ div >)}
{hasMore && (
< button onClick = {() => setSize (size + 1 )}>Load More</ button >
)}
</ div >
);
}
State Management Comparison
Library
Best For
Learning Curve
Bundle Size
Redux Toolkit
Complex apps, DevTools, middleware
Medium
~12KB
Zustand
Simple API, minimal boilerplate
Low
~1KB
Context API
Small apps, theme/auth
Low
Built-in
Recoil
Atomic state, derived values
Medium
~21KB
MobX
OOP style, automatic tracking
Medium
~16KB
SWR
Server state, caching, revalidation
Low
~5KB
3. Data Flow Architecture Implementation
3.1 Flux Redux Unidirectional Flow
Concept
Component
Description
Role
Action
{ type: 'ADD', payload: data }
Plain object describing state change intent
Event description
Dispatcher
dispatch(action)
Central hub distributing actions to stores
Action distribution
Store
function reducer(state, action)
Holds application state, responds to actions
State container
View
useSelector(state => state.data)
React component subscribing to store updates
UI rendering
Middleware
const logger = store => next => action => {}
Intercepts actions for logging, async, etc
Side effect handling
Example: Complete Flux/Redux unidirectional data flow
// Actions - describe what happened
const ActionTypes = {
ADD_TODO: 'ADD_TODO' ,
TOGGLE_TODO: 'TOGGLE_TODO' ,
SET_FILTER: 'SET_FILTER' ,
};
const addTodo = ( text ) => ({
type: ActionTypes. ADD_TODO ,
payload: { id: Date. now (), text, completed: false },
});
const toggleTodo = ( id ) => ({
type: ActionTypes. TOGGLE_TODO ,
payload: { id },
});
// Reducer - specifies how state changes
function todosReducer ( state = [], action ) {
switch (action.type) {
case ActionTypes. ADD_TODO :
return [ ... state, action.payload];
case ActionTypes. TOGGLE_TODO :
return state. map ( todo =>
todo.id === action.payload.id
? { ... todo, completed: ! todo.completed }
: todo
);
default :
return state;
}
}
// Store - holds state, provides dispatch
import { createStore, combineReducers } from 'redux' ;
const rootReducer = combineReducers ({
todos: todosReducer,
filter: filterReducer,
});
const store = createStore (rootReducer);
// View - subscribes and dispatches
import { useSelector, useDispatch } from 'react-redux' ;
function TodoList () {
const todos = useSelector ( state => state.todos);
const dispatch = useDispatch ();
return (
< div >
{todos. map ( todo => (
< div key = {todo.id}>
< input
type = "checkbox"
checked = {todo.completed}
onChange = {() => dispatch ( toggleTodo (todo.id))}
/>
{todo.text}
</ div >
))}
< button onClick = {() => dispatch ( addTodo ( 'New Todo' ))}>
Add Todo
</ button >
</ div >
);
}
// Data Flow:
// 1. User clicks button → dispatch(action)
// 2. Action sent to reducer
// 3. Reducer creates new state
// 4. Store notifies subscribers
// 5. Component re-renders with new state
Note: Unidirectional flow ensures predictable state changes -
state can only be modified through actions, making debugging easier with Redux DevTools.
3.2 GraphQL Apollo Client Normalization
Feature
Syntax
Description
Use Case
ApolloClient
new ApolloClient({ uri, cache })
GraphQL client with intelligent caching
Client initialization
InMemoryCache
new InMemoryCache({ typePolicies })
Normalized cache storing entities by ID
Cache configuration
useQuery
const { data, loading } = useQuery(QUERY)
Fetches data with automatic caching and updates
Data fetching
useMutation
const [mutate] = useMutation(MUTATION)
Executes mutations with cache updates
Data modification
cache.writeQuery
cache.writeQuery({ query, data })
Manually updates cache with new data
Optimistic updates
refetchQueries
{ refetchQueries: [{ query: QUERY }] }
Refreshes queries after mutation
Cache invalidation
@client directive
field @client
Local-only fields not fetched from server
Client-side state
Example: Apollo Client with normalized cache and optimistic updates
// Apollo Client setup
import { ApolloClient, InMemoryCache, gql } from '@apollo/client' ;
const client = new ApolloClient ({
uri: 'https://api.example.com/graphql' ,
cache: new InMemoryCache ({
typePolicies: {
Query: {
fields: {
posts: {
merge ( existing = [], incoming ) {
return [ ... existing, ... incoming];
},
},
},
},
User: {
fields: {
fullName: {
read ( _ , { readField }) {
return `${ readField ( 'firstName' ) } ${ readField ( 'lastName' ) }` ;
},
},
},
},
},
}),
});
// GraphQL queries and mutations
const GET_USERS = gql `
query GetUsers {
users {
id
name
email
}
}
` ;
const CREATE_USER = gql `
mutation CreateUser($input: UserInput!) {
createUser(input: $input) {
id
name
email
}
}
` ;
// Component with query
import { useQuery, useMutation } from '@apollo/client' ;
function UserList () {
const { data , loading , error } = useQuery ( GET_USERS , {
pollInterval: 30000 , // Refetch every 30s
fetchPolicy: 'cache-and-network' ,
});
const [ createUser ] = useMutation ( CREATE_USER , {
// Optimistic response
optimisticResponse: {
createUser: {
__typename: 'User' ,
id: 'temp-id' ,
name: 'New User' ,
email: 'temp@example.com' ,
},
},
// Update cache after mutation
update ( cache , { data : { createUser } }) {
const existing = cache. readQuery ({ query: GET_USERS });
cache. writeQuery ({
query: GET_USERS ,
data: {
users: [ ... existing.users, createUser],
},
});
},
});
if (loading) return < div >Loading...</ div >;
if (error) return < div >Error: {error.message}</ div >;
return (
< div >
{data.users. map ( user => (
< div key = {user.id}>{user.name} - {user.email}</ div >
))}
< button onClick = {() => createUser ({
variables: { input: { name: 'John' , email: 'john@example.com' } }
})}>
Add User
</ button >
</ div >
);
}
// Cache normalization example
// Apollo automatically normalizes:
// { users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }] }
// Into:
// {
// ROOT_QUERY: { users: [ref('User:1'), ref('User:2')] },
// 'User:1': { id: 1, name: 'John' },
// 'User:2': { id: 2, name: 'Jane' }
// }
3.3 React Query Optimistic Updates
Feature
Syntax
Description
Use Case
useQuery
useQuery({ queryKey, queryFn })
Declarative data fetching with caching
Server state
useMutation
useMutation({ mutationFn, onMutate })
Data mutations with rollback support
Data updates
queryClient.setQueryData
queryClient.setQueryData(key, data)
Manually updates cached query data
Optimistic updates
onMutate
onMutate: async (newData) => {}
Fires before mutation, returns rollback context
Optimistic UI
onError
onError: (err, vars, context) => {}
Handles mutation errors, rolls back changes
Error recovery
invalidateQueries
queryClient.invalidateQueries(['key'])
Marks queries as stale, triggers refetch
Cache invalidation
Example: React Query with optimistic updates and rollback
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' ;
function TodoList () {
const queryClient = useQueryClient ();
// Fetch todos
const { data : todos } = useQuery ({
queryKey: [ 'todos' ],
queryFn : async () => {
const res = await fetch ( '/api/todos' );
return res. json ();
},
});
// Mutation with optimistic update
const updateTodoMutation = useMutation ({
mutationFn : async ( updatedTodo ) => {
const res = await fetch ( `/api/todos/${ updatedTodo . id }` , {
method: 'PUT' ,
body: JSON . stringify (updatedTodo),
});
return res. json ();
},
// Optimistic update
onMutate : async ( updatedTodo ) => {
// Cancel outgoing refetches
await queryClient. cancelQueries ({ queryKey: [ 'todos' ] });
// Snapshot current value
const previousTodos = queryClient. getQueryData ([ 'todos' ]);
// Optimistically update cache
queryClient. setQueryData ([ 'todos' ], ( old ) =>
old. map (( todo ) =>
todo.id === updatedTodo.id ? updatedTodo : todo
)
);
// Return context with snapshot
return { previousTodos };
},
// Rollback on error
onError : ( err , updatedTodo , context ) => {
queryClient. setQueryData ([ 'todos' ], context.previousTodos);
console. error ( 'Update failed, rolled back:' , err);
},
// Refetch after success
onSettled : () => {
queryClient. invalidateQueries ({ queryKey: [ 'todos' ] });
},
});
const deleteTodoMutation = useMutation ({
mutationFn : async ( id ) => {
await fetch ( `/api/todos/${ id }` , { method: 'DELETE' });
},
onMutate : async ( deletedId ) => {
await queryClient. cancelQueries ({ queryKey: [ 'todos' ] });
const previousTodos = queryClient. getQueryData ([ 'todos' ]);
// Optimistically remove from UI
queryClient. setQueryData ([ 'todos' ], ( old ) =>
old. filter (( todo ) => todo.id !== deletedId)
);
return { previousTodos };
},
onError : ( err , deletedId , context ) => {
queryClient. setQueryData ([ 'todos' ], context.previousTodos);
},
onSettled : () => {
queryClient. invalidateQueries ({ queryKey: [ 'todos' ] });
},
});
const handleToggle = ( todo ) => {
updateTodoMutation. mutate ({
... todo,
completed: ! todo.completed,
});
};
return (
< div >
{todos?. map (( todo ) => (
< div key = {todo.id}>
< input
type = "checkbox"
checked = {todo.completed}
onChange = {() => handleToggle (todo)}
/>
{todo.text}
< button onClick = {() => deleteTodoMutation. mutate (todo.id)}>
Delete
</ button >
</ div >
))}
</ div >
);
}
3.4 WebSocket Real-time Data Sync
Feature
API/Method
Description
Use Case
WebSocket
new WebSocket('ws://url')
Creates persistent bidirectional connection
Real-time communication
onopen
ws.onopen = () => {}
Fires when connection established
Connection ready
onmessage
ws.onmessage = (event) => {}
Receives messages from server
Incoming data
send
ws.send(JSON.stringify(data))
Sends data to server
Outgoing messages
onerror
ws.onerror = (error) => {}
Handles connection errors
Error handling
onclose
ws.onclose = () => {}
Fires when connection closes
Cleanup, reconnection
Socket.io
io('http://url')
Library with auto-reconnection, rooms, namespaces
Enhanced WebSocket
Example: WebSocket with React hook and reconnection logic
import { useEffect, useState, useRef } from 'react' ;
// Custom WebSocket hook
function useWebSocket ( url ) {
const [ messages , setMessages ] = useState ([]);
const [ connectionStatus , setConnectionStatus ] = useState ( 'disconnected' );
const ws = useRef ( null );
const reconnectTimeoutRef = useRef ( null );
const connect = () => {
ws.current = new WebSocket (url);
ws.current. onopen = () => {
console. log ( 'Connected' );
setConnectionStatus ( 'connected' );
};
ws.current. onmessage = ( event ) => {
const data = JSON . parse (event.data);
setMessages (( prev ) => [ ... prev, data]);
};
ws.current. onerror = ( error ) => {
console. error ( 'WebSocket error:' , error);
setConnectionStatus ( 'error' );
};
ws.current. onclose = () => {
console. log ( 'Disconnected' );
setConnectionStatus ( 'disconnected' );
// Auto-reconnect after 3 seconds
reconnectTimeoutRef.current = setTimeout (() => {
console. log ( 'Reconnecting...' );
connect ();
}, 3000 );
};
};
useEffect (() => {
connect ();
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout (reconnectTimeoutRef.current);
}
if (ws.current) {
ws.current. close ();
}
};
}, [url]);
const sendMessage = ( message ) => {
if (ws.current?.readyState === WebSocket. OPEN ) {
ws.current. send ( JSON . stringify (message));
}
};
return { messages, sendMessage, connectionStatus };
}
// Component using WebSocket
function ChatRoom () {
const { messages , sendMessage , connectionStatus } = useWebSocket (
'ws://localhost:8080'
);
const [ input , setInput ] = useState ( '' );
const handleSend = () => {
sendMessage ({ type: 'chat' , text: input, timestamp: Date. now () });
setInput ( '' );
};
return (
< div >
< div >Status: {connectionStatus}</ div >
< div >
{messages. map (( msg , i ) => (
< div key = {i}>{msg.text}</ div >
))}
</ div >
< input
value = {input}
onChange = {( e ) => setInput (e.target.value)}
onKeyPress = {( e ) => e.key === 'Enter' && handleSend ()}
/>
< button onClick = {handleSend}>Send</ button >
</ div >
);
}
// Socket.io alternative
import io from 'socket.io-client' ;
function useSocketIO ( url ) {
const [ socket , setSocket ] = useState ( null );
useEffect (() => {
const newSocket = io (url, {
reconnection: true ,
reconnectionDelay: 1000 ,
reconnectionAttempts: 5 ,
});
newSocket. on ( 'connect' , () => {
console. log ( 'Socket.io connected' );
});
setSocket (newSocket);
return () => newSocket. close ();
}, [url]);
return socket;
}
3.5 IndexedDB Offline Data Persistence
Feature
API
Description
Use Case
open
indexedDB.open(name, version)
Opens database, creates if doesn't exist
DB initialization
createObjectStore
db.createObjectStore(name, { keyPath })
Creates table-like storage space
Schema definition
transaction
db.transaction([store], 'readwrite')
Groups operations with ACID properties
Data operations
add
objectStore.add(data)
Inserts new record, fails if key exists
Insert operation
put
objectStore.put(data)
Updates or inserts record
Upsert operation
get
objectStore.get(key)
Retrieves single record by key
Read operation
getAll
objectStore.getAll()
Retrieves all records
Bulk read
Index
objectStore.createIndex(name, keyPath)
Creates searchable index on field
Query optimization
Example: IndexedDB wrapper with React hook
// IndexedDB helper class
class DatabaseManager {
constructor ( dbName , version = 1 ) {
this .dbName = dbName;
this .version = version;
this .db = null ;
}
async open () {
return new Promise (( resolve , reject ) => {
const request = indexedDB. open ( this .dbName, this .version);
request. onerror = () => reject (request.error);
request. onsuccess = () => {
this .db = request.result;
resolve ( this .db);
};
request. onupgradeneeded = ( event ) => {
const db = event.target.result;
// Create object stores
if ( ! db.objectStoreNames. contains ( 'todos' )) {
const todoStore = db. createObjectStore ( 'todos' , {
keyPath: 'id' ,
autoIncrement: true
});
todoStore. createIndex ( 'completed' , 'completed' , { unique: false });
todoStore. createIndex ( 'createdAt' , 'createdAt' , { unique: false });
}
};
});
}
async add ( storeName , data ) {
const transaction = this .db. transaction ([storeName], 'readwrite' );
const store = transaction. objectStore (storeName);
return new Promise (( resolve , reject ) => {
const request = store. add (data);
request. onsuccess = () => resolve (request.result);
request. onerror = () => reject (request.error);
});
}
async getAll ( storeName ) {
const transaction = this .db. transaction ([storeName], 'readonly' );
const store = transaction. objectStore (storeName);
return new Promise (( resolve , reject ) => {
const request = store. getAll ();
request. onsuccess = () => resolve (request.result);
request. onerror = () => reject (request.error);
});
}
async update ( storeName , data ) {
const transaction = this .db. transaction ([storeName], 'readwrite' );
const store = transaction. objectStore (storeName);
return new Promise (( resolve , reject ) => {
const request = store. put (data);
request. onsuccess = () => resolve (request.result);
request. onerror = () => reject (request.error);
});
}
async delete ( storeName , key ) {
const transaction = this .db. transaction ([storeName], 'readwrite' );
const store = transaction. objectStore (storeName);
return new Promise (( resolve , reject ) => {
const request = store. delete (key);
request. onsuccess = () => resolve ();
request. onerror = () => reject (request.error);
});
}
}
// React hook for IndexedDB
function useIndexedDB ( dbName , storeName ) {
const [ db , setDb ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
const initDB = async () => {
const manager = new DatabaseManager (dbName);
await manager. open ();
setDb (manager);
setLoading ( false );
};
initDB ();
}, [dbName]);
const addItem = async ( data ) => {
if ( ! db) return ;
return db. add (storeName, data);
};
const getAllItems = async () => {
if ( ! db) return [];
return db. getAll (storeName);
};
const updateItem = async ( data ) => {
if ( ! db) return ;
return db. update (storeName, data);
};
const deleteItem = async ( key ) => {
if ( ! db) return ;
return db. delete (storeName, key);
};
return { addItem, getAllItems, updateItem, deleteItem, loading };
}
// Component using IndexedDB
function TodoApp () {
const [ todos , setTodos ] = useState ([]);
const { addItem , getAllItems , updateItem , deleteItem , loading } =
useIndexedDB ( 'TodoDB' , 'todos' );
useEffect (() => {
if ( ! loading) {
loadTodos ();
}
}, [loading]);
const loadTodos = async () => {
const items = await getAllItems ();
setTodos (items);
};
const handleAdd = async ( text ) => {
const newTodo = {
text,
completed: false ,
createdAt: Date. now (),
};
await addItem (newTodo);
loadTodos ();
};
const handleToggle = async ( todo ) => {
await updateItem ({ ... todo, completed: ! todo.completed });
loadTodos ();
};
if (loading) return < div >Loading...</ div >;
return (
< div >
{todos. map ( todo => (
< div key = {todo.id}>
< input
type = "checkbox"
checked = {todo.completed}
onChange = {() => handleToggle (todo)}
/>
{todo.text}
</ div >
))}
</ div >
);
}
3.6 Observer Pattern Event Emitters
Pattern
Implementation
Description
Use Case
EventEmitter
class EventEmitter { on, emit, off }
Pub/sub pattern for decoupled communication
Event-driven architecture
on/subscribe
emitter.on('event', callback)
Registers listener for specific event
Event subscription
emit/publish
emitter.emit('event', data)
Triggers event, notifies all listeners
Event broadcasting
off/unsubscribe
emitter.off('event', callback)
Removes specific event listener
Cleanup
once
emitter.once('event', callback)
Listener fires only first time
One-time events
Custom Events
new CustomEvent('type', { detail })
Native browser event system
DOM-based communication
Example: Event Emitter implementation with React integration
// EventEmitter class
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);
}
once ( event , listener ) {
const onceWrapper = ( ... args ) => {
listener ( ... args);
this . off (event, onceWrapper);
};
return this . on (event, onceWrapper);
}
emit ( event , ... args ) {
if ( ! this .events[event]) return ;
this .events[event]. forEach ( listener => listener ( ... args));
}
off ( event , listenerToRemove ) {
if ( ! this .events[event]) return ;
this .events[event] = this .events[event]. filter (
listener => listener !== listenerToRemove
);
}
removeAllListeners ( event ) {
if (event) {
delete this .events[event];
} else {
this .events = {};
}
}
}
// Global event bus instance
const eventBus = new EventEmitter ();
// React hook for event subscriptions
function useEventListener ( event , handler ) {
useEffect (() => {
const unsubscribe = eventBus. on (event, handler);
return unsubscribe; // Cleanup on unmount
}, [event, handler]);
}
// Components using event bus
function UserProfile () {
const [ user , setUser ] = useState ( null );
// Subscribe to user updates
useEventListener ( 'user:updated' , ( updatedUser ) => {
setUser (updatedUser);
});
return < div >{user?.name}</ div >;
}
function UpdateButton () {
const handleUpdate = () => {
const newUser = { id: 1 , name: 'John Updated' };
// Emit event to all subscribers
eventBus. emit ( 'user:updated' , newUser);
};
return < button onClick = {handleUpdate}>Update User</ button >;
}
// Custom DOM Events
function NotificationSystem () {
useEffect (() => {
const handleNotification = ( event ) => {
console. log ( 'Notification:' , event.detail);
};
window. addEventListener ( 'app:notification' , handleNotification);
return () => window. removeEventListener ( 'app:notification' , handleNotification);
}, []);
return < div >Notification System</ div >;
}
function TriggerNotification () {
const notify = () => {
const event = new CustomEvent ( 'app:notification' , {
detail: {
message: 'Hello World' ,
type: 'info' ,
timestamp: Date. now (),
},
});
window. dispatchEvent (event);
};
return < button onClick = {notify}>Send Notification</ button >;
}
// Advanced: Typed EventEmitter with TypeScript
interface Events {
'user:login' : ( user : User ) => void ;
'user:logout' : () => void ;
'notification' : ( message : string ) => void ;
}
class TypedEventEmitter < T extends Record < string , ( ... args : any []) => void >> {
private events = new Map < keyof T , Set < T [ keyof T ]>>();
on < K extends keyof T >( event : K , listener : T [ K ]) : () => void {
if ( ! this .events. has (event)) {
this .events. set (event, new Set ());
}
this .events. get (event) ! . add (listener);
return () => this . off (event, listener);
}
emit < K extends keyof T >( event : K , ... args : Parameters < T [ K ]>) : void {
this .events. get (event)?. forEach ( listener => listener ( ... args));
}
off < K extends keyof T >( event : K , listener : T [ K ]) : void {
this .events. get (event)?. delete (listener);
}
}
const typedEventBus = new TypedEventEmitter < Events >();
typedEventBus. on ( 'user:login' , ( user ) => console. log (user));
typedEventBus. emit ( 'user:login' , { id: 1 , name: 'John' });
Data Flow Architecture Patterns
Flux/Redux - Predictable unidirectional flow, ideal for complex state
management with time-travel debugging
GraphQL - Normalized cache eliminates data duplication, automatic updates
across queries
React Query - Optimistic updates provide instant feedback, with automatic
rollback on errors
WebSocket - Real-time bidirectional communication, essential for chat, live
updates, collaborative editing
IndexedDB - Offline-first architecture with large data storage capacity
(GBs), transactional operations
Observer Pattern - Decoupled component communication, reduces prop
drilling, enables event-driven architecture
4. Event Handling Implementation Patterns
4.1 React SyntheticEvent preventDefault
Feature
Syntax
Description
Use Case
SyntheticEvent
event: React.MouseEvent<T>
Cross-browser wrapper around native events
Consistent event handling
preventDefault
event.preventDefault()
Prevents default browser action
Form submission, link navigation
stopPropagation
event.stopPropagation()
Stops event bubbling to parent elements
Nested clickable elements
currentTarget
event.currentTarget
Element handler is attached to
Event delegation
target
event.target
Element that triggered the event
Dynamic element access
nativeEvent
event.nativeEvent
Access underlying browser event
Browser-specific features
Example: React SyntheticEvent with type-safe handlers
import React from 'react' ;
// Form submission with preventDefault
function LoginForm () {
const handleSubmit = ( event : React . FormEvent < HTMLFormElement >) => {
event. preventDefault (); // Prevent page reload
const formData = new FormData (event.currentTarget);
const email = formData. get ( 'email' ) as string ;
const password = formData. get ( 'password' ) as string ;
console. log ({ email, password });
};
return (
< form onSubmit = {handleSubmit} >
< input name = "email" type = "email" required />
< input name = "password" type = "password" required />
< button type = "submit" > Login </ button >
</ form >
);
}
// Click event with stopPropagation
function NestedButtons () {
const handleParentClick = () => {
console. log ( 'Parent clicked' );
};
const handleChildClick = ( event : React . MouseEvent < HTMLButtonElement >) => {
event. stopPropagation (); // Prevent parent handler from firing
console. log ( 'Child clicked' );
};
return (
< div onClick = {handleParentClick} style = {{ padding : '20px' , background : '#eee' }} >
< button onClick = {handleChildClick} >
Click me (won 't trigger parent )
</ button >
</ div >
);
}
// Input change with proper typing
function SearchInput () {
const handleChange = ( event : React . ChangeEvent < HTMLInputElement >) => {
const value = event.target.value;
console. log ( 'Search:' , value);
};
const handleKeyPress = ( event : React . KeyboardEvent < HTMLInputElement >) => {
if (event.key === 'Enter' ) {
event. preventDefault ();
console. log ( 'Search submitted' );
}
};
return (
< input
type = "text"
onChange = {handleChange}
onKeyPress = {handleKeyPress}
placeholder = "Search..."
/>
);
}
// Mouse events with coordinates
function TrackMouse () {
const handleMouseMove = ( event : React . MouseEvent < HTMLDivElement >) => {
const { clientX , clientY , pageX , pageY } = event;
console. log ( 'Client:' , clientX, clientY);
console. log ( 'Page:' , pageX, pageY);
};
const handleClick = ( event : React . MouseEvent < HTMLDivElement >) => {
// Access native event for browser-specific features
console. log ( 'Native event:' , event.nativeEvent);
console. log ( 'Button clicked:' , event.button); // 0=left, 1=middle, 2=right
};
return (
< div
onMouseMove = {handleMouseMove}
onClick = {handleClick}
style = {{ width : '300px' , height : '300px' , background : '#f0f0f0' }}
>
Move mouse here
</ div >
);
}
// Focus events
function FocusExample () {
const handleFocus = ( event : React . FocusEvent < HTMLInputElement >) => {
event.target.style.borderColor = 'blue' ;
};
const handleBlur = ( event : React . FocusEvent < HTMLInputElement >) => {
event.target.style.borderColor = 'gray' ;
};
return (
< input
type = "text"
onFocus = {handleFocus}
onBlur = {handleBlur}
placeholder = "Focus me"
/>
);
}
4.2 Event Delegation querySelector addEventListener
Method
Syntax
Description
Use Case
addEventListener
element.addEventListener('click', handler)
Attaches event listener to element
DOM event handling
removeEventListener
element.removeEventListener('click', handler)
Removes specific event listener
Cleanup, memory management
Event Delegation
parent.addEventListener('click', (e) => {})
Single listener on parent handles child events
Dynamic elements, performance
event.target
e.target.matches('.class')
Identifies which child triggered event
Delegation filtering
capture phase
addEventListener('click', fn, true)
Event fires during capture before bubble
Event interception
once option
addEventListener('click', fn, { once: true })
Listener auto-removes after first call
One-time events
Example: Event delegation with dynamic elements
// Event delegation for dynamic list items
function setupDynamicList () {
const list = document. querySelector ( '#todo-list' );
// Single event listener on parent handles all children
list. addEventListener ( 'click' , ( event ) => {
const target = event.target;
// Check if clicked element matches selector
if (target. matches ( '.delete-btn' )) {
const todoItem = target. closest ( '.todo-item' );
todoItem. remove ();
}
if (target. matches ( '.checkbox' )) {
const todoItem = target. closest ( '.todo-item' );
todoItem.classList. toggle ( 'completed' );
}
if (target. matches ( '.edit-btn' )) {
const todoItem = target. closest ( '.todo-item' );
editTodo (todoItem);
}
});
// Add new items dynamically - no need for new listeners
function addTodo ( text ) {
const li = document. createElement ( 'li' );
li.className = 'todo-item' ;
li.innerHTML = `
<input type="checkbox" class="checkbox">
<span>${ text }</span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
` ;
list. appendChild (li);
}
}
// Event listener options
function advancedEventListening () {
const button = document. querySelector ( '#submit-btn' );
// Once option - auto-removes after first call
button. addEventListener ( 'click' , handleClick, { once: true });
// Passive option - improves scroll performance
document. addEventListener ( 'scroll' , handleScroll, { passive: true });
// Capture phase - fires before bubble phase
document. addEventListener ( 'click' , captureHandler, true );
function handleClick ( event ) {
console. log ( 'Button clicked once' );
}
function handleScroll ( event ) {
// Cannot call preventDefault in passive listener
console. log ( 'Scrolling...' );
}
function captureHandler ( event ) {
console. log ( 'Capture phase:' , event.target);
}
}
// React hook for native event listeners
import { useEffect, useRef } from 'react' ;
function useEventListener (
eventName : string ,
handler : ( event : Event ) => void ,
element : HTMLElement | Window = window
) {
const savedHandler = useRef <( event : Event ) => void >();
useEffect (() => {
savedHandler.current = handler;
}, [handler]);
useEffect (() => {
const isSupported = element && element.addEventListener;
if ( ! isSupported) return ;
const eventListener = ( event : Event ) => savedHandler. current ?.(event);
element. addEventListener (eventName, eventListener);
return () => {
element. removeEventListener (eventName, eventListener);
};
}, [eventName, element]);
}
// Usage in React component
function Component () {
const divRef = useRef < HTMLDivElement >( null );
useEventListener ( 'mousemove' , ( event ) => {
console. log ( 'Mouse position:' , event.clientX, event.clientY);
}, divRef.current);
useEventListener ( 'resize' , () => {
console. log ( 'Window resized' );
}, window);
return < div ref = {divRef}>Content</ div >;
}
// Event delegation with closest()
document. querySelector ( '.container' ). addEventListener ( 'click' , ( event ) => {
// Find closest ancestor matching selector
const card = event.target. closest ( '.card' );
if (card) {
console. log ( 'Card clicked:' , card.dataset.id);
}
});
4.3 Custom Events dispatch CustomEvent
API
Syntax
Description
Use Case
CustomEvent
new CustomEvent('type', { detail })
Creates custom event with data payload
Component communication
detail
{ detail: { data: value } }
Payload data attached to event
Data passing
bubbles
{ bubbles: true }
Event propagates up DOM tree
Event bubbling
dispatchEvent
element.dispatchEvent(event)
Triggers custom event on element
Event emission
composed
{ composed: true }
Event crosses shadow DOM boundary
Web Components
Example: Custom events for component communication
// Creating and dispatching custom events
class NotificationSystem {
static show ( message , type = 'info' ) {
const event = new CustomEvent ( 'app:notification' , {
detail: {
message,
type,
timestamp: Date. now (),
},
bubbles: true ,
composed: true ,
});
document. dispatchEvent (event);
}
}
// Listening to custom events
document. addEventListener ( 'app:notification' , ( event ) => {
const { message , type , timestamp } = event.detail;
console. log ( `[${ type }] ${ message } at ${ new Date ( timestamp ) }` );
showToast (message, type);
});
// Usage
NotificationSystem. show ( 'User logged in' , 'success' );
NotificationSystem. show ( 'Failed to load data' , 'error' );
// React hook for custom events
function useCustomEvent ( eventName , handler ) {
useEffect (() => {
const eventHandler = ( event ) => handler (event.detail);
document. addEventListener (eventName, eventHandler);
return () => document. removeEventListener (eventName, eventHandler);
}, [eventName, handler]);
}
// Component emitting custom event
function CartButton () {
const addToCart = ( product ) => {
const event = new CustomEvent ( 'cart:add' , {
detail: { product, quantity: 1 },
bubbles: true ,
});
document. dispatchEvent (event);
};
return < button onClick = {() => addToCart ({ id: 1 , name: 'Product' })}>Add to Cart</ button >;
}
// Component listening to custom event
function CartCounter () {
const [ count , setCount ] = useState ( 0 );
useCustomEvent ( 'cart:add' , ( detail ) => {
setCount ( prev => prev + detail.quantity);
});
return < div >Cart: {count}</ div >;
}
// Web Component with custom events
class UserCard extends HTMLElement {
connectedCallback () {
this .innerHTML = `
<div class="card">
<h3>${ this . getAttribute ( 'name' ) }</h3>
<button id="follow-btn">Follow</button>
</div>
` ;
this . querySelector ( '#follow-btn' ). addEventListener ( 'click' , () => {
const event = new CustomEvent ( 'user:follow' , {
detail: {
userId: this . getAttribute ( 'user-id' ),
userName: this . getAttribute ( 'name' ),
},
bubbles: true ,
composed: true , // Cross shadow DOM
});
this . dispatchEvent (event);
});
}
}
customElements. define ( 'user-card' , UserCard);
// Listen to web component events
document. addEventListener ( 'user:follow' , ( event ) => {
console. log ( 'Following user:' , event.detail.userName);
});
// TypeScript typed custom events
interface AppEventMap {
'cart:add' : CustomEvent <{ product : Product ; quantity : number }>;
'user:login' : CustomEvent <{ user : User }>;
'notification' : CustomEvent <{ message : string ; type : string }>;
}
function dispatchTypedEvent < K extends keyof AppEventMap >(
type : K ,
detail : AppEventMap [ K ][ 'detail' ]
) {
const event = new CustomEvent (type, { detail, bubbles: true });
document. dispatchEvent (event);
}
// Type-safe usage
dispatchTypedEvent ( 'cart:add' , { product: myProduct, quantity: 2 });
4.4 Debounce Throttle Lodash useDebounce
Technique
Behavior
Description
Use Case
Debounce
Delays execution until pause in events
Waits for event stream to stop, then executes once
Search input, window resize
Throttle
Limits execution rate to interval
Executes at most once per time period
Scroll, mouse move, animation
lodash.debounce
debounce(fn, delay, options)
Debounce with leading/trailing edge control
Production-ready utility
lodash.throttle
throttle(fn, interval, options)
Throttle with leading/trailing edge control
Rate limiting
useDebounce hook
const debounced = useDebounce(value, delay)
React hook for debounced values
React state debouncing
Example: Debounce and throttle implementations with React hooks
// Pure JavaScript debounce implementation
function debounce ( func , delay ) {
let timeoutId;
return function ( ... args ) {
clearTimeout (timeoutId);
timeoutId = setTimeout (() => func. apply ( this , args), delay);
};
}
// Pure JavaScript throttle implementation
function throttle ( func , interval ) {
let lastCall = 0 ;
return function ( ... args ) {
const now = Date. now ();
if (now - lastCall >= interval) {
lastCall = now;
func. apply ( this , args);
}
};
}
// Debounced search input
const searchInput = document. querySelector ( '#search' );
const debouncedSearch = debounce (( query ) => {
console. log ( 'Searching for:' , query);
fetchResults (query);
}, 500 );
searchInput. addEventListener ( 'input' , ( e ) => {
debouncedSearch (e.target.value);
});
// Throttled scroll handler
const handleScroll = throttle (() => {
console. log ( 'Scroll position:' , window.scrollY);
updateScrollPosition ();
}, 100 );
window. addEventListener ( 'scroll' , handleScroll);
// React useDebounce hook
function useDebounce < T >( value : T , delay : number ) : T {
const [ debouncedValue , setDebouncedValue ] = useState (value);
useEffect (() => {
const handler = setTimeout (() => {
setDebouncedValue (value);
}, delay);
return () => clearTimeout (handler);
}, [value, delay]);
return debouncedValue;
}
// React useThrottle hook
function useThrottle < T >( value : T , interval : number ) : T {
const [ throttledValue , setThrottledValue ] = useState (value);
const lastUpdated = useRef (Date. now ());
useEffect (() => {
const now = Date. now ();
const timeSinceLastUpdate = now - lastUpdated.current;
if (timeSinceLastUpdate >= interval) {
lastUpdated.current = now;
setThrottledValue (value);
} else {
const timeoutId = setTimeout (() => {
lastUpdated.current = Date. now ();
setThrottledValue (value);
}, interval - timeSinceLastUpdate);
return () => clearTimeout (timeoutId);
}
}, [value, interval]);
return throttledValue;
}
// Debounced search component
function SearchBox () {
const [ searchTerm , setSearchTerm ] = useState ( '' );
const debouncedSearchTerm = useDebounce (searchTerm, 500 );
useEffect (() => {
if (debouncedSearchTerm) {
console. log ( 'Searching:' , debouncedSearchTerm);
fetchSearchResults (debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
< input
type = "text"
value = {searchTerm}
onChange = {( e ) => setSearchTerm (e.target.value)}
placeholder = "Search..."
/>
);
}
// Throttled scroll component
function ScrollTracker () {
const [ scrollY , setScrollY ] = useState ( 0 );
const throttledScrollY = useThrottle (scrollY, 100 );
useEffect (() => {
const handleScroll = () => setScrollY (window.scrollY);
window. addEventListener ( 'scroll' , handleScroll);
return () => window. removeEventListener ( 'scroll' , handleScroll);
}, []);
return < div >Scroll Position: {throttledScrollY}px</ div >;
}
// Using lodash debounce/throttle
import { debounce, throttle } from 'lodash' ;
function SearchWithLodash () {
const debouncedSearch = useMemo (
() => debounce (( query ) => {
console. log ( 'Searching:' , query);
}, 500 , {
leading: false ,
trailing: true ,
}),
[]
);
useEffect (() => {
return () => debouncedSearch. cancel (); // Cleanup
}, [debouncedSearch]);
return (
< input
onChange = {( e ) => debouncedSearch (e.target.value)}
placeholder = "Search..."
/>
);
}
// Advanced: Debounce with abort controller
function useDebouncedCallback ( callback , delay ) {
const callbackRef = useRef (callback);
const timeoutRef = useRef ();
const abortControllerRef = useRef ();
useEffect (() => {
callbackRef.current = callback;
}, [callback]);
return useCallback (( ... args ) => {
// Cancel previous request
if (abortControllerRef.current) {
abortControllerRef.current. abort ();
}
clearTimeout (timeoutRef.current);
timeoutRef.current = setTimeout (() => {
abortControllerRef.current = new AbortController ();
callbackRef. current ( ... args, abortControllerRef.current.signal);
}, delay);
}, [delay]);
}
4.5 Keyboard Navigation Tab Index Focus
Property/Method
Syntax
Description
Use Case
tabIndex
tabIndex={0}
Makes element keyboard focusable in DOM order
Custom interactive elements
tabIndex -1
tabIndex={-1}
Focusable programmatically but not via Tab
Modal focus management
focus()
element.focus()
Programmatically sets focus to element
Auto-focus input
onKeyDown
onKeyDown={(e) => {}}
Handles keyboard key press events
Keyboard shortcuts
roving tabindex
One item tabIndex={0}, others -1
Single Tab stop for component group
Toolbars, menus, lists
aria-activedescendant
aria-activedescendant="id"
Indicates active descendant without focus move
Listbox, combobox
Example: Keyboard navigation with roving tabindex
// Simple keyboard navigation
function KeyboardShortcuts () {
const handleKeyDown = ( event : React . KeyboardEvent ) => {
// Ctrl/Cmd + S = Save
if ((event.ctrlKey || event.metaKey) && event.key === 's' ) {
event. preventDefault ();
handleSave ();
}
// Escape = Close modal
if (event.key === 'Escape' ) {
handleClose ();
}
// Arrow keys = Navigation
if (event.key === 'ArrowDown' ) {
event. preventDefault ();
navigateDown ();
}
};
return (
< div onKeyDown = {handleKeyDown} tabIndex = { 0 }>
Press Ctrl+S to save
</ div >
);
}
// Roving tabindex implementation
function Toolbar () {
const [ focusedIndex , setFocusedIndex ] = useState ( 0 );
const buttonsRef = useRef <( HTMLButtonElement | null )[]>([]);
const buttons = [ 'Cut' , 'Copy' , 'Paste' , 'Undo' , 'Redo' ];
const handleKeyDown = ( event : React . KeyboardEvent , index : number ) => {
let newIndex = index;
switch (event.key) {
case 'ArrowRight' :
event. preventDefault ();
newIndex = Math. min (index + 1 , buttons. length - 1 );
break ;
case 'ArrowLeft' :
event. preventDefault ();
newIndex = Math. max (index - 1 , 0 );
break ;
case 'Home' :
event. preventDefault ();
newIndex = 0 ;
break ;
case 'End' :
event. preventDefault ();
newIndex = buttons. length - 1 ;
break ;
default :
return ;
}
setFocusedIndex (newIndex);
buttonsRef.current[newIndex]?. focus ();
};
return (
< div role = "toolbar" aria-label = "Text formatting" >
{buttons. map (( label , index ) => (
< button
key = {label}
ref = {( el ) => (buttonsRef.current[index] = el)}
tabIndex = {index === focusedIndex ? 0 : - 1 }
onKeyDown = {( e ) => handleKeyDown (e, index)}
onClick = {() => console. log ( `${ label } clicked` )}
>
{label}
</ button >
))}
</ div >
);
}
// Focus trap for modal
function useFocusTrap ( ref : React . RefObject < HTMLElement >) {
useEffect (() => {
const element = ref.current;
if ( ! element) return ;
const focusableElements = element. querySelectorAll (
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[ 0 ] as HTMLElement ;
const lastElement = focusableElements[focusableElements. length - 1 ] as HTMLElement ;
const handleTabKey = ( e : KeyboardEvent ) => {
if (e.key !== 'Tab' ) return ;
if (e.shiftKey) {
if (document.activeElement === firstElement) {
lastElement. focus ();
e. preventDefault ();
}
} else {
if (document.activeElement === lastElement) {
firstElement. focus ();
e. preventDefault ();
}
}
};
element. addEventListener ( 'keydown' , handleTabKey);
firstElement?. focus ();
return () => element. removeEventListener ( 'keydown' , handleTabKey);
}, [ref]);
}
// Modal with focus management
function Modal ({ isOpen , onClose , children }) {
const modalRef = useRef < HTMLDivElement >( null );
const previousFocusRef = useRef < HTMLElement | null >( null );
useFocusTrap (modalRef);
useEffect (() => {
if (isOpen) {
previousFocusRef.current = document.activeElement as HTMLElement ;
} else if (previousFocusRef.current) {
previousFocusRef.current. focus ();
}
}, [isOpen]);
if ( ! isOpen) return null ;
return (
< div
ref = {modalRef}
role = "dialog"
aria-modal = "true"
onKeyDown = {( e ) => e.key === 'Escape' && onClose ()}
>
{children}
< button onClick = {onClose}>Close</ button >
</ div >
);
}
// Custom dropdown with keyboard navigation
function Dropdown ({ options } : { options : string [] }) {
const [ isOpen , setIsOpen ] = useState ( false );
const [ selectedIndex , setSelectedIndex ] = useState ( - 1 );
const listRef = useRef < HTMLUListElement >( null );
const handleKeyDown = ( e : React . KeyboardEvent ) => {
if ( ! isOpen) {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown' ) {
e. preventDefault ();
setIsOpen ( true );
setSelectedIndex ( 0 );
}
return ;
}
switch (e.key) {
case 'ArrowDown' :
e. preventDefault ();
setSelectedIndex (( prev ) => Math. min (prev + 1 , options. length - 1 ));
break ;
case 'ArrowUp' :
e. preventDefault ();
setSelectedIndex (( prev ) => Math. max (prev - 1 , 0 ));
break ;
case 'Enter' :
case ' ' :
e. preventDefault ();
console. log ( 'Selected:' , options[selectedIndex]);
setIsOpen ( false );
break ;
case 'Escape' :
e. preventDefault ();
setIsOpen ( false );
break ;
}
};
return (
< div >
< button
onKeyDown = {handleKeyDown}
onClick = {() => setIsOpen ( ! isOpen)}
aria-haspopup = "listbox"
aria-expanded = {isOpen}
>
Select option
</ button >
{isOpen && (
< ul ref = {listRef} role = "listbox" >
{options. map (( option , index ) => (
< li
key = {option}
role = "option"
aria-selected = {index === selectedIndex}
style = {{
background: index === selectedIndex ? '#e0e0e0' : 'transparent' ,
}}
>
{option}
</ li >
))}
</ ul >
)}
</ div >
);
}
4.6 Touch Events Mobile Gesture Handling
Event
Trigger
Description
Use Case
touchstart
Finger touches screen
Fires when touch begins
Gesture initiation
touchmove
Finger moves on screen
Fires repeatedly during touch movement
Drag, swipe tracking
touchend
Finger leaves screen
Fires when touch ends
Gesture completion
touches
event.touches
Array of all current touch points
Multi-touch gestures
changedTouches
event.changedTouches
Touches that changed in this event
Specific touch handling
Pointer Events
onPointerDown/Move/Up
Unified API for mouse, touch, pen
Cross-device input
Example: Touch gestures with swipe and pinch detection
// Swipe gesture detection
function useSwipe ( onSwipeLeft , onSwipeRight , threshold = 50 ) {
const touchStart = useRef <{ x : number ; y : number } | null >( null );
const handleTouchStart = ( e : React . TouchEvent ) => {
touchStart.current = {
x: e.touches[ 0 ].clientX,
y: e.touches[ 0 ].clientY,
};
};
const handleTouchEnd = ( e : React . TouchEvent ) => {
if ( ! touchStart.current) return ;
const touchEnd = {
x: e.changedTouches[ 0 ].clientX,
y: e.changedTouches[ 0 ].clientY,
};
const deltaX = touchEnd.x - touchStart.current.x;
const deltaY = Math. abs (touchEnd.y - touchStart.current.y);
// Horizontal swipe (deltaX > deltaY)
if (Math. abs (deltaX) > threshold && Math. abs (deltaX) > deltaY) {
if (deltaX > 0 ) {
onSwipeRight ?.();
} else {
onSwipeLeft ?.();
}
}
touchStart.current = null ;
};
return { handleTouchStart, handleTouchEnd };
}
// Swipeable component
function SwipeableCard () {
const { handleTouchStart , handleTouchEnd } = useSwipe (
() => console. log ( 'Swiped left' ),
() => console. log ( 'Swiped right' )
);
return (
< div
onTouchStart = {handleTouchStart}
onTouchEnd = {handleTouchEnd}
style = {{
width: '300px' ,
height: '200px' ,
background: '#f0f0f0' ,
display: 'flex' ,
alignItems: 'center' ,
justifyContent: 'center' ,
}}
>
Swipe me left or right
</ div >
);
}
// Draggable element with touch
function DraggableElement () {
const [ position , setPosition ] = useState ({ x: 0 , y: 0 });
const [ isDragging , setIsDragging ] = useState ( false );
const startPos = useRef ({ x: 0 , y: 0 });
const currentPos = useRef ({ x: 0 , y: 0 });
const handleTouchStart = ( e : React . TouchEvent ) => {
setIsDragging ( true );
startPos.current = {
x: e.touches[ 0 ].clientX - position.x,
y: e.touches[ 0 ].clientY - position.y,
};
};
const handleTouchMove = ( e : React . TouchEvent ) => {
if ( ! isDragging) return ;
currentPos.current = {
x: e.touches[ 0 ].clientX - startPos.current.x,
y: e.touches[ 0 ].clientY - startPos.current.y,
};
setPosition (currentPos.current);
};
const handleTouchEnd = () => {
setIsDragging ( false );
};
return (
< div
onTouchStart = {handleTouchStart}
onTouchMove = {handleTouchMove}
onTouchEnd = {handleTouchEnd}
style = {{
position: 'absolute' ,
left: `${ position . x }px` ,
top: `${ position . y }px` ,
width: '100px' ,
height: '100px' ,
background: '#4CAF50' ,
cursor: isDragging ? 'grabbing' : 'grab' ,
touchAction: 'none' , // Prevent default touch behaviors
}}
>
Drag me
</ div >
);
}
// Pinch-to-zoom gesture
function usePinchZoom () {
const [ scale , setScale ] = useState ( 1 );
const initialDistance = useRef < number | null >( null );
const initialScale = useRef ( 1 );
const getDistance = ( touch1 : React . Touch , touch2 : React . Touch ) => {
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math. sqrt (dx * dx + dy * dy);
};
const handleTouchStart = ( e : React . TouchEvent ) => {
if (e.touches. length === 2 ) {
initialDistance.current = getDistance (e.touches[ 0 ], e.touches[ 1 ]);
initialScale.current = scale;
}
};
const handleTouchMove = ( e : React . TouchEvent ) => {
if (e.touches. length === 2 && initialDistance.current) {
const currentDistance = getDistance (e.touches[ 0 ], e.touches[ 1 ]);
const newScale = (currentDistance / initialDistance.current) * initialScale.current;
setScale (Math. min (Math. max (newScale, 0.5 ), 3 )); // Limit scale 0.5x to 3x
}
};
const handleTouchEnd = () => {
initialDistance.current = null ;
};
return { scale, handleTouchStart, handleTouchMove, handleTouchEnd };
}
// Zoomable image
function ZoomableImage ({ src } : { src : string }) {
const { scale , handleTouchStart , handleTouchMove , handleTouchEnd } = usePinchZoom ();
return (
< div
onTouchStart = {handleTouchStart}
onTouchMove = {handleTouchMove}
onTouchEnd = {handleTouchEnd}
style = {{
overflow: 'hidden' ,
touchAction: 'none' ,
}}
>
< img
src = {src}
alt = "Zoomable"
style = {{
transform: `scale(${ scale })` ,
transformOrigin: 'center center' ,
transition: 'transform 0.1s' ,
}}
/>
</ div >
);
}
// Unified pointer events (mouse + touch + pen)
function UnifiedPointerHandler () {
const [ isPressed , setIsPressed ] = useState ( false );
const [ position , setPosition ] = useState ({ x: 0 , y: 0 });
const handlePointerDown = ( e : React . PointerEvent ) => {
setIsPressed ( true );
(e.target as HTMLElement ). setPointerCapture (e.pointerId);
};
const handlePointerMove = ( e : React . PointerEvent ) => {
if (isPressed) {
setPosition ({ x: e.clientX, y: e.clientY });
}
};
const handlePointerUp = ( e : React . PointerEvent ) => {
setIsPressed ( false );
(e.target as HTMLElement ). releasePointerCapture (e.pointerId);
};
return (
< div
onPointerDown = {handlePointerDown}
onPointerMove = {handlePointerMove}
onPointerUp = {handlePointerUp}
style = {{
width: '100%' ,
height: '400px' ,
background: '#f0f0f0' ,
position: 'relative' ,
touchAction: 'none' ,
}}
>
< div
style = {{
position: 'absolute' ,
left: `${ position . x }px` ,
top: `${ position . y }px` ,
width: '20px' ,
height: '20px' ,
background: isPressed ? 'red' : 'blue' ,
borderRadius: '50%' ,
transform: 'translate(-50%, -50%)' ,
}}
/>
</ div >
);
}
Event Handling Best Practices
SyntheticEvent - React's cross-browser event wrapper, use preventDefault()
for form submissions
Event Delegation - Single listener on parent for dynamic children, improves
performance
Custom Events - Enable decoupled component communication via browser's
event system
Debounce - For search, resize, input validation (wait for pause)
Throttle - For scroll, mouse move, animations (limit execution rate)
Keyboard Navigation - Roving tabindex for toolbars, arrow keys for lists,
focus trap for modals
Touch Events - Use pointer events for unified mouse/touch/pen handling,
prevent default behaviors with touchAction
5. Modern Rendering Strategies Implementation
5.1 Next.js 14 App Router SSR SSG NEW
Feature
Syntax
Description
Use Case
App Router
app/ directory structure
File-system based routing with layouts and nested routes
Modern Next.js architecture
Server Components
async function Page() {}
Components that render on server by default
Zero JS to client, SEO
generateStaticParams
export async function generateStaticParams()
Static generation of dynamic routes at build time
SSG for dynamic routes
Dynamic Rendering
export const dynamic = 'force-dynamic'
SSR on every request, no caching
Personalized content
Revalidation
export const revalidate = 60
Incremental Static Regeneration (ISR) interval
Periodic content updates
Loading UI
loading.tsx
Instant loading state while streaming
Progressive rendering
Error Boundaries
error.tsx
Route-level error handling
Graceful error recovery
Example: Next.js 14 App Router with SSR, SSG, and ISR
// app/layout.tsx - Root layout with metadata
import type { Metadata } from 'next' ;
export const metadata : Metadata = {
title: 'My App' ,
description: 'Next.js 14 App Router Example' ,
};
export default function RootLayout ({
children ,
} : {
children : React . ReactNode ;
}) {
return (
< html lang = "en" >
< body >
< nav >Navigation </ nav >
{ children }
</ body >
</ html >
);
}
// app/page.tsx - Static page (SSG by default)
export default async function HomePage () {
const data = await fetch ( 'https://api.example.com/posts' , {
cache: 'force-cache' , // SSG: cached at build time
}). then ( res => res. json ());
return (
< div >
< h1 >Home Page (Static) </ h1 >
{ data . map ( post => < div key ={ post . id }>{post.title} </ div > )}
</ div >
);
}
// app/posts/[id]/page.tsx - Dynamic route with SSG
interface PageProps {
params : { id : string };
}
// Generate static paths at build time
export async function generateStaticParams () {
const posts = await fetch ( 'https://api.example.com/posts' ). then ( res => res. json ());
return posts. map (( post : any ) => ({
id: post.id. toString (),
}));
}
// Revalidate every 60 seconds (ISR)
export const revalidate = 60 ;
export default async function PostPage ({ params } : PageProps ) {
const post = await fetch ( `https://api.example.com/posts/${ params . id }` , {
next: { revalidate: 60 }, // Per-fetch revalidation
}). then ( res => res. json ());
return (
< article >
< h1 >{post.title} </ h1 >
< p >{post.content} </ p >
</ article >
);
}
// app/dashboard/page.tsx - Dynamic SSR (no cache)
export const dynamic = 'force-dynamic' ;
export default async function DashboardPage () {
const user = await getCurrentUser ();
const stats = await fetch ( `https://api.example.com/stats/${ user . id }` , {
cache: 'no-store' , // SSR: fetch on every request
}). then ( res => res. json ());
return (
< div >
< h1 > Dashboard (Dynamic SSR ) </ h1 >
< p > Views : { stats . views }</ p >
</ div >
);
}
// app/products/loading.tsx - Loading state
export default function Loading () {
return < div >Loading products ...</ div > ;
}
// app/products/error.tsx - Error boundary
'use client' ;
export default function Error ({
error ,
reset ,
} : {
error : Error ;
reset : () => void ;
}) {
return (
< div >
< h2 >Something went wrong !</ h2 >
< button onClick = {reset} > Try again </ button >
</ div >
);
}
// Client Component with 'use client'
'use client' ;
import { useState } from 'react' ;
export default function Counter () {
const [ count , setCount ] = useState ( 0 );
return (
< button onClick = {() => setCount ( count + 1)} >
Count : { count }
</ button >
);
}
5.2 Vite React SPA Client Rendering
Feature
Configuration
Description
Benefit
HMR
Hot Module Replacement
Instant updates without full page reload
Fast development
ESBuild
transform: { jsx: 'react' }
Lightning-fast JavaScript/TypeScript compilation
10-100x faster than webpack
Code Splitting
import()
Dynamic imports for lazy loading
Smaller initial bundle
Tree Shaking
build.rollupOptions
Dead code elimination in production
Optimized bundle size
CSS Modules
.module.css
Scoped CSS with automatic class names
No style conflicts
Environment Variables
import.meta.env.VITE_*
Access env vars prefixed with VITE_
Configuration management
Example: Vite React SPA with optimized configuration
// vite.config.ts
import { defineConfig } from 'vite' ;
import react from '@vitejs/plugin-react' ;
import { visualizer } from 'rollup-plugin-visualizer' ;
export default defineConfig ({
plugins: [
react ({
// Fast Refresh for HMR
fastRefresh: true ,
}),
visualizer ({
open: true ,
gzipSize: true ,
brotliSize: true ,
}),
],
build: {
// Target modern browsers
target: 'esnext' ,
// Manual chunk splitting
rollupOptions: {
output: {
manualChunks: {
vendor: [ 'react' , 'react-dom' ],
router: [ 'react-router-dom' ],
ui: [ '@mui/material' ],
},
},
},
// Source maps for production debugging
sourcemap: true ,
// Chunk size warning limit
chunkSizeWarningLimit: 1000 ,
},
server: {
port: 3000 ,
open: true ,
proxy: {
'/api' : {
target: 'http://localhost:8080' ,
changeOrigin: true ,
rewrite : ( path ) => path. replace ( / ^ \/ api/ , '' ),
},
},
},
resolve: {
alias: {
'@' : '/src' ,
'@components' : '/src/components' ,
'@utils' : '/src/utils' ,
},
},
});
// src/main.tsx - Entry point
import React from 'react' ;
import ReactDOM from 'react-dom/client' ;
import App from './App' ;
import './index.css' ;
ReactDOM. createRoot (document. getElementById ( 'root' ) ! ). render (
< React.StrictMode >
< App />
</ React.StrictMode >
);
// src/App.tsx - Lazy loading routes
import { lazy, Suspense } from 'react' ;
import { BrowserRouter, Routes, Route } from 'react-router-dom' ;
const Home = lazy (() => import ( './pages/Home' ));
const Dashboard = lazy (() => import ( './pages/Dashboard' ));
const Settings = lazy (() => import ( './pages/Settings' ));
function App () {
return (
< BrowserRouter >
< Suspense fallback = {< div >Loading...</ div >}>
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/dashboard" element = {< Dashboard />} />
< Route path = "/settings" element = {< Settings />} />
</ Routes >
</ Suspense >
</ BrowserRouter >
);
}
export default App;
// .env - Environment variables
VITE_API_URL = https : //api.example.com
VITE_API_KEY = abc123
// Using environment variables
const apiUrl = import . meta .env. VITE_API_URL ;
const apiKey = import . meta .env. VITE_API_KEY ;
// CSS Modules - Button.module.css
.button {
padding : 10px 20px;
background : blue;
}
// Button.tsx
import styles from './Button.module.css' ;
function Button () {
return < button className = {styles.button}>Click Me</ button >;
}
5.3 Nuxt 3 Universal Rendering
Feature
Syntax
Description
Use Case
Universal Rendering
SSR + Hydration
Server renders HTML, client hydrates with interactivity
Best of both worlds
Auto Imports
No import statements needed
Components, composables auto-imported
Less boilerplate
useFetch
const { data } = await useFetch('/api')
Universal data fetching with SSR support
Isomorphic data fetching
useAsyncData
useAsyncData('key', () => $fetch())
Manual async data fetching with caching
Complex data needs
Server Routes
server/api/*.ts
API routes built into Nuxt
Full-stack framework
Middleware
middleware/*.ts
Route guards for auth, validation
Navigation control
Example: Nuxt 3 universal rendering with data fetching
// nuxt.config.ts
export default defineNuxtConfig({
// Rendering mode
ssr: true,
// Hybrid rendering per route
routeRules: {
'/': { prerender: true }, // SSG
'/admin/**': { ssr: false }, // SPA
'/api/**': { cors: true },
'/blog/**': { swr: 3600 }, // ISR with 1h cache
},
// App configuration
app: {
head: {
title: 'Nuxt 3 App',
meta: [
{ name: 'description', content: 'Universal rendering example' }
],
},
},
// Modules
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
// Auto imports
imports: {
dirs: ['composables', 'utils'],
},
});
// pages/index.vue - SSR page with data fetching
< template >
< div >
< h1 >Posts</ h1 >
< div v-for = "post in posts" :key = "post.id" >
< h2 >{{ post.title }}</ h2 >
< p >{{ post.content }}</ p >
</ div >
</ div >
</ template >
< script setup >
// Auto-imported useFetch
const { data : posts } = await useFetch ( '/api/posts' );
// SEO metadata
useHead ({
title: 'Blog Posts' ,
meta: [
{ name: 'description' , content: 'Latest blog posts' }
],
});
</ script >
// pages/post/[id].vue - Dynamic route with SSR
< template >
< article >
< h1 >{{ post?.title }}</ h1 >
< p >{{ post?.content }}</ p >
</ article >
</ template >
< script setup >
const route = useRoute ();
const { data : post } = await useAsyncData (
`post-${ route . params . id }` ,
() => $fetch ( `/api/posts/${ route . params . id }` )
);
</ script >
// server/api/posts.ts - API route
export default defineEventHandler(async (event) => {
const posts = await fetchPostsFromDB();
return posts;
});
// server/api/posts/[id].ts - Dynamic API route
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const post = await fetchPostById(id);
if (!post) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found',
});
}
return post;
});
// composables/useAuth.ts - Auto-imported composable
export const useAuth = () => {
const user = useState('user', () => null);
const login = async (credentials) => {
const data = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials,
});
user.value = data.user;
};
const logout = () => {
user.value = null;
};
return { user, login, logout };
};
// middleware/auth.ts - Route middleware
export default defineNuxtRouteMiddleware((to, from) => {
const { user } = useAuth();
if (!user.value && to.path !== '/login') {
return navigateTo('/login');
}
});
// pages/dashboard.vue - Protected route
< template >
< div >Dashboard for {{ user?.name }}</ div >
</ template >
< script setup >
definePageMeta ({
middleware: 'auth' ,
});
const { user } = useAuth ();
</ script >
5.4 React 18 Concurrent Features NEW
Feature
API
Description
Use Case
Concurrent Rendering
createRoot()
Interruptible rendering for better UX
Responsive UI
useTransition
const [isPending, startTransition] = useTransition()
Marks updates as non-urgent, allows interruption
Smooth UI transitions
useDeferredValue
const deferredValue = useDeferredValue(value)
Defers value updates to prioritize urgent renders
Debounced rendering
Suspense
<Suspense fallback={...}>
Declarative loading states for async components
Code splitting, data fetching
startTransition
startTransition(() => setState())
Marks state updates as low-priority
Heavy computations
useId
const id = useId()
Generates unique IDs for SSR compatibility
Accessible forms
Example: React 18 concurrent features in action
// main.tsx - Concurrent mode with createRoot
import { createRoot } from 'react-dom/client' ;
import App from './App' ;
const container = document. getElementById ( 'root' ) ! ;
const root = createRoot (container);
root. render (< App />);
// useTransition for non-blocking updates
import { useState, useTransition } from 'react' ;
function SearchResults () {
const [ query , setQuery ] = useState ( '' );
const [ results , setResults ] = useState ([]);
const [ isPending , startTransition ] = useTransition ();
const handleSearch = ( value : string ) => {
setQuery (value); // Urgent: update input immediately
// Non-urgent: expensive search operation
startTransition (() => {
const filtered = expensiveSearch (value);
setResults (filtered);
});
};
return (
< div >
< input
value = {query}
onChange = {( e ) => handleSearch (e.target.value)}
placeholder = "Search..."
/>
{isPending && < div >Searching...</ div >}
< ul >
{results. map ( item => < li key = {item.id}>{item.name}</ li >)}
</ ul >
</ div >
);
}
// useDeferredValue for responsive UI
function ProductList ({ searchQuery } : { searchQuery : string }) {
// Deferred value updates slower, keeps input responsive
const deferredQuery = useDeferredValue (searchQuery);
const [ products , setProducts ] = useState ([]);
useEffect (() => {
// Heavy filtering operation uses deferred value
const filtered = expensiveFilter (deferredQuery);
setProducts (filtered);
}, [deferredQuery]);
return (
< div >
{products. map ( product => (
< div key = {product.id}>{product.name}</ div >
))}
</ div >
);
}
// Suspense for code splitting and data fetching
import { lazy, Suspense } from 'react' ;
const HeavyComponent = lazy (() => import ( './HeavyComponent' ));
function App () {
return (
< Suspense fallback = {< div >Loading component...</ div >}>
< HeavyComponent />
</ Suspense >
);
}
// Suspense with data fetching (using libraries like Relay, React Query)
function UserProfile ({ userId } : { userId : string }) {
return (
< Suspense fallback = {< ProfileSkeleton />}>
< ProfileDetails userId = {userId} />
< Suspense fallback = {< PostsSkeleton />}>
< UserPosts userId = {userId} />
</ Suspense >
</ Suspense >
);
}
// useId for SSR-safe unique IDs
function FormField ({ label } : { label : string }) {
const id = useId ();
return (
< div >
< label htmlFor = {id}>{label}</ label >
< input id = {id} type = "text" />
</ div >
);
}
// Combining concurrent features
function Dashboard () {
const [ activeTab , setActiveTab ] = useState ( 'overview' );
const [ isPending , startTransition ] = useTransition ();
const handleTabChange = ( tab : string ) => {
startTransition (() => {
setActiveTab (tab);
});
};
return (
< div >
< nav >
< button onClick = {() => handleTabChange ( 'overview' )}>Overview</ button >
< button onClick = {() => handleTabChange ( 'analytics' )}>Analytics</ button >
< button onClick = {() => handleTabChange ( 'reports' )}>Reports</ button >
</ nav >
{isPending && < div >Loading...</ div >}
< Suspense fallback = {< div >Loading content...</ div >}>
< TabContent tab = {activeTab} />
</ Suspense >
</ div >
);
}
// Automatic batching (React 18)
function Counter () {
const [ count , setCount ] = useState ( 0 );
const [ flag , setFlag ] = useState ( false );
const handleClick = () => {
// Both updates batched automatically (even in async)
setTimeout (() => {
setCount ( c => c + 1 );
setFlag ( f => ! f);
// Only one re-render
}, 1000 );
};
return < button onClick = {handleClick}>Count: {count}</ button >;
}
Component
Syntax
Description
Use Case
FixedSizeList
<FixedSizeList height={} itemCount={} itemSize={}>
List with fixed-height items
Uniform item heights
VariableSizeList
<VariableSizeList itemSize={(index) => {}}>
List with dynamic item heights
Variable content sizes
FixedSizeGrid
<FixedSizeGrid columnCount={} rowCount={}>
Grid with fixed cell sizes
Spreadsheets, tables
Windowing
Only renders visible items
Renders only what's in viewport + buffer
Performance with huge lists
react-virtuoso
<Virtuoso data={} itemContent={}>
Advanced virtualization with features
Complex scrolling needs
import { FixedSizeList, VariableSizeList } from 'react-window' ;
import AutoSizer from 'react-virtualized-auto-sizer' ;
// Fixed-size list
function VirtualList ({ items } : { items : any [] }) {
const Row = ({ index , style } : { index : number ; style : React . CSSProperties }) => (
< div style = {style}>
{items[index].name}
</ div >
);
return (
< FixedSizeList
height = { 600 }
itemCount = {items. length }
itemSize = { 50 }
width = "100%"
>
{Row}
</ FixedSizeList >
);
}
// Variable-size list
function VariableList ({ items } : { items : any [] }) {
const itemSizes = useRef < number []>([]);
const getItemSize = ( index : number ) => {
// Calculate or cache item heights
return itemSizes.current[index] || 50 ;
};
const Row = ({ index , style } : any ) => {
const rowRef = useRef < HTMLDivElement >( null );
useEffect (() => {
if (rowRef.current) {
itemSizes.current[index] = rowRef.current.offsetHeight;
}
}, [index]);
return (
< div ref = {rowRef} style = {style}>
{items[index].content}
</ div >
);
};
return (
< VariableSizeList
height = { 600 }
itemCount = {items. length }
itemSize = {getItemSize}
width = "100%"
>
{Row}
</ VariableSizeList >
);
}
// With AutoSizer for responsive dimensions
function ResponsiveList ({ items } : { items : any [] }) {
return (
< div style = {{ height: '100vh' }}>
< AutoSizer >
{({ height , width }) => (
< FixedSizeList
height = {height}
width = {width}
itemCount = {items. length }
itemSize = { 50 }
>
{({ index , style }) => (
< div style = {style}>{items[index].name}</ div >
)}
</ FixedSizeList >
)}
</ AutoSizer >
</ div >
);
}
// Infinite scroll with react-window
function InfiniteList ({ loadMore } : { loadMore : () => Promise < void > }) {
const [ items , setItems ] = useState < any []>([]);
const [ hasMore , setHasMore ] = useState ( true );
const [ isLoading , setIsLoading ] = useState ( false );
const loadMoreItems = async () => {
if (isLoading || ! hasMore) return ;
setIsLoading ( true );
const newItems = await loadMore ();
setItems ( prev => [ ... prev, ... newItems]);
setHasMore (newItems. length > 0 );
setIsLoading ( false );
};
const isItemLoaded = ( index : number ) => ! hasMore || index < items. length ;
return (
< InfiniteLoader
isItemLoaded = {isItemLoaded}
itemCount = {hasMore ? items. length + 1 : items. length }
loadMoreItems = {loadMoreItems}
>
{({ onItemsRendered , ref }) => (
< FixedSizeList
height = { 600 }
itemCount = {items. length }
itemSize = { 50 }
onItemsRendered = {onItemsRendered}
ref = {ref}
width = "100%"
>
{({ index , style }) => (
< div style = {style}>
{ isItemLoaded (index) ? items[index].name : 'Loading...' }
</ div >
)}
</ FixedSizeList >
)}
</ InfiniteLoader >
);
}
// react-virtuoso (alternative with better features)
import { Virtuoso } from 'react-virtuoso' ;
function VirtuosoList ({ items } : { items : any [] }) {
return (
< Virtuoso
style = {{ height: '600px' }}
data = {items}
itemContent = {( index , item ) => (
< div >
< h3 >{item.title}</ h3 >
< p >{item.description}</ p >
</ div >
)}
endReached = {() => console. log ( 'Reached end' )}
/>
);
}
5.6 Streaming SSR Suspense Components
Feature
Implementation
Description
Benefit
Streaming SSR
renderToPipeableStream()
Sends HTML chunks as they're ready
Faster Time to First Byte
Selective Hydration
React 18 automatic
Hydrates components in priority order
Interactive sooner
Suspense Boundaries
<Suspense fallback={...}>
Stream different parts independently
Progressive enhancement
Server Components
Next.js 13+ RSC
Zero-JS components on server
Reduced bundle size
Progressive Hydration
Load JS incrementally
Prioritize visible/interactive parts
Better perceived performance
Example: Streaming SSR with Suspense boundaries
// server.js - Node.js streaming SSR
import { renderToPipeableStream } from 'react-dom/server' ;
import App from './App' ;
app. get ( '/' , ( req , res ) => {
res. setHeader ( 'Content-Type' , 'text/html' );
const { pipe , abort } = renderToPipeableStream (< App />, {
bootstrapScripts: [ '/main.js' ],
onShellReady () {
// Send initial shell immediately
res.statusCode = 200 ;
pipe (res);
},
onShellError ( error ) {
res.statusCode = 500 ;
res. send ( '<h1>Server Error</h1>' );
},
onError ( error ) {
console. error ( 'Streaming error:' , error);
},
});
setTimeout (abort, 10000 ); // Abort after 10s
});
// App.tsx - Multiple Suspense boundaries
function App () {
return (
< html >
< head >
< title >Streaming SSR</ title >
</ head >
< body >
< nav >Navigation (renders immediately)</ nav >
{ /* High priority content */ }
< Suspense fallback = {< HeroSkeleton />}>
< Hero />
</ Suspense >
{ /* Stream independently */ }
< Suspense fallback = {< ProductsSkeleton />}>
< Products />
</ Suspense >
{ /* Lower priority, loads last */ }
< Suspense fallback = {< CommentsSkeleton />}>
< Comments />
</ Suspense >
< footer >Footer (renders immediately)</ footer >
</ body >
</ html >
);
}
// Async component with data fetching
function Products () {
const products = use ( fetchProducts ()); // React 19 use() hook
return (
< div >
{products. map ( p => < ProductCard key = {p.id} product = {p} />)}
</ div >
);
}
// Next.js 13+ App Router with streaming
// app/page.tsx
import { Suspense } from 'react' ;
export default function Page () {
return (
< div >
< h1 >Dashboard</ h1 >
{ /* Fast content renders first */ }
< Suspense fallback = {< Skeleton />}>
< FastComponent />
</ Suspense >
{ /* Slow content streams later */ }
< Suspense fallback = {< Skeleton />}>
< SlowComponent />
</ Suspense >
</ div >
);
}
// Server Component (no JS sent to client)
async function FastComponent () {
const data = await fetch ( 'https://api.example.com/fast' ). then ( r => r. json ());
return < div >{data.title}</ div >;
}
// Slow async component
async function SlowComponent () {
await new Promise ( resolve => setTimeout (resolve, 3000 ));
const data = await fetch ( 'https://api.example.com/slow' ). then ( r => r. json ());
return < div >{data.content}</ div >;
}
// Client Component for interactivity
'use client' ;
import { useState } from 'react' ;
function InteractiveWidget () {
const [ count , setCount ] = useState ( 0 );
return < button onClick = {() => setCount (count + 1 )}>{count}</ button >;
}
// Streaming timeline visualization:
// Time 0ms: HTML shell + nav + footer sent
// Time 100ms: Hero content streams in
// Time 500ms: Products stream in
// Time 2000ms: Comments stream in
// User sees content progressively, not all at once
Rendering Strategy Comparison
Strategy
Initial Load
SEO
Interactivity
Best For
SSR (Next.js)
Fast (pre-rendered)
Excellent
Hydration delay
Content-heavy, SEO-critical
SSG (Static)
Fastest (CDN)
Excellent
Hydration delay
Blogs, docs, marketing
SPA (Vite)
Slow (blank page)
Poor
Instant
Web apps, dashboards
Streaming SSR
Progressive
Excellent
Progressive
Large pages, mixed content
ISR (Next.js)
Fast (cached)
Excellent
Hydration delay
E-commerce, dynamic content
6. Responsive Design Implementation Patterns
6.1 CSS Grid Flexbox Layout Systems
Property
Syntax
Description
Use Case
CSS Grid
display: grid
2D layout system for rows and columns
Page layouts, complex grids
grid-template-columns
repeat(auto-fit, minmax(250px, 1fr))
Responsive columns without media queries
Card grids, galleries
Flexbox
display: flex
1D layout for rows or columns
Navigation, components
flex-wrap
flex-wrap: wrap
Items wrap to next line when space runs out
Responsive lists
gap
gap: 1rem
Spacing between grid/flex items
Consistent spacing
Grid Areas
grid-template-areas
Named template areas for semantic layouts
Complex page structures
Example: CSS Grid and Flexbox responsive layouts
/* Responsive Grid - Auto-fit with minmax */
.card-grid {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
gap : 2 rem ;
padding : 1 rem ;
}
/* Responsive without media queries! */
.card {
background : white ;
padding : 1.5 rem ;
border-radius : 8 px ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
}
/* CSS Grid with named areas */
.page-layout {
display : grid ;
grid-template-areas :
"header header header"
"sidebar main aside"
"footer footer footer" ;
grid-template-columns : 200 px 1 fr 200 px ;
grid-template-rows : auto 1 fr auto ;
gap : 1 rem ;
min-height : 100 vh ;
}
.header { grid-area : header; }
.sidebar { grid-area : sidebar; }
.main { grid-area : main; }
.aside { grid-area : aside; }
.footer { grid-area : footer; }
/* Responsive grid areas */
@media ( max-width : 768 px ) {
.page-layout {
grid-template-areas :
"header"
"main"
"sidebar"
"aside"
"footer" ;
grid-template-columns : 1 fr ;
}
}
/* Flexbox responsive navigation */
.nav {
display : flex ;
justify-content : space-between ;
align-items : center ;
flex-wrap : wrap ;
gap : 1 rem ;
padding : 1 rem ;
}
.nav-links {
display : flex ;
gap : 2 rem ;
flex-wrap : wrap ;
}
/* Responsive flex direction */
.container {
display : flex ;
flex-direction : row ;
gap : 2 rem ;
}
@media ( max-width : 768 px ) {
.container {
flex-direction : column ;
}
}
/* Holy Grail Layout with Flexbox */
.holy-grail {
display : flex ;
flex-direction : column ;
min-height : 100 vh ;
}
.holy-grail-body {
display : flex ;
flex : 1 ;
}
.holy-grail-content {
flex : 1 ;
}
.holy-grail-nav ,
.holy-grail-ads {
flex : 0 0 12 em ;
}
.holy-grail-nav {
order : -1 ;
}
@media ( max-width : 768 px ) {
.holy-grail-body {
flex-direction : column ;
}
.holy-grail-nav ,
.holy-grail-ads {
order : 0 ;
}
}
/* Advanced Grid - Masonry-style layout */
.masonry {
display : grid ;
grid-template-columns : repeat ( auto-fill , minmax ( 250 px , 1 fr ));
grid-auto-rows : 10 px ;
gap : 1 rem ;
}
.masonry-item {
grid-row-end : span 20 ; /* Adjust based on content height */
}
/* Subgrid (modern browsers) */
.parent-grid {
display : grid ;
grid-template-columns : repeat ( 3 , 1 fr );
gap : 1 rem ;
}
.child-grid {
display : grid ;
grid-template-columns : subgrid ;
grid-column : span 3 ;
}
6.2 Tailwind CSS Responsive Utilities
Breakpoint
Syntax
Min-Width
Example
sm
sm:text-lg
640px
Small devices (tablets)
md
md:flex-row
768px
Medium devices (small laptops)
lg
lg:grid-cols-3
1024px
Large devices (desktops)
xl
xl:container
1280px
Extra large screens
2xl
2xl:px-8
1536px
Ultra-wide screens
Custom
theme.screens
Configurable
Custom breakpoints
Example: Tailwind CSS responsive design patterns
// tailwind.config.js - Custom breakpoints
module.exports = {
theme: {
screens: {
'xs': '475px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
'3xl': '1920px',
},
extend: {
spacing: {
'128': '32rem',
},
},
},
};
{/* Responsive grid with Tailwind */}
< div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" >
< div className = "bg-white p-4 rounded-lg shadow" >Card 1</ div >
< div className = "bg-white p-4 rounded-lg shadow" >Card 2</ div >
< div className = "bg-white p-4 rounded-lg shadow" >Card 3</ div >
</ div >
{/* Mobile-first responsive typography */}
< h1 className = "text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-bold" >
Responsive Heading
</ h1 >
{/* Responsive flex direction */}
< div className = "flex flex-col md:flex-row gap-4" >
< aside className = "w-full md:w-1/4 bg-gray-100 p-4" >Sidebar</ aside >
< main className = "w-full md:w-3/4 p-4" >Main Content</ main >
</ div >
{/* Responsive visibility */}
< button className = "hidden md:block" >Desktop Only</ button >
< button className = "md:hidden" >Mobile Only</ button >
{/* Responsive padding and margins */}
< div className = "px-4 sm:px-6 md:px-8 lg:px-12 xl:px-16" >
< div className = "max-w-7xl mx-auto" >
Centered content with responsive padding
</ div >
</ div >
{/* Responsive navigation */}
< nav className = "bg-white shadow" >
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
< div className = "flex justify-between h-16" >
< div className = "flex" >
< div className = "flex-shrink-0 flex items-center" >
Logo
</ div >
< div className = "hidden sm:ml-6 sm:flex sm:space-x-8" >
< a href = "#" className = "border-b-2 px-3 py-2" >Home</ a >
< a href = "#" className = "px-3 py-2" >About</ a >
< a href = "#" className = "px-3 py-2" >Contact</ a >
</ div >
</ div >
< div className = "sm:hidden" >
< button >Menu</ button >
</ div >
</ div >
</ div >
</ nav >
{/* Responsive container with breakpoint-specific max-widths */}
< div className = "container mx-auto px-4 sm:px-6 lg:px-8" >
{/* Container automatically adjusts max-width at each breakpoint */}
</ div >
{/* Arbitrary values for custom responsive design */}
< div className = "grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4" >
Custom grid
</ div >
{/* Responsive aspect ratios */}
< div className = "aspect-video md:aspect-square lg:aspect-[16/9]" >
< img src = "image.jpg" className = "w-full h-full object-cover" />
</ div >
{/* Dark mode + responsive */}
< div className = "bg-white dark:bg-gray-800 p-4 sm:p-6 md:p-8" >
< h2 className = "text-gray-900 dark:text-white text-lg sm:text-xl md:text-2xl" >
Responsive dark mode content
</ h2 >
</ div >
6.3 Container Queries CSS Modern NEW
Property
Syntax
Description
Advantage
container-type
container-type: inline-size
Establishes containment context
Component-level responsiveness
container-name
container-name: card
Names container for querying
Multiple container contexts
@container
@container (min-width: 400px)
Query container dimensions, not viewport
Truly reusable components
cqw/cqh
width: 50cqw
Container query width/height units
Fluid component sizing
container shorthand
container: card / inline-size
Combined name and type declaration
Cleaner syntax
Example: Container queries for component-based responsiveness
/* Container query setup */
.card-container {
container-type : inline-size;
container-name : card;
}
/* Component styles based on container width */
.card {
display : flex ;
flex-direction : column ;
gap : 1 rem ;
padding : 1 rem ;
background : white ;
border-radius : 8 px ;
}
/* When container is at least 400px wide */
@container card (min-width: 400px) {
.card {
flex-direction : row ;
align-items : center ;
}
.card-image {
width : 200 px ;
height : 200 px ;
}
.card-content {
flex : 1 ;
}
}
/* When container is at least 600px wide */
@container card (min-width: 600px) {
.card {
padding : 2 rem ;
}
.card-title {
font-size : 2 rem ;
}
}
/* Product card with container queries */
.product-grid {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
gap : 2 rem ;
}
.product-card {
container-type : inline-size;
background : white ;
border-radius : 12 px ;
overflow : hidden ;
}
.product-content {
padding : 1 rem ;
}
.product-details {
display : none ; /* Hidden by default */
}
/* Show details when card is wide enough */
@container (min-width: 350px) {
.product-details {
display : block ;
}
.product-button {
width : 100 % ;
}
}
@container (min-width: 500px) {
.product-content {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
gap : 1 rem ;
}
}
/* Container query units */
.responsive-text {
container-type : inline-size;
}
.responsive-text h1 {
font-size : calc ( 5 cqw + 1 rem );
/* Font size relative to container width */
}
/* Named containers with Tailwind (plugin) */
.sidebar {
container-type : inline-size;
container-name : sidebar;
}
@container sidebar (min-width: 300px) {
.sidebar-nav {
flex-direction : row ;
}
}
/* Nested containers */
.page {
container-type : inline-size;
container-name : page ;
}
.section {
container-type : inline-size;
container-name : section;
}
/* Query specific container */
@container page (min-width: 1200px) {
.page-header {
display : flex ;
}
}
@container section (min-width: 400px) {
.section-content {
columns : 2 ;
}
}
/* React component with container queries */
function ProductCard({ product }) {
return (
< div className =" product-card ">
< img src ={ product . image } alt={ product . name } / >
< div className=" product-content " >
< h3 > { product . name }</ h3 >
< p className=" product-price " > ${ product . price }</ p >
< p className=" product-details " > { product . description }</ p >
< button className=" product-button " > Add to Cart</ button >
</ div >
</ div >
);
}
6.4 Intersection Observer Lazy Loading
Feature
Syntax
Description
Use Case
IntersectionObserver
new IntersectionObserver(callback)
Observes element visibility in viewport
Lazy loading, infinite scroll
threshold
{ threshold: 0.5 }
Percentage of element visible to trigger
Precise loading control
rootMargin
{ rootMargin: '100px' }
Margin around viewport to trigger early
Preload before visible
observe()
observer.observe(element)
Start observing an element
Track visibility
unobserve()
observer.unobserve(element)
Stop observing an element
Cleanup after loading
loading="lazy"
<img loading="lazy">
Native browser lazy loading
Simple image/iframe lazy load
Example: Intersection Observer for lazy loading
// Vanilla JS lazy loading images
const images = document. querySelectorAll ( 'img[data-src]' );
const imageObserver = new IntersectionObserver (( entries , observer ) => {
entries. forEach ( entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList. add ( 'loaded' );
observer. unobserve (img);
}
});
}, {
rootMargin: '50px' , // Load 50px before entering viewport
threshold: 0.1 ,
});
images. forEach ( img => imageObserver. observe (img));
// React lazy loading component
import { useEffect, useRef, useState } from 'react' ;
function LazyImage ({ src , alt , placeholder }) {
const [ isLoaded , setIsLoaded ] = useState ( false );
const [ isInView , setIsInView ] = useState ( false );
const imgRef = useRef < HTMLImageElement >( null );
useEffect (() => {
const observer = new IntersectionObserver (
([ entry ]) => {
if (entry.isIntersecting) {
setIsInView ( true );
observer. disconnect ();
}
},
{ rootMargin: '100px' }
);
if (imgRef.current) {
observer. observe (imgRef.current);
}
return () => observer. disconnect ();
}, []);
return (
< img
ref = {imgRef}
src = {isInView ? src : placeholder}
alt = {alt}
onLoad = {() => setIsLoaded ( true )}
style = {{
opacity: isLoaded ? 1 : 0.5 ,
transition: 'opacity 0.3s' ,
}}
/>
);
}
// Infinite scroll implementation
function InfiniteScroll () {
const [ items , setItems ] = useState ([]);
const [ page , setPage ] = useState ( 1 );
const [ hasMore , setHasMore ] = useState ( true );
const loaderRef = useRef ( null );
useEffect (() => {
const observer = new IntersectionObserver (
( entries ) => {
if (entries[ 0 ].isIntersecting && hasMore) {
loadMore ();
}
},
{ threshold: 1.0 }
);
if (loaderRef.current) {
observer. observe (loaderRef.current);
}
return () => observer. disconnect ();
}, [hasMore, page]);
const loadMore = async () => {
const newItems = await fetchItems (page);
setItems ( prev => [ ... prev, ... newItems]);
setPage ( prev => prev + 1 );
setHasMore (newItems. length > 0 );
};
return (
< div >
{items. map ( item => < div key = {item.id}>{item.name}</ div >)}
< div ref = {loaderRef}>{hasMore && 'Loading...' }</ div >
</ div >
);
}
// Custom hook for intersection observer
function useIntersectionObserver (
ref : React . RefObject < Element >,
options : IntersectionObserverInit = {}
) {
const [ isIntersecting , setIsIntersecting ] = useState ( false );
useEffect (() => {
const observer = new IntersectionObserver (([ entry ]) => {
setIsIntersecting (entry.isIntersecting);
}, options);
if (ref.current) {
observer. observe (ref.current);
}
return () => observer. disconnect ();
}, [ref, options]);
return isIntersecting;
}
// Usage
function AnimatedSection () {
const ref = useRef ( null );
const isVisible = useIntersectionObserver (ref, { threshold: 0.5 });
return (
< div
ref = {ref}
style = {{
opacity: isVisible ? 1 : 0 ,
transform: isVisible ? 'translateY(0)' : 'translateY(50px)' ,
transition: 'all 0.6s ease-out' ,
}}
>
Content fades in when 50% visible
</ div >
);
}
// Native lazy loading (simpler approach)
< img
src = "image.jpg"
alt = "Description"
loading = "lazy"
decoding = "async"
/>
< iframe
src = "https://example.com"
loading = "lazy"
></ iframe >
// Background image lazy loading
function LazyBackground ({ imageUrl , children }) {
const [ isLoaded , setIsLoaded ] = useState ( false );
const divRef = useRef ( null );
useEffect (() => {
const observer = new IntersectionObserver (([ entry ]) => {
if (entry.isIntersecting) {
setIsLoaded ( true );
observer. disconnect ();
}
});
if (divRef.current) {
observer. observe (divRef.current);
}
return () => observer. disconnect ();
}, []);
return (
< div
ref = {divRef}
style = {{
backgroundImage: isLoaded ? `url(${ imageUrl })` : 'none' ,
backgroundColor: '#f0f0f0' ,
backgroundSize: 'cover' ,
}}
>
{children}
</ div >
);
}
6.5 Mobile-First Breakpoint Strategy
Breakpoint
Device
Approach
Best Practice
Base (mobile)
320px - 639px
Default styles, no media query
Single column, stack elements
sm (tablet)
640px+
@media (min-width: 640px)
2 columns, larger text
md (laptop)
768px+
@media (min-width: 768px)
Sidebar layouts, 3 columns
lg (desktop)
1024px+
@media (min-width: 1024px)
Multi-column grids, hover states
xl (wide)
1280px+
@media (min-width: 1280px)
Max content width, more spacing
Touch-first
All mobile
44px touch targets, no hover
Accessibility, usability
Example: Mobile-first responsive design
/* Mobile-first CSS approach */
/* Base styles (mobile, no media query) */
.container {
width : 100 % ;
padding : 1 rem ;
}
.grid {
display : grid ;
grid-template-columns : 1 fr ; /* Single column on mobile */
gap : 1 rem ;
}
.nav {
flex-direction : column ;
}
.button {
min-height : 44 px ; /* Touch-friendly target */
width : 100 % ;
font-size : 16 px ; /* Prevents zoom on iOS */
}
/* Tablet (640px+) */
@media ( min-width : 640 px ) {
.container {
padding : 2 rem ;
}
.grid {
grid-template-columns : repeat ( 2 , 1 fr );
}
.button {
width : auto ;
min-width : 120 px ;
}
}
/* Laptop (768px+) */
@media ( min-width : 768 px ) {
.container {
max-width : 768 px ;
margin : 0 auto ;
}
.grid {
grid-template-columns : repeat ( 3 , 1 fr );
gap : 2 rem ;
}
.nav {
flex-direction : row ;
justify-content : space-between ;
}
/* Show hover states only on larger screens */
.button:hover {
background-color : #0056b3 ;
}
}
/* Desktop (1024px+) */
@media ( min-width : 1024 px ) {
.container {
max-width : 1024 px ;
}
.grid {
grid-template-columns : repeat ( 4 , 1 fr );
}
.sidebar {
display : block ; /* Show sidebar on desktop */
}
}
/* Wide screens (1280px+) */
@media ( min-width : 1280 px ) {
.container {
max-width : 1280 px ;
padding : 3 rem ;
}
}
/* React mobile-first component */
function ResponsiveCard() {
return (
< div className ="
/* Mobile base */
flex flex-col
p- 4
w-full
/* Tablet */
sm :flex-row
sm:p-6
/* Laptop */
md:max-w-2xl
md:mx-auto
/* Desktop */
lg:max-w-4xl
lg:p-8
" >
<div className= "
/* Mobile: full width image */
w-full
h-48
/* Tablet: side image */
sm:w-1/ 3
sm:h-auto
/* Laptop: larger */
md:w-1/ 4
" >
<img src= "image.jpg" alt= "Card" className= "w-full h-full object-cover" />
</div>
<div className= "
/* Mobile: full width content */
w-full
mt-4
/* Tablet: beside image */
sm:w-2/ 3
sm:mt-0
sm:pl-6
/* Laptop: more space */
md:w-3/ 4
md:pl-8
" >
<h2 className= "text-xl sm:text-2xl md:text-3xl lg:text-4xl" >
Responsive Title
</h2>
<p className= "mt-2 text-sm sm:text-base md:text-lg" >
Description text
</p>
</div>
</div>
);
}
/* Mobile-first typography scale */
:root {
/* Mobile base sizes */
--text-xs : 0.75 rem ;
--text-sm : 0.875 rem ;
--text-base : 1 rem ;
--text-lg : 1.125 rem ;
--text-xl : 1.25 rem ;
--text-2xl : 1.5 rem ;
}
@media ( min-width : 768 px ) {
:root {
/* Larger text on desktop */
--text-base : 1.125 rem ;
--text-lg : 1.25 rem ;
--text-xl : 1.5 rem ;
--text-2xl : 2 rem ;
}
}
/* Touch-first interactions */
.interactive {
/* Minimum touch target size */
min-width : 44 px ;
min-height : 44 px ;
/* No hover effects on touch devices */
-webkit-tap-highlight-color : transparent ;
}
@media ( hover : hover ) {
/* Only add hover effects on devices that support hover */
.interactive:hover {
background-color : #f0f0f0 ;
}
}
/* Mobile-first navigation */
.mobile-nav {
display : flex ;
flex-direction : column ;
position : fixed ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 100 % ;
background : white ;
transform : translateX ( -100 % );
transition : transform 0.3 s ;
}
.mobile-nav.open {
transform : translateX ( 0 );
}
@media ( min-width : 768 px ) {
.mobile-nav {
position : static ;
flex-direction : row ;
width : auto ;
height : auto ;
transform : none ;
}
}
6.6 Adaptive Loading Network Conditions
API
Property
Description
Use Case
Network Information
navigator.connection
Access network connection info
Adapt to connection quality
effectiveType
connection.effectiveType
'4g', '3g', '2g', 'slow-2g'
Load different assets
saveData
connection.saveData
User enabled data saver mode
Reduce data usage
deviceMemory
navigator.deviceMemory
Device RAM in GB
Load lighter alternatives
hardwareConcurrency
navigator.hardwareConcurrency
Number of CPU cores
Adjust processing tasks
Adaptive Loading
react-adaptive-hooks
React hooks for adaptive loading
Component-level adaptation
Example: Adaptive loading based on network and device
// Check network connection
function getNetworkInfo () {
const connection = navigator.connection
|| navigator.mozConnection
|| navigator.webkitConnection;
if ( ! connection) {
return { effectiveType: '4g' , saveData: false };
}
return {
effectiveType: connection.effectiveType,
saveData: connection.saveData,
downlink: connection.downlink,
rtt: connection.rtt,
};
}
// Adaptive image loading
function AdaptiveImage ({ src , alt }) {
const network = getNetworkInfo ();
// Load different image quality based on connection
const imageSrc = network.effectiveType === '4g' && ! network.saveData
? src.high
: network.effectiveType === '3g'
? src.medium
: src.low;
return < img src = {imageSrc} alt = {alt} loading = "lazy" />;
}
// React hook for network status
import { useEffect, useState } from 'react' ;
function useNetworkStatus () {
const [ status , setStatus ] = useState ({
online: navigator.onLine,
effectiveType: '4g' ,
saveData: false ,
});
useEffect (() => {
const connection = navigator.connection;
const updateNetworkStatus = () => {
setStatus ({
online: navigator.onLine,
effectiveType: connection?.effectiveType || '4g' ,
saveData: connection?.saveData || false ,
});
};
updateNetworkStatus ();
window. addEventListener ( 'online' , updateNetworkStatus);
window. addEventListener ( 'offline' , updateNetworkStatus);
connection?. addEventListener ( 'change' , updateNetworkStatus);
return () => {
window. removeEventListener ( 'online' , updateNetworkStatus);
window. removeEventListener ( 'offline' , updateNetworkStatus);
connection?. removeEventListener ( 'change' , updateNetworkStatus);
};
}, []);
return status;
}
// Usage
function VideoPlayer ({ videoUrl }) {
const { effectiveType , saveData , online } = useNetworkStatus ();
if ( ! online) {
return < div >You are offline</ div >;
}
// Adapt video quality
const quality = saveData || effectiveType === '2g' ? 'low'
: effectiveType === '3g' ? 'medium'
: 'high' ;
return (
< video src = {videoUrl[quality]} controls autoPlay = {effectiveType === '4g' } />
);
}
// Device capability detection
function useDeviceCapabilities () {
return {
memory: navigator.deviceMemory || 4 ,
cores: navigator.hardwareConcurrency || 2 ,
platform: navigator.platform,
};
}
// Adaptive component loading
function Dashboard () {
const { memory , cores } = useDeviceCapabilities ();
const { effectiveType , saveData } = useNetworkStatus ();
// Load heavy components only on capable devices with good connection
const shouldLoadHeavyFeatures =
memory >= 4 &&
cores >= 4 &&
effectiveType === '4g' &&
! saveData;
return (
< div >
< BasicDashboard />
{shouldLoadHeavyFeatures && (
< Suspense fallback = {< Skeleton />}>
< HeavyChart />
< RealTimeData />
</ Suspense >
)}
</ div >
);
}
// Adaptive image component with multiple strategies
function SmartImage ({ src , alt , sizes }) {
const { effectiveType , saveData } = useNetworkStatus ();
const { memory } = useDeviceCapabilities ();
// Preload critical images on fast connections
useEffect (() => {
if (effectiveType === '4g' && ! saveData) {
const link = document. createElement ( 'link' );
link.rel = 'preload' ;
link.as = 'image' ;
link.href = src;
document.head. appendChild (link);
}
}, [src, effectiveType, saveData]);
// Format based on device capability
const format = memory >= 4 ? 'webp' : 'jpg' ;
return (
< picture >
{ ! saveData && effectiveType === '4g' && (
< source srcSet = { `${ src }.${ format } 2x` } type = { `image/${ format }` } />
)}
< source srcSet = { `${ src }.${ format }` } type = { `image/${ format }` } />
< img src = { `${ src }.jpg` } alt = {alt} loading = "lazy" />
</ picture >
);
}
// react-adaptive-hooks library
import {
useNetworkStatus,
useSaveData,
useHardwareConcurrency,
useMemoryStatus,
} from 'react-adaptive-hooks' ;
function AdaptiveApp () {
const { effectiveConnectionType } = useNetworkStatus ();
const { saveData } = useSaveData ();
const { numberOfLogicalProcessors } = useHardwareConcurrency ();
const { deviceMemory } = useMemoryStatus ();
const isLowEndDevice = deviceMemory < 4 || numberOfLogicalProcessors < 4 ;
const isSlowNetwork = effectiveConnectionType === '2g' || effectiveConnectionType === '3g' ;
return (
< div >
{isLowEndDevice || isSlowNetwork || saveData ? (
< LightweightComponent />
) : (
< FeatureRichComponent />
)}
</ div >
);
}
// Adaptive font loading
if (navigator.connection?.effectiveType === '4g' && ! navigator.connection?.saveData) {
// Load custom fonts
document.fonts. load ( '16px CustomFont' ). then (() => {
document.body.classList. add ( 'fonts-loaded' );
});
} else {
// Use system fonts
document.body.style.fontFamily = 'system-ui, sans-serif' ;
}
Responsive Design Best Practices
Mobile-First - Start with mobile styles, enhance for larger screens with
min-width media queries
CSS Grid + Flexbox - Use Grid for 2D layouts, Flexbox for 1D components,
leverage auto-fit/minmax for responsiveness
Container Queries - Component-based responsiveness, truly reusable
components independent of viewport
Tailwind Utilities - Rapid responsive development with sm/md/lg/xl
breakpoint prefixes
Lazy Loading - Use Intersection Observer for images, components, infinite
scroll; native loading="lazy" for simple cases
Adaptive Loading - Detect network conditions (4g/3g/2g), save-data mode,
device memory to serve appropriate assets
Touch-First - Minimum 44px touch targets, avoid hover-only interactions,
use pointer events for unified input handling
7. Accessibility Implementation Best Practices
7.1 WCAG 2.1 AA Semantic HTML5
Element
Purpose
WCAG Guideline
Example
<header>
Page/section header
Info and Relationships (1.3.1)
Site banner, article header
<nav>
Navigation links
Info and Relationships (1.3.1)
Main menu, breadcrumbs
<main>
Primary content
Info and Relationships (1.3.1)
One per page, unique content
<article>
Self-contained content
Info and Relationships (1.3.1)
Blog post, news article
<aside>
Tangentially related content
Info and Relationships (1.3.1)
Sidebar, related links
<footer>
Page/section footer
Info and Relationships (1.3.1)
Copyright, contact info
Heading hierarchy
<h1> to <h6> in order
Info and Relationships (1.3.1)
Don't skip levels (h2 → h4)
Example: WCAG 2.1 AA compliant semantic HTML structure
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title >Accessible Web Page</ title >
</ head >
< body >
<!-- Skip to main content link -->
< a href = "#main-content" class = "skip-link" >Skip to main content</ a >
<!-- Page header with site branding -->
< header role = "banner" >
< h1 >Site Name</ h1 >
< p >Tagline describing the site</ p >
</ header >
<!-- Main navigation -->
< nav aria-label = "Main navigation" >
< ul >
< li >< a href = "/" >Home</ a ></ li >
< li >< a href = "/about" >About</ a ></ li >
< li >< a href = "/contact" >Contact</ a ></ li >
</ ul >
</ nav >
<!-- Main content area -->
< main id = "main-content" >
< h2 >Page Title</ h2 >
<!-- Article section -->
< article >
< h3 >Article Heading</ h3 >
< p >Article content with < a href = "#" >meaningful link text</ a >.</ p >
< section >
< h4 >Subsection Heading</ h4 >
< p >Subsection content.</ p >
</ section >
</ article >
<!-- Accessible form -->
< form action = "/submit" method = "post" >
< fieldset >
< legend >Contact Information</ legend >
< label for = "name" >Name</ label >
< input type = "text" id = "name" name = "name" required aria-required = "true" >
< label for = "email" >Email</ label >
< input
type = "email"
id = "email"
name = "email"
required
aria-required = "true"
aria-describedby = "email-hint"
>
< span id = "email-hint" class = "hint" >We'll never share your email.</ span >
</ fieldset >
< button type = "submit" >Submit</ button >
</ form >
<!-- Accessible table -->
< table >
< caption >Monthly Sales Data</ caption >
< thead >
< tr >
< th scope = "col" >Month</ th >
< th scope = "col" >Revenue</ th >
</ tr >
</ thead >
< tbody >
< tr >
< th scope = "row" >January</ th >
< td >$10,000</ td >
</ tr >
</ tbody >
</ table >
<!-- Accessible image -->
< figure >
< img
src = "chart.png"
alt = "Bar chart showing 25% increase in sales over Q1"
>
< figcaption >Sales growth in Q1 2025</ figcaption >
</ figure >
<!-- Decorative image (empty alt) -->
< img src = "decorative-line.png" alt = "" role = "presentation" >
</ main >
<!-- Sidebar content -->
< aside aria-label = "Related content" >
< h2 >Related Articles</ h2 >
< ul >
< li >< a href = "/article1" >Article 1</ a ></ li >
< li >< a href = "/article2" >Article 2</ a ></ li >
</ ul >
</ aside >
<!-- Page footer -->
< footer role = "contentinfo" >
< p > © 2025 Company Name</ p >
< nav aria-label = "Footer navigation" >
< a href = "/privacy" >Privacy Policy</ a >
< a href = "/terms" >Terms of Service</ a >
</ nav >
</ footer >
</ body >
</ html >
<!-- CSS for skip link -->
< style >
.skip-link {
position : absolute ;
top : -40 px ;
left : 0 ;
background : #000 ;
color : #fff ;
padding : 8 px ;
z-index : 100 ;
}
.skip-link:focus {
top : 0 ;
}
</ style >
7.2 ARIA Labels Roles Screen Readers
ARIA Attribute
Purpose
Example
Use Case
role
Defines element's purpose
role="button"
Non-semantic elements as interactive
aria-label
Provides accessible name
aria-label="Close dialog"
Icon buttons without text
aria-labelledby
References element for label
aria-labelledby="heading-id"
Associate region with heading
aria-describedby
Additional description
aria-describedby="help-text"
Form field hints, tooltips
aria-live
Announces dynamic content
aria-live="polite"
Status messages, notifications
aria-hidden
Hides from screen readers
aria-hidden="true"
Decorative icons, duplicates
aria-expanded
Expandable state
aria-expanded="false"
Accordions, dropdowns
aria-current
Current item in set
aria-current="page"
Active navigation link
Example: ARIA attributes for accessible components
// Icon button with aria-label
< button aria-label = "Close dialog" >
< svg aria-hidden = "true" >...</ svg >
</ button >
// Search form with aria-labelledby
< form role = "search" aria-labelledby = "search-heading" >
< h2 id = "search-heading" >Search Products</ h2 >
< input type = "search" aria-label = "Search query" >
< button type = "submit" >Search</ button >
</ form >
// Form field with aria-describedby
< label for = "password" >Password</ label >
< input
type = "password"
id = "password"
aria-describedby = "password-requirements"
aria-invalid = "false"
>
< div id = "password-requirements" >
Must be at least 8 characters
</ div >
// Accordion with aria-expanded
function Accordion() {
const [isOpen, setIsOpen] = useState ( false );
return (
< div >
< button
aria-expanded = {isOpen}
aria-controls = "accordion-content"
onClick = {() => setIsOpen ( ! isOpen)}
>
Show Details
</ button >
{isOpen && (
< div id = "accordion-content" role = "region" >
Accordion content
</ div >
)}
</ div >
);
}
// Live region for notifications
< div
role = "status"
aria-live = "polite"
aria-atomic = "true"
className = "sr-only"
>
{statusMessage}
</ div >
// Alert for errors
< div role = "alert" aria-live = "assertive" >
{errorMessage}
</ div >
// Modal dialog
function Modal({ isOpen, onClose, title, children }) {
return (
< div
role = "dialog"
aria-modal = "true"
aria-labelledby = "dialog-title"
hidden = { ! isOpen}
>
< h2 id = "dialog-title" >{title}</ h2 >
< div >{children}</ div >
< button onClick = {onClose} aria-label = "Close dialog" >
< span aria-hidden = "true" > × </ span >
</ button >
</ div >
);
}
// Navigation with aria-current
< nav aria-label = "Main navigation" >
< a href = "/" aria-current = "page" >Home</ a >
< a href = "/about" >About</ a >
< a href = "/contact" >Contact</ a >
</ nav >
// Tabs with ARIA
function Tabs() {
const [activeTab, setActiveTab] = useState ( 0 );
return (
< div >
< div role = "tablist" aria-label = "Content tabs" >
< button
role = "tab"
aria-selected = {activeTab === 0 }
aria-controls = "panel-0"
id = "tab-0"
onClick = {() => setActiveTab ( 0 )}
>
Tab 1
</ button >
< button
role = "tab"
aria-selected = {activeTab === 1 }
aria-controls = "panel-1"
id = "tab-1"
onClick = {() => setActiveTab ( 1 )}
>
Tab 2
</ button >
</ div >
< div
role = "tabpanel"
id = "panel-0"
aria-labelledby = "tab-0"
hidden = {activeTab !== 0 }
>
Panel 1 content
</ div >
< div
role = "tabpanel"
id = "panel-1"
aria-labelledby = "tab-1"
hidden = {activeTab !== 1 }
>
Panel 2 content
</ div >
</ div >
);
}
// Loading state with aria-busy
< div aria-busy = {isLoading} aria-live = "polite" >
{isLoading ? 'Loading...' : content}
</ div >
// Visually hidden but screen-reader accessible
.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 ;
}
< span class = "sr-only" >Additional context for screen readers</ span >
7.3 Focus Management roving tabindex
Technique
Implementation
Description
Use Case
tabindex="0"
Includes in tab order
Makes non-interactive elements focusable
Custom widgets
tabindex="-1"
Programmatically focusable
Not in tab order, focus via JS
Skip links, roving tabindex
Roving tabindex
One item tabindex="0"
Single tab stop, arrow keys navigate
Toolbars, menus, grids
Focus trap
Constrain focus within
Prevent focus leaving modal/dialog
Modals, drawers
Focus visible
:focus-visible
Show focus only for keyboard users
Better UX than :focus
Focus restoration
Return focus after action
Focus trigger after closing modal
Modal dialogs
Example: Focus management and roving tabindex
// 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' :
newIndex = (index + 1 ) % items. length ;
break ;
case 'ArrowLeft' :
newIndex = (index - 1 + items. length ) % items. length ;
break ;
case 'Home' :
newIndex = 0 ;
break ;
case 'End' :
newIndex = items. length - 1 ;
break ;
default :
return ;
}
e. preventDefault ();
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)}
onFocus = {() => setFocusedIndex (index)}
aria-label = {item.label}
>
{item.icon}
</ button >
))}
</ div >
);
}
// Focus trap for modal
import { useEffect, useRef } from 'react' ;
function FocusTrap ({ children }) {
const trapRef = useRef ( null );
useEffect (() => {
const trap = trapRef.current;
if ( ! trap) return ;
const focusableElements = trap. querySelectorAll (
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[ 0 ];
const lastElement = focusableElements[focusableElements. length - 1 ];
const handleTabKey = ( 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 ();
}
}
};
trap. addEventListener ( 'keydown' , handleTabKey);
firstElement?. focus ();
return () => trap. removeEventListener ( 'keydown' , handleTabKey);
}, []);
return < div ref = {trapRef}>{children}</ div >;
}
// Modal with focus management
function Modal ({ isOpen , onClose , children }) {
const previousFocus = useRef ( null );
const modalRef = useRef ( null );
useEffect (() => {
if (isOpen) {
// Save current focus
previousFocus.current = document.activeElement;
// Focus modal
modalRef.current?. focus ();
} else {
// Restore focus when closed
previousFocus.current?. focus ();
}
}, [isOpen]);
if ( ! isOpen) return null ;
return (
< div
ref = {modalRef}
role = "dialog"
aria-modal = "true"
tabIndex = { - 1 }
>
< FocusTrap >
{children}
< button onClick = {onClose}>Close</ button >
</ FocusTrap >
</ div >
);
}
// Focus-visible CSS
button :focus {
outline : none; /* Remove default */
}
button :focus - visible {
outline : 2px solid #0066cc;
outline - offset : 2px;
}
// Custom hook for roving tabindex
function useRovingTabIndex ( size ) {
const [ currentIndex , setCurrentIndex ] = useState ( 0 );
const handleKeyDown = useCallback (( e ) => {
switch (e.key) {
case 'ArrowRight' :
case 'ArrowDown' :
e. preventDefault ();
setCurrentIndex (( prev ) => (prev + 1 ) % size);
break ;
case 'ArrowLeft' :
case 'ArrowUp' :
e. preventDefault ();
setCurrentIndex (( prev ) => (prev - 1 + size) % size);
break ;
case 'Home' :
e. preventDefault ();
setCurrentIndex ( 0 );
break ;
case 'End' :
e. preventDefault ();
setCurrentIndex (size - 1 );
break ;
}
}, [size]);
return { currentIndex, setCurrentIndex, handleKeyDown };
}
// Usage
function Menu () {
const items = [ 'Item 1' , 'Item 2' , 'Item 3' ];
const { currentIndex , handleKeyDown } = useRovingTabIndex (items. length );
return (
< div role = "menu" >
{items. map (( item , index ) => (
< div
key = {index}
role = "menuitem"
tabIndex = {index === currentIndex ? 0 : - 1 }
onKeyDown = {handleKeyDown}
>
{item}
</ div >
))}
</ div >
);
}
// Skip to main content
< a href = "#main-content" className = "skip-link" >
Skip to main content
</ a >
< main id = "main-content" tabIndex = { - 1 }>
{ /* Main content */ }
</ main >
7.4 Color Contrast Checker WebAIM
Level
Contrast Ratio
Requirement
Use Case
AA Normal Text
4.5:1
Minimum for body text
<18px or <14px bold
AA Large Text
3:1
Large or bold text
>=18px or >=14px bold
AAA Normal Text
7:1
Enhanced contrast
Higher accessibility
AAA Large Text
4.5:1
Enhanced for large text
Better readability
UI Components
3:1
Form controls, icons
Interactive elements
Focus Indicators
3:1
Focus state contrast
Keyboard navigation
Example: Color contrast compliance and testing
/* WCAG AA Compliant Colors */
/* Good contrast examples (AA compliant) */
:root {
-- text - dark : #1a1a1a; /* On white: 16.1:1 (AAA) */
-- text - medium : # 595959 ; /* On white: 7:1 (AAA) */
-- text - light : # 757575 ; /* On white: 4.6:1 (AA) */
-- primary : #0066cc; /* On white: 4.7:1 (AA) */
-- primary - dark : # 004080 ; /* On white: 8.6:1 (AAA) */
-- success : # 008000 ; /* On white: 4.5:1 (AA) */
-- error : #c00000; /* On white: 7.3:1 (AAA) */
-- warning : #c68400; /* On white: 4.5:1 (AA) */
/* Dark mode */
-- bg - dark : #1a1a1a;
-- text - dark - mode : #e6e6e6; /* On dark: 11.6:1 (AAA) */
}
/* Bad contrast examples (WCAG fails) */
.bad - contrast {
color : # 999 ; /* On white: 2.8:1 (FAIL) */
background : #fff;
}
/* Good contrast example */
.good - contrast {
color : # 595959 ; /* On white: 7:1 (AAA) */
background : #fff;
}
/* Button with sufficient contrast */
.button {
background : #0066cc; /* Background color */
color : #ffffff; /* Text: 4.7:1 (AA) */
border : 2px solid # 004080 ; /* Border: 8.6:1 (AAA) */
}
.button:focus {
outline : 2px solid #0066cc; /* 3:1 with background (AA) */
outline - offset : 2px;
}
/* Link colors with sufficient contrast */
a {
color : #0066cc; /* 4.7:1 on white (AA) */
text - decoration : underline; /* Don't rely on color alone */
}
a :visited {
color : #551a8b; /* 6.4:1 on white (AA) */
}
/* Testing contrast in JavaScript */
function getContrast ( foreground , background ) {
const getLuminance = ( color ) => {
const rgb = color. match ( / \d + / g ). map (Number);
const [ r , g , b ] = rgb. map ( c => {
c = c / 255 ;
return c <= 0.03928 ? c / 12.92 : Math. pow ((c + 0.055 ) / 1.055 , 2.4 );
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const l1 = getLuminance (foreground);
const l2 = getLuminance (background);
const lighter = Math. max (l1, l2);
const darker = Math. min (l1, l2);
return (lighter + 0.05 ) / (darker + 0.05 );
}
// Check if contrast meets WCAG AA
const contrastRatio = getContrast ( 'rgb(0, 102, 204)' , 'rgb(255, 255, 255)' );
const meetsAA = contrastRatio >= 4.5 ; // true
// React component for contrast checking
function ContrastChecker ({ foreground , background }) {
const ratio = getContrast (foreground, background);
const meetsAA = ratio >= 4.5 ;
const meetsAAA = ratio >= 7 ;
return (
< div >
< div style = {{ color: foreground, background }}>
Sample Text
</ div >
< p >Contrast Ratio: {ratio. toFixed ( 2 )}:1</ p >
< p >WCAG AA: {meetsAA ? '✓ Pass' : '✗ Fail' }</ p >
< p >WCAG AAA: {meetsAAA ? '✓ Pass' : '✗ Fail' }</ p >
</ div >
);
}
/* Dark mode with proper contrast */
@ media (prefers - color - scheme: dark) {
:root {
-- bg : #1a1a1a;
-- text : #e6e6e6; /* 11.6:1 (AAA) */
-- text - muted : #b3b3b3; /* 5.7:1 (AA) */
-- primary : #4d94ff; /* 5.1:1 on dark (AA) */
}
body {
background : var (--bg);
color : var (--text);
}
}
/* Don't rely on color alone - use icons/patterns */
.status - success {
color : # 008000 ;
/* Add icon for non-color indicator */
}
.status - success ::before {
content : '✓' ;
margin - right : 0.5rem;
}
.status - error {
color : #c00000;
}
.status - error ::before {
content : '✗' ;
margin - right : 0.5rem;
}
/* WebAIM Contrast Checker Tools */
// Online: https://webaim.org/resources/contrastchecker/
// Browser DevTools: Chrome/Edge Lighthouse, Firefox Accessibility Inspector
// VS Code Extensions: webhint, axe Accessibility Linter
// npm packages: axe-core, pa11y
7.5 Keyboard Navigation Testing
Key
Action
Expected Behavior
Component
Tab
Move forward
Focus next interactive element
All focusable elements
Shift + Tab
Move backward
Focus previous interactive element
All focusable elements
Enter
Activate
Trigger button/link action
Buttons, links
Space
Activate
Toggle checkbox, press button
Buttons, checkboxes
Arrow keys
Navigate within
Move between items in group
Radio, select, tabs, menus
Escape
Close/Cancel
Close modal, dismiss menu
Modals, menus, tooltips
Home/End
Jump to start/end
First/last item in list
Lists, select, inputs
Example: Keyboard navigation implementation
// Keyboard-accessible dropdown
function Dropdown ({ items , label }) {
const [ isOpen , setIsOpen ] = useState ( false );
const [ selectedIndex , setSelectedIndex ] = useState ( - 1 );
const buttonRef = useRef ( null );
const itemRefs = useRef ([]);
const handleKeyDown = ( e ) => {
switch (e.key) {
case 'Enter' :
case ' ' :
e. preventDefault ();
setIsOpen ( ! isOpen);
break ;
case 'Escape' :
setIsOpen ( false );
buttonRef.current?. focus ();
break ;
case 'ArrowDown' :
e. preventDefault ();
if ( ! isOpen) {
setIsOpen ( true );
} else {
const next = (selectedIndex + 1 ) % items. length ;
setSelectedIndex (next);
itemRefs.current[next]?. focus ();
}
break ;
case 'ArrowUp' :
e. preventDefault ();
if (isOpen) {
const prev = (selectedIndex - 1 + items. length ) % items. length ;
setSelectedIndex (prev);
itemRefs.current[prev]?. focus ();
}
break ;
case 'Home' :
e. preventDefault ();
setSelectedIndex ( 0 );
itemRefs.current[ 0 ]?. focus ();
break ;
case 'End' :
e. preventDefault ();
setSelectedIndex (items. length - 1 );
itemRefs.current[items. length - 1 ]?. focus ();
break ;
}
};
return (
< div >
< button
ref = {buttonRef}
aria-haspopup = "listbox"
aria-expanded = {isOpen}
onKeyDown = {handleKeyDown}
onClick = {() => setIsOpen ( ! isOpen)}
>
{label}
</ button >
{isOpen && (
< ul role = "listbox" >
{items. map (( item , index ) => (
< li
key = {item.id}
ref = { el => itemRefs.current[index] = el}
role = "option"
tabIndex = { - 1 }
aria-selected = {index === selectedIndex}
onKeyDown = {handleKeyDown}
>
{item.label}
</ li >
))}
</ ul >
)}
</ div >
);
}
// Keyboard navigation for tabs
function Tabs ({ tabs }) {
const [ activeIndex , setActiveIndex ] = useState ( 0 );
const tabRefs = useRef ([]);
const handleTabKeyDown = ( e , index ) => {
let newIndex = index;
switch (e.key) {
case 'ArrowRight' :
newIndex = (index + 1 ) % tabs. length ;
break ;
case 'ArrowLeft' :
newIndex = (index - 1 + tabs. length ) % tabs. length ;
break ;
case 'Home' :
newIndex = 0 ;
break ;
case 'End' :
newIndex = tabs. length - 1 ;
break ;
default :
return ;
}
e. preventDefault ();
setActiveIndex (newIndex);
tabRefs.current[newIndex]?. focus ();
};
return (
< div >
< div role = "tablist" >
{tabs. map (( tab , index ) => (
< button
key = {tab.id}
ref = { el => tabRefs.current[index] = el}
role = "tab"
tabIndex = {index === activeIndex ? 0 : - 1 }
aria-selected = {index === activeIndex}
aria-controls = { `panel-${ index }` }
onKeyDown = {( e ) => handleTabKeyDown (e, index)}
onClick = {() => setActiveIndex (index)}
>
{tab.label}
</ button >
))}
</ div >
{tabs. map (( tab , index ) => (
< div
key = {tab.id}
role = "tabpanel"
id = { `panel-${ index }` }
hidden = {index !== activeIndex}
tabIndex = { 0 }
>
{tab.content}
</ div >
))}
</ div >
);
}
// Modal with keyboard handling
function Modal ({ isOpen , onClose , children }) {
useEffect (() => {
const handleEscape = ( e ) => {
if (e.key === 'Escape' && isOpen) {
onClose ();
}
};
document. addEventListener ( 'keydown' , handleEscape);
return () => document. removeEventListener ( 'keydown' , handleEscape);
}, [isOpen, onClose]);
if ( ! isOpen) return null ;
return (
< div role = "dialog" aria-modal = "true" >
{children}
< button onClick = {onClose}>Close (Esc)</ button >
</ div >
);
}
// Custom checkbox with keyboard support
function Checkbox ({ checked , onChange , label }) {
const handleKeyDown = ( e ) => {
if (e.key === ' ' ) {
e. preventDefault ();
onChange ( ! checked);
}
};
return (
< div
role = "checkbox"
aria-checked = {checked}
tabIndex = { 0 }
onKeyDown = {handleKeyDown}
onClick = {() => onChange ( ! checked)}
>
{label}
</ div >
);
}
// Keyboard testing checklist
const keyboardTests = [
'Can navigate all interactive elements with Tab' ,
'Can operate all functionality with keyboard only' ,
'Focus indicator is clearly visible' ,
'Tab order follows logical sequence' ,
'No keyboard traps (can escape all contexts)' ,
'Arrow keys work in composite widgets' ,
'Enter/Space activate buttons' ,
'Escape closes modals and menus' ,
'Can skip repetitive content (skip links)' ,
'Custom widgets follow ARIA keyboard patterns' ,
];
// Testing script
function testKeyboardNavigation () {
console. log ( 'Keyboard Navigation Test' );
// Get all focusable elements
const focusable = document. querySelectorAll (
'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
console. log ( `Found ${ focusable . length } focusable elements` );
// Check tab order
focusable. forEach (( el , i ) => {
console. log ( `${ i + 1 }. ${ el . tagName } - ${ el . textContent ?. trim () }` );
});
}
7.6 axe-core Automated Testing
Tool
Usage
Description
Integration
axe-core
JavaScript library
Automated accessibility testing engine
Runtime, CI/CD
@axe-core/react
React integration
Logs violations in development
Development mode
jest-axe
Jest matcher
Accessibility assertions in tests
Unit/integration tests
cypress-axe
Cypress plugin
E2E accessibility testing
End-to-end tests
axe DevTools
Browser extension
Manual testing in DevTools
Development, QA
pa11y
CLI tool
Automated accessibility testing
CI/CD pipelines
Example: Automated accessibility testing with axe-core
// Install packages
npm install -- save - dev axe - core @axe - core / react jest - axe cypress - axe
// 1. axe-core in React (development mode)
// index.tsx or main.tsx
import React from 'react' ;
import ReactDOM from 'react-dom/client' ;
import App from './App' ;
if (process.env. NODE_ENV !== 'production' ) {
import ( '@axe-core/react' ). then ( axe => {
axe. default (React, ReactDOM, 1000 );
});
}
ReactDOM. createRoot (document. getElementById ( 'root' ) ! ). render (< App />);
// 2. jest-axe for unit tests
import { render } from '@testing-library/react' ;
import { axe, toHaveNoViolations } from 'jest-axe' ;
expect. extend (toHaveNoViolations);
test ( 'should not have accessibility violations' , async () => {
const { container } = render (< MyComponent />);
const results = await axe (container);
expect (results). toHaveNoViolations ();
});
// Test specific component
test ( 'button should be accessible' , async () => {
const { container } = render (
< button >Click me</ button >
);
const results = await axe (container);
expect (results). toHaveNoViolations ();
});
// Test with custom rules
test ( 'form should be accessible' , async () => {
const { container } = render (< LoginForm />);
const results = await axe (container, {
rules: {
'color-contrast' : { enabled: true },
'label' : { enabled: true },
}
});
expect (results). toHaveNoViolations ();
});
// 3. cypress-axe for E2E tests
// cypress/support/e2e.ts
import 'cypress-axe' ;
// cypress/e2e/accessibility.cy.ts
describe ( 'Accessibility tests' , () => {
beforeEach (() => {
cy. visit ( '/' );
cy. injectAxe ();
});
it ( 'should not have accessibility violations on home page' , () => {
cy. checkA11y ();
});
it ( 'should not have violations after interaction' , () => {
cy. get ( 'button' ). click ();
cy. checkA11y ();
});
it ( 'should check specific element' , () => {
cy. checkA11y ( '.navigation' );
});
it ( 'should check with options' , () => {
cy. checkA11y ( null , {
runOnly: {
type: 'tag' ,
values: [ 'wcag2a' , 'wcag2aa' ],
},
});
});
it ( 'should exclude specific elements' , () => {
cy. checkA11y ({
exclude: [ '.advertisement' ],
});
});
});
// 4. Playwright with axe-core
import { test, expect } from '@playwright/test' ;
import AxeBuilder from '@axe-core/playwright' ;
test ( 'should not have accessibility violations' , async ({ page }) => {
await page. goto ( '/' );
const accessibilityScanResults = await new AxeBuilder ({ page })
. analyze ();
expect (accessibilityScanResults.violations). toEqual ([]);
});
test ( 'should check specific section' , async ({ page }) => {
await page. goto ( '/' );
const results = await new AxeBuilder ({ page })
. include ( '.main-content' )
. exclude ( '.ads' )
. analyze ();
expect (results.violations). toEqual ([]);
});
// 5. pa11y for CI/CD
// package.json
{
"scripts" : {
"test:a11y" : "pa11y-ci"
}
}
// .pa11yci.json
{
"defaults" : {
"timeout" : 5000 ,
"standard" : "WCAG2AA" ,
"runners" : [ "axe" , "htmlcs" ]
},
"urls" : [
"http://localhost:3000" ,
"http://localhost:3000/about" ,
"http://localhost:3000/contact"
]
}
// 6. Custom axe-core integration
import { run } from 'axe-core' ;
async function checkAccessibility ( element ) {
try {
const results = await run (element || document.body, {
runOnly: {
type: 'tag' ,
values: [ 'wcag2a' , 'wcag2aa' , 'wcag21a' , 'wcag21aa' ],
},
});
if (results.violations. length > 0 ) {
console. error ( 'Accessibility violations:' , results.violations);
results.violations. forEach ( violation => {
console. group (violation.help);
console. log ( 'Impact:' , violation.impact);
console. log ( 'Description:' , violation.description);
console. log ( 'Nodes:' , violation.nodes);
console. groupEnd ();
});
} else {
console. log ( '✓ No accessibility violations found' );
}
return results;
} catch (error) {
console. error ( 'Accessibility check failed:' , error);
}
}
// Usage in development
if (process.env. NODE_ENV === 'development' ) {
window.checkA11y = checkAccessibility;
// In console: checkA11y()
}
// 7. GitHub Actions CI
// .github/workflows/accessibility.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 test :a11y
// 8. React Testing Library with axe
import { render, screen } from '@testing-library/react' ;
import userEvent from '@testing-library/user-event' ;
import { axe } from 'jest-axe' ;
test ( 'modal should be accessible' , async () => {
const { container } = render (< App />);
// Open modal
await userEvent. click (screen. getByRole ( 'button' , { name: 'Open' }));
// Check accessibility
const results = await axe (container);
expect (results). toHaveNoViolations ();
});
Accessibility Best Practices Summary
Semantic HTML - Use proper HTML5 elements (header, nav, main, article,
aside, footer) with heading hierarchy
ARIA Attributes - Use aria-label, aria-labelledby, aria-describedby for
context; aria-live for dynamic content; aria-expanded for state
Focus Management - Implement roving tabindex for toolbars/menus, focus
traps for modals, restore focus after interactions
Color Contrast - WCAG AA requires 4.5:1 for normal text, 3:1 for large text
and UI components; use :focus-visible for keyboard users
Keyboard Navigation - Tab/Shift+Tab for focus, Enter/Space to activate,
Arrow keys for navigation, Escape to close, Home/End to jump
Automated Testing - Use axe-core in development (@axe-core/react), jest-axe
for unit tests, cypress-axe for E2E, pa11y for CI/CD
Manual Testing - Test with keyboard only, screen readers (NVDA, JAWS,
VoiceOver), axe DevTools browser extension
8. Error Handling Implementation Patterns
8.1 React Error Boundaries getDerivedStateFromError
Method
Purpose
When Called
Return Value
getDerivedStateFromError
Update state for fallback UI
After error thrown in descendant
State object to trigger re-render
componentDidCatch
Log error information
After error thrown, commit phase
void (side effects allowed)
Error Boundary
Catches React render errors
Render, lifecycle, constructors
Class component only
resetErrorBoundary
Reset error state
User action to retry
Re-render children
react-error-boundary
Library with hooks
Simplifies error boundaries
useErrorHandler hook
Example: React Error Boundaries implementation
// Basic Error Boundary class component
import React, { Component, ErrorInfo, ReactNode } from 'react' ;
interface Props {
children : ReactNode ;
fallback ?: ReactNode ;
}
interface State {
hasError : boolean ;
error : Error | null ;
}
class ErrorBoundary extends Component < Props , State > {
constructor ( props : Props ) {
super (props);
this .state = { hasError: false , error: null };
}
static getDerivedStateFromError ( error : Error ) : State {
// Update state to trigger fallback UI
return { hasError: true , error };
}
componentDidCatch ( error : Error , errorInfo : ErrorInfo ) {
// Log error to service
console. error ( 'Error caught by boundary:' , error, errorInfo);
// Send to error tracking service
logErrorToService (error, errorInfo);
}
resetErrorBoundary = () => {
this . setState ({ hasError: false , error: null });
};
render () {
if ( this .state.hasError) {
return (
this .props.fallback || (
< div role = "alert" >
< h2 >Something went wrong </ h2 >
< details >
< summary >Error details </ summary >
< pre >{this.state.error?.message} </ pre >
</ details >
< button onClick = {this.resetErrorBoundary} >
Try again
</ button >
</ div >
)
);
}
return this .props.children;
}
}
// Usage
function App () {
return (
< ErrorBoundary fallback = {<ErrorFallback />} >
< MainContent />
</ ErrorBoundary >
);
}
// Multiple error boundaries for granular handling
function Dashboard () {
return (
< div >
< ErrorBoundary fallback = {<div>Header error</div>} >
< Header />
</ ErrorBoundary >
< ErrorBoundary fallback = {<div>Sidebar error</div>} >
< Sidebar />
</ ErrorBoundary >
< ErrorBoundary fallback = {<div>Main content error</div>} >
< MainContent />
</ ErrorBoundary >
</ div >
);
}
// react-error-boundary library (recommended)
import { ErrorBoundary, useErrorHandler } from 'react-error-boundary' ;
function ErrorFallback ({ error , resetErrorBoundary }) {
return (
< div role = "alert" >
< h2 >Oops ! Something went wrong </ h2 >
< pre style = {{ color : 'red' }} > {error.message} </ pre >
< button onClick = {resetErrorBoundary} > Try again </ button >
</ div >
);
}
function App () {
return (
< ErrorBoundary
FallbackComponent = {ErrorFallback}
onError = {(error, errorInfo) => {
console. error ( 'Error logged:' , error, errorInfo);
}}
onReset = {() => {
// Reset app state
window.location.href = '/' ;
}}
>
< MyApp />
</ ErrorBoundary >
);
}
// useErrorHandler hook for async errors
function UserProfile ({ userId }) {
const handleError = useErrorHandler ();
useEffect (() => {
fetchUser (userId). catch (handleError);
}, [userId, handleError]);
return < div >Profile </ div > ;
}
// Error boundary with retry logic
function AppWithRetry () {
const [ resetKey , setResetKey ] = useState ( 0 );
return (
< ErrorBoundary
FallbackComponent = {ErrorFallback}
onReset = {() => setResetKey ( key => key + 1 )}
resetKeys = {[resetKey]}
>
<MyApp key={resetKey} />
</ErrorBoundary>
);
}
// Limitations: Error boundaries DON'T catch:
// - Event handlers (use try-catch)
// - Async code (setTimeout, promises)
// - Server-side rendering
// - Errors in error boundary itself
// Handle event handler errors
function MyComponent() {
const handleError = useErrorHandler();
const handleClick = async () => {
try {
await riskyOperation();
} catch (error) {
handleError(error); // Pass to error boundary
}
};
return <button onClick={handleClick}>Click</button>;
}
8.2 Vue errorCaptured Global Error Handler
Method
Syntax
Description
Use Case
errorCaptured
errorCaptured(err, instance, info)
Component-level error handler
Catch errors in descendants
app.config.errorHandler
app.config.errorHandler = fn
Global error handler
Catch all uncaught errors
onErrorCaptured
onErrorCaptured(fn)
Composition API hook
Handle errors in setup
Return false
Stop propagation
Prevent error from bubbling
Local error handling
warnHandler
app.config.warnHandler
Handle Vue warnings
Development warnings
Example: Vue error handling patterns
// Vue 3 global error handler
import { createApp } from 'vue' ;
import App from './App.vue' ;
const app = createApp (App);
// Global error handler
app.config. errorHandler = ( err , instance , info ) => {
console. error ( 'Global error:' , err);
console. log ( 'Component:' , instance);
console. log ( 'Error info:' , info);
// Send to error tracking service
logErrorToService (err, { component: instance?.$options.name, info });
};
// Global warning handler (development)
app.config. warnHandler = ( msg , instance , trace ) => {
console. warn ( 'Vue warning:' , msg);
console. log ( 'Trace:' , trace);
};
app. mount ( '#app' );
// Component-level error handling with errorCaptured
< script >
export default {
name: 'ErrorBoundary' ,
data () {
return {
error : null ,
};
},
errorCaptured (err, instance, info) {
// Capture error from child components
this.error = err;
console.error( 'Error captured:' , err, info);
// Return false to stop propagation
return false;
},
render () {
if (this.error) {
return this .$slots. fallback ?.() || 'Something went wrong' ;
}
return this.$slots.default?.();
},
};
</ script >
// Usage
< template >
< ErrorBoundary >
< template #default>
<ChildComponent />
</ template >
< template #fallback>
<div>Error occurred in child</div>
</template>
</ErrorBoundary>
</template>
// Vue 3 Composition API with onErrorCaptured
<script setup >
import { ref, onErrorCaptured } from 'vue';
const error = ref(null);
onErrorCaptured((err, instance, info) => {
error.value = err;
console. error ( 'Error in descendant:' , err, info);
// Return false to stop propagation
return false ;
});
</ script >
< template >
< div >
< div v-if = "error" class = "error" >
< h3 >Error: {{ error.message }}</ h3 >
< button @click="error = null">Reset</button>
</div>
<slot v-else />
</ div >
</ template >
// Async error handling in Vue
< script setup >
import { ref } from 'vue';
const data = ref(null);
const error = ref(null);
const loading = ref(false);
async function fetchData() {
loading.value = true ;
error.value = null ;
try {
const response = await fetch ( '/api/data' );
if (!response.ok) throw new Error ( 'Failed to fetch' );
data.value = await response. json ();
} catch (err) {
error.value = err;
console.error( 'Fetch error:' , err);
} finally {
loading.value = false ;
}
}
</ script >
< template >
< div >
< button @click="fetchData" :disabled="loading">
{{ loading ? 'Loading...' : 'Fetch Data' }}
</button>
<div v-if = "error" class = "error" >
Error: {{ error.message }}
</ div >
< div v-else-if = "data" >
{{ data }}
</ div >
</ div >
</ template >
// Reusable error boundary composable
import { ref, onErrorCaptured } from 'vue';
export function useErrorBoundary() {
const error = ref ( null );
const errorInfo = ref ( null );
onErrorCaptured (( err , instance , info ) => {
error.value = err;
errorInfo.value = info;
return false ;
});
const reset = () => {
error.value = null ;
errorInfo.value = null ;
};
return { error, errorInfo, reset };
}
// Usage
< script setup >
const { error, errorInfo, reset } = useErrorBoundary();
</ script >
< template >
< div v-if = "error" >
< h3 >Error occurred</ h3 >
< p >{{ error.message }}</ p >
< p >Info: {{ errorInfo }}</ p >
< button @click="reset">Try Again</button>
</div>
<slot v-else />
</ template >
8.3 Sentry LogRocket Error Monitoring
Tool
Purpose
Features
Use Case
Sentry
Error tracking & monitoring
Stack traces, releases, breadcrumbs
Production error tracking
LogRocket
Session replay & monitoring
Video replay, console logs, network
Debug user sessions
Source Maps
Map minified to original code
Readable stack traces
Production debugging
Breadcrumbs
Event trail before error
User actions, network, console
Error context
Performance
Monitor app performance
Transactions, spans, metrics
Performance tracking
User Context
Identify affected users
User ID, email, metadata
User-specific issues
Example: Sentry and LogRocket integration
// Install packages
npm install @sentry / react logrocket logrocket - react
// Sentry initialization (React)
import * as Sentry from '@sentry/react' ;
import { BrowserTracing } from '@sentry/tracing' ;
Sentry. init ({
dsn: 'YOUR_SENTRY_DSN' ,
integrations: [
new BrowserTracing (),
new Sentry. Replay ({
maskAllText: false ,
blockAllMedia: false ,
}),
],
// Performance monitoring
tracesSampleRate: 1.0 , // 100% in dev, lower in prod
// Session replay
replaysSessionSampleRate: 0.1 , // 10% of sessions
replaysOnErrorSampleRate: 1.0 , // 100% when error occurs
// Environment
environment: process.env. NODE_ENV ,
release: process.env. REACT_APP_VERSION ,
// Filter errors
beforeSend ( event , hint ) {
// Don't send errors in development
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
import { createRoot } from 'react-dom/client' ;
const root = createRoot (document. getElementById ( 'root' ));
root. render (
< Sentry.ErrorBoundary fallback = {ErrorFallback} showDialog >
< App />
</ Sentry.ErrorBoundary >
);
// Set user context
Sentry. setUser ({
id: user.id,
email: user.email,
username: user.name,
});
// Add breadcrumbs manually
Sentry. addBreadcrumb ({
category: 'auth' ,
message: 'User logged in' ,
level: 'info' ,
});
// Capture exceptions manually
try {
riskyOperation ();
} catch (error) {
Sentry. captureException (error);
}
// Capture messages
Sentry. captureMessage ( 'Something went wrong' , 'warning' );
// Performance monitoring
const transaction = Sentry. startTransaction ({ name: 'checkout' });
// Child spans
const span = transaction. startChild ({
op: 'payment' ,
description: 'Process payment' ,
});
await processPayment ();
span. finish ();
transaction. finish ();
// React component with Sentry profiling
import { withProfiler } from '@sentry/react' ;
function MyComponent () {
return < div >Content</ div >;
}
export default withProfiler (MyComponent);
// LogRocket initialization
import LogRocket from 'logrocket' ;
import setupLogRocketReact from 'logrocket-react' ;
LogRocket. init ( 'YOUR_LOGROCKET_APP_ID' , {
release: process.env. REACT_APP_VERSION ,
console: {
shouldAggregateConsoleErrors: true ,
},
network: {
requestSanitizer : ( request ) => {
// Remove sensitive data
if (request.headers[ 'Authorization' ]) {
request.headers[ 'Authorization' ] = '[REDACTED]' ;
}
return request;
},
},
});
// Setup LogRocket React plugin
setupLogRocketReact (LogRocket);
// Identify user
LogRocket. identify (user.id, {
name: user.name,
email: user.email,
subscriptionType: user.plan,
});
// Track custom events
LogRocket. track ( 'Checkout Completed' , {
orderId: order.id,
total: order.total,
});
// Integrate Sentry + LogRocket
import * as Sentry from '@sentry/react' ;
import LogRocket from 'logrocket' ;
LogRocket. getSessionURL (( sessionURL ) => {
Sentry. configureScope (( scope ) => {
scope. setExtra ( 'sessionURL' , sessionURL);
});
});
// Also add to error context
Sentry. init ({
beforeSend ( event ) {
const sessionURL = LogRocket.sessionURL;
if (sessionURL) {
event.extra = event.extra || {};
event.extra.sessionURL = sessionURL;
}
return event;
},
});
// Error boundary with Sentry + LogRocket
function ErrorFallback ({ error , resetErrorBoundary }) {
useEffect (() => {
// Send to Sentry
Sentry. captureException (error);
// LogRocket will automatically capture
LogRocket. captureException (error);
}, [error]);
return (
< div >
< h2 >Something went wrong</ h2 >
< pre >{error.message}</ pre >
< button onClick = {resetErrorBoundary}>Try again</ button >
< button onClick = {() => Sentry. showReportDialog ()}>
Report feedback
</ button >
</ div >
);
}
// Upload source maps to Sentry
// package.json
{
"scripts" : {
"build" : "react-scripts build && sentry-cli releases files $npm_package_version upload-sourcemaps ./build"
}
}
// .sentryclirc
[defaults]
org = your - org
project = your - project
[auth]
token = YOUR_AUTH_TOKEN
// Next.js with Sentry
// next.config.js
const { withSentryConfig } = require ( '@sentry/nextjs' );
module . exports = withSentryConfig (
{
// Next.js config
},
{
// Sentry webpack plugin options
silent: true ,
org: 'your-org' ,
project: 'your-project' ,
}
);
8.4 Toast Notifications react-hot-toast
Method
Syntax
Description
Use Case
toast.success
toast.success('Saved!')
Success notification
Successful operations
toast.error
toast.error('Failed!')
Error notification
Failed operations
toast.loading
toast.loading('Saving...')
Loading notification
Async operations
toast.promise
toast.promise(promise, msgs)
Auto-updates based on promise
Async with feedback
toast.custom
toast.custom(jsx)
Custom notification component
Custom designs
Position
position: 'top-right'
Toast position on screen
UX preference
Example: Toast notifications for user feedback
// Install react-hot-toast
npm install react - hot - toast
// Basic setup
import { Toaster, toast } from 'react-hot-toast' ;
function App () {
return (
< div >
< Toaster
position = "top-right"
reverseOrder = { false }
gutter = { 8 }
toastOptions = {{
duration: 4000 ,
style: {
background: '#363636' ,
color: '#fff' ,
},
success: {
duration: 3000 ,
iconTheme: {
primary: '#4ade80' ,
secondary: '#fff' ,
},
},
error: {
duration: 5000 ,
iconTheme: {
primary: '#ef4444' ,
secondary: '#fff' ,
},
},
}}
/>
< MyApp />
</ div >
);
}
// Basic toast notifications
function FormExample () {
const handleSubmit = async ( data ) => {
try {
await saveData (data);
toast. success ( 'Successfully saved!' );
} catch (error) {
toast. error ( 'Failed to save. Please try again.' );
}
};
return < form onSubmit = {handleSubmit}>...</ form >;
}
// Loading toast with manual control
function AsyncOperation () {
const handleAction = async () => {
const toastId = toast. loading ( 'Processing...' );
try {
await performAsyncOperation ();
toast. success ( 'Completed!' , { id: toastId });
} catch (error) {
toast. error ( 'Failed!' , { id: toastId });
}
};
return < button onClick = {handleAction}>Start</ button >;
}
// Promise-based toast (automatic)
function PromiseExample () {
const handleSave = () => {
const savePromise = fetch ( '/api/save' , {
method: 'POST' ,
body: JSON . stringify (data),
}). then ( res => res. json ());
toast. promise (savePromise, {
loading: 'Saving...' ,
success: 'Saved successfully!' ,
error: 'Could not save.' ,
});
};
return < button onClick = {handleSave}>Save</ button >;
}
// Custom toast with JSX
function CustomToast () {
const notify = () => {
toast. custom (( t ) => (
< div
className = { `${
t . visible ? 'animate-enter' : 'animate-leave'
} bg-white shadow-lg rounded-lg p-4` }
>
< div className = "flex items-center" >
< div className = "flex-shrink-0" >
< CheckIcon />
</ div >
< div className = "ml-3" >
< h3 className = "font-medium" >Custom notification</ h3 >
< p className = "text-sm text-gray-500" >This is a custom toast</ p >
</ div >
< button onClick = {() => toast. dismiss (t.id)}>
< XIcon />
</ button >
</ div >
</ div >
));
};
return < button onClick = {notify}>Show Custom Toast</ button >;
}
// Toast with action button
function ToastWithAction () {
const handleDelete = () => {
toast (( t ) => (
< div >
< p >Are you sure you want to delete?</ p >
< div className = "flex gap-2 mt-2" >
< button
onClick = {() => {
performDelete ();
toast. dismiss (t.id);
toast. success ( 'Deleted' );
}}
>
Delete
</ button >
< button onClick = {() => toast. dismiss (t.id)}>
Cancel
</ button >
</ div >
</ div >
), { duration: Infinity });
};
return < button onClick = {handleDelete}>Delete Item</ button >;
}
// Different toast types
function ToastExamples () {
return (
< div >
< button onClick = {() => toast ( 'Basic message' )}>
Default
</ button >
< button onClick = {() => toast. success ( 'Success!' )}>
Success
</ button >
< button onClick = {() => toast. error ( 'Error!' )}>
Error
</ button >
< button onClick = {() => toast. loading ( 'Loading...' )}>
Loading
</ button >
< button onClick = {() => toast ( 'Info' , { icon: 'ℹ️' })}>
Info
</ button >
</ div >
);
}
// Custom styling
toast. success ( 'Styled toast' , {
style: {
border: '1px solid #713200' ,
padding: '16px' ,
color: '#713200' ,
},
iconTheme: {
primary: '#713200' ,
secondary: '#FFFAEE' ,
},
});
// Dismiss all toasts
toast. dismiss ();
// Dismiss specific toast
const toastId = toast. loading ( 'Processing...' );
toast. dismiss (toastId);
// Alternative: 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
rtl = { false }
pauseOnFocusLoss
draggable
pauseOnHover
/>
< MyApp />
</>
);
}
toast. success ( 'Success!' );
toast. error ( 'Error!' );
toast. info ( 'Info' );
toast. warn ( 'Warning' );
8.5 Retry Logic Exponential Backoff
Strategy
Formula
Description
Use Case
Exponential Backoff
delay = baseDelay * 2^attempt
Doubles delay between retries
API rate limiting
Linear Backoff
delay = baseDelay * attempt
Linear increase in delay
Simple retry scenarios
Jitter
delay += random(0, jitter)
Add randomness to prevent thundering herd
Multiple clients
Max Retries
Limit retry attempts
Prevent infinite loops
All retry scenarios
Circuit Breaker
Stop retries after threshold
Fail fast when service down
Microservices
Example: Retry logic with exponential backoff
// Basic exponential backoff
async function fetchWithRetry ( url , options = {}, maxRetries = 3 ) {
let lastError;
for ( let attempt = 0 ; attempt <= maxRetries; attempt ++ ) {
try {
const response = await fetch (url, options);
if ( ! response.ok) {
throw new Error ( `HTTP ${ response . status }` );
}
return await response. json ();
} catch (error) {
lastError = error;
// Don't retry on last attempt
if (attempt === maxRetries) {
break ;
}
// Calculate exponential backoff delay
const delay = Math. min ( 1000 * Math. pow ( 2 , attempt), 10000 );
console. log ( `Retry ${ attempt + 1 }/${ maxRetries } after ${ delay }ms` );
await new Promise ( resolve => setTimeout (resolve, delay));
}
}
throw lastError;
}
// Usage
try {
const data = await fetchWithRetry ( '/api/data' );
console. log (data);
} catch (error) {
console. error ( 'Failed after retries:' , error);
}
// Advanced retry with jitter
function calculateBackoff ( attempt , baseDelay = 1000 , maxDelay = 30000 ) {
// Exponential backoff
const exponentialDelay = baseDelay * Math. pow ( 2 , attempt);
// Add jitter (randomness)
const jitter = Math. random () * 0.3 * exponentialDelay;
// Cap at max delay
return Math. min (exponentialDelay + jitter, maxDelay);
}
async function retryWithBackoff ( fn , options = {}) {
const {
maxRetries = 3 ,
baseDelay = 1000 ,
maxDelay = 30000 ,
shouldRetry = () => true ,
onRetry = () => {},
} = options;
let lastError;
for ( let attempt = 0 ; attempt <= maxRetries; attempt ++ ) {
try {
return await fn ();
} catch (error) {
lastError = error;
// Check if we should retry
if (attempt === maxRetries || ! shouldRetry (error, attempt)) {
break ;
}
// Calculate backoff delay
const delay = calculateBackoff (attempt, baseDelay, maxDelay);
// Notify retry
onRetry (error, attempt, delay);
// Wait before retry
await new Promise ( resolve => setTimeout (resolve, delay));
}
}
throw lastError;
}
// Usage with custom retry conditions
const data = await retryWithBackoff (
() => fetch ( '/api/data' ). then ( r => r. json ()),
{
maxRetries: 5 ,
baseDelay: 500 ,
maxDelay: 10000 ,
shouldRetry : ( error , attempt ) => {
// Retry on network errors or 5xx
return error.message. includes ( 'Failed to fetch' ) ||
(error.status >= 500 && error.status < 600 );
},
onRetry : ( error , attempt , delay ) => {
console. log ( `Retrying (${ attempt + 1 }) after ${ delay }ms: ${ error . message }` );
toast. error ( `Request failed, retrying... (${ attempt + 1 })` );
},
}
);
// React hook for retry logic
function useRetry ( fn , options = {}) {
const [ state , setState ] = useState ({
data: null ,
error: null ,
loading: false ,
retryCount: 0 ,
});
const execute = useCallback ( async ( ... args ) => {
setState ( prev => ({ ... prev, loading: true , error: null }));
try {
const result = await retryWithBackoff (
() => fn ( ... args),
{
... options,
onRetry : ( error , attempt ) => {
setState ( prev => ({ ... prev, retryCount: attempt + 1 }));
options. onRetry ?.(error, attempt);
},
}
);
setState ({
data: result,
error: null ,
loading: false ,
retryCount: 0 ,
});
return result;
} catch (error) {
setState ( prev => ({
... prev,
error,
loading: false ,
}));
throw error;
}
}, [fn, options]);
return { ... state, execute };
}
// Usage in component
function DataComponent () {
const { data , error , loading , retryCount , execute } = useRetry (
() => fetch ( '/api/data' ). then ( r => r. json ()),
{ maxRetries: 3 }
);
useEffect (() => {
execute ();
}, []);
if (loading) return < div >Loading... {retryCount > 0 && `(Retry ${ retryCount })` }</ div >;
if (error) return < div >Error: {error.message} < button onClick = {execute}>Retry</ button ></ div >;
return < div >{ JSON . stringify (data)}</ div >;
}
// Circuit breaker pattern
class CircuitBreaker {
constructor ( threshold = 5 , timeout = 60000 ) {
this .failureThreshold = threshold;
this .resetTimeout = timeout;
this .failureCount = 0 ;
this .state = 'CLOSED' ; // CLOSED, OPEN, HALF_OPEN
this .nextAttempt = Date. now ();
}
async execute ( fn ) {
if ( this .state === 'OPEN' ) {
if (Date. now () < this .nextAttempt) {
throw new Error ( 'Circuit breaker is OPEN' );
}
this .state = 'HALF_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 ++ ;
if ( this .failureCount >= this .failureThreshold) {
this .state = 'OPEN' ;
this .nextAttempt = Date. now () + this .resetTimeout;
console. log ( 'Circuit breaker opened' );
}
}
}
// Usage
const breaker = new CircuitBreaker ( 3 , 30000 );
async function callAPI () {
return breaker. execute (() => fetch ( '/api/data' ). then ( r => r. json ()));
}
8.6 Fallback UI Skeleton Loading States
Pattern
Implementation
Description
Use Case
Skeleton Screens
Placeholder with shape
Shows content structure while loading
Better perceived performance
Shimmer Effect
Animated gradient
Indicates loading in progress
Visual feedback
Suspense
<Suspense fallback={...}>
React's declarative loading
Code splitting, data fetching
Empty States
No data placeholder
Shows when no content available
Empty lists, no results
Error States
Error message with retry
Shows when loading fails
Failed data fetching
Progressive Loading
Load in stages
Load critical content first
Large pages
Example: Skeleton screens and loading states
// Basic skeleton component
function Skeleton ({ width = '100%' , height = '20px' , className = '' }) {
return (
< div
className = { `skeleton ${ className }` }
style = {{
width,
height,
backgroundColor: '#e0e0e0' ,
borderRadius: '4px' ,
animation: 'pulse 1.5s ease-in-out infinite' ,
}}
/>
);
}
// CSS for shimmer effect
.skeleton {
background : linear - gradient (
90deg,
#e0e0e0 25 % ,
#f0f0f0 50 % ,
#e0e0e0 75 %
);
background - size : 200 % 100 % ;
animation : shimmer 1.5s infinite;
}
@keyframes shimmer {
0 % { background - position : - 200 % 0 ; }
100 % { background - position : 200 % 0 ; }
}
// Card skeleton
function CardSkeleton () {
return (
< div className = "card" >
< Skeleton height = "200px" className = "mb-4" />
< Skeleton height = "24px" width = "80%" className = "mb-2" />
< Skeleton height = "16px" width = "100%" className = "mb-1" />
< Skeleton height = "16px" width = "90%" />
</ div >
);
}
// List skeleton
function ListSkeleton ({ count = 5 }) {
return (
< div >
{Array. from ({ length: count }). map (( _ , i ) => (
< div key = {i} className = "flex items-center gap-4 p-4" >
< Skeleton width = "50px" height = "50px" style = {{ borderRadius: '50%' }} />
< div className = "flex-1" >
< Skeleton height = "16px" width = "40%" className = "mb-2" />
< Skeleton height = "14px" width = "80%" />
</ div >
</ div >
))}
</ div >
);
}
// Data fetching with loading states
function UserProfile ({ userId }) {
const [ state , setState ] = useState ({
data: null ,
loading: true ,
error: null ,
});
useEffect (() => {
fetchUser (userId)
. then ( data => setState ({ data, loading: false , error: null }))
. catch ( error => setState ({ data: null , loading: false , error }));
}, [userId]);
if (state.loading) return < CardSkeleton />;
if (state.error) {
return (
< div className = "error-state" >
< h3 >Failed to load user</ h3 >
< p >{state.error.message}</ p >
< button onClick = {() => window.location. reload ()}>
Try Again
</ button >
</ div >
);
}
if ( ! state.data) {
return (
< div className = "empty-state" >
< p >No user found</ p >
</ div >
);
}
return (
< div className = "user-profile" >
< img src = {state.data.avatar} alt = {state.data.name} />
< h2 >{state.data.name}</ h2 >
< p >{state.data.bio}</ p >
</ div >
);
}
// React Suspense with skeleton
import { Suspense, lazy } from 'react' ;
const HeavyComponent = lazy (() => import ( './HeavyComponent' ));
function App () {
return (
< Suspense fallback = {< CardSkeleton />}>
< HeavyComponent />
</ Suspense >
);
}
// Custom loading hook
function useLoadingState ( initialLoading = false ) {
const [ loading , setLoading ] = useState (initialLoading);
const [ error , setError ] = useState ( null );
const withLoading = useCallback ( async ( fn ) => {
setLoading ( true );
setError ( null );
try {
const result = await fn ();
return result;
} catch (err) {
setError (err);
throw err;
} finally {
setLoading ( false );
}
}, []);
return { loading, error, withLoading };
}
// Usage
function DataComponent () {
const [ data , setData ] = useState ( null );
const { loading , error , withLoading } = useLoadingState ( true );
useEffect (() => {
withLoading ( async () => {
const result = await fetchData ();
setData (result);
});
}, []);
if (loading) return < Skeleton />;
if (error) return < ErrorState error = {error} />;
return < div >{data}</ div >;
}
// Skeleton library: react-loading-skeleton
import Skeleton from 'react-loading-skeleton' ;
import 'react-loading-skeleton/dist/skeleton.css' ;
function ProfileSkeleton () {
return (
< div >
< Skeleton circle width = { 100 } height = { 100 } />
< Skeleton count = { 3 } />
</ div >
);
}
// Progressive loading
function Dashboard () {
return (
< div >
{ /* Critical content loads first */ }
< Header />
{ /* Non-critical wrapped in Suspense */ }
< Suspense fallback = {< ChartSkeleton />}>
< AnalyticsChart />
</ Suspense >
< Suspense fallback = {< TableSkeleton />}>
< DataTable />
</ Suspense >
</ div >
);
}
// Empty state component
function EmptyState ({ title , description , action }) {
return (
< div className = "empty-state" >
< EmptyIcon />
< h3 >{title}</ h3 >
< p >{description}</ p >
{action && < button onClick = {action.onClick}>{action.label}</ button >}
</ div >
);
}
// Usage
function ProductList ({ products }) {
if (products. length === 0 ) {
return (
< EmptyState
title = "No products found"
description = "Try adjusting your filters"
action = {{ label: 'Clear filters' , onClick: clearFilters }}
/>
);
}
return products. map ( product => < ProductCard key = {product.id} { ... product} />);
}
Error Handling Best Practices
Error Boundaries - Use React Error Boundaries (getDerivedStateFromError,
componentDidCatch) or react-error-boundary library for catching render errors
Vue Error Handling - Use errorCaptured lifecycle hook in components,
app.config.errorHandler for global errors, onErrorCaptured in Composition API
Error Monitoring - Integrate Sentry for error tracking with source maps,
LogRocket for session replay, add breadcrumbs and user context
User Feedback - Use toast notifications (react-hot-toast) for
success/error/loading states, show clear error messages with retry options
Retry Logic - Implement exponential backoff with jitter, limit max retries,
use circuit breaker pattern for failing services
Loading States - Show skeleton screens during loading, empty states for no
data, error states with retry, use React Suspense for declarative loading
Production Ready - Filter sensitive errors before sending to monitoring,
upload source maps for readable stack traces, set up alerts for critical errors
9.1 Core Web Vitals LCP CLS FID
Metric
Measures
Good Target
Optimization
LCP (Largest Contentful Paint)
Loading performance
< 2.5s
Optimize images, server response, render-blocking resources
CLS (Cumulative Layout Shift)
Visual stability
< 0.1
Set image/video dimensions, avoid inserting content, use CSS transforms
FID (First Input Delay)
Interactivity
< 100ms
Reduce JS execution time, code splitting, web workers
INP (Interaction to Next Paint)
Responsiveness (replaces FID)
< 200ms
Optimize event handlers, reduce main thread work
TTFB (Time to First Byte)
Server response
< 800ms
CDN, server optimization, caching
FCP (First Contentful Paint)
First render
< 1.8s
Inline critical CSS, preload fonts, optimize server
Example: Core Web Vitals measurement and optimization
// Measure Core Web Vitals with web-vitals library
npm install web - vitals
import { onCLS, onFID, onLCP, onINP, onFCP, onTTFB } from 'web-vitals' ;
// Send to analytics
function sendToAnalytics ( metric ) {
const body = JSON . stringify (metric);
// Use sendBeacon if available (doesn't block page unload)
if (navigator.sendBeacon) {
navigator. sendBeacon ( '/analytics' , body);
} else {
fetch ( '/analytics' , { body, method: 'POST' , keepalive: true });
}
}
// Measure all Core Web Vitals
onCLS (sendToAnalytics);
onFID (sendToAnalytics);
onLCP (sendToAnalytics);
onINP (sendToAnalytics);
onFCP (sendToAnalytics);
onTTFB (sendToAnalytics);
// React hook for Core Web Vitals
import { useEffect } from 'react' ;
function useWebVitals ( onMetric ) {
useEffect (() => {
import ( 'web-vitals' ). then (({ onCLS , onFID , onLCP }) => {
onCLS (onMetric);
onFID (onMetric);
onLCP (onMetric);
});
}, [onMetric]);
}
// Usage
function App () {
useWebVitals (( metric ) => {
console. log (metric.name, metric.value);
});
return < div >App</ div >;
}
// Optimize LCP - Preload critical resources
< head >
{ /* Preload hero image */ }
< link rel = "preload" as = "image" href = "/hero.jpg" />
{ /* Preload critical fonts */ }
< link
rel = "preload"
as = "font"
href = "/fonts/inter.woff2"
type = "font/woff2"
crossOrigin = "anonymous"
/>
{ /* Preconnect to external domains */ }
< link rel = "preconnect" href = "https://api.example.com" />
< link rel = "dns-prefetch" href = "https://cdn.example.com" />
</ head >
// Optimize CLS - Reserve space for images
< img
src = "image.jpg"
alt = "Description"
width = "800"
height = "600"
style = {{ aspectRatio: '800/600' }}
/>
// Use CSS aspect-ratio
.image - container {
aspect - ratio : 16 / 9 ;
}
// Avoid layout shifts from dynamic content
.ad - container {
min - height : 250px; /* Reserve space */
}
// Use CSS transform instead of top/left
// Bad (causes layout shift)
.element {
top : 100px;
transition : top 0.3s;
}
// Good (no layout shift)
.element {
transform : translateY (100px);
transition : transform 0.3s;
}
// Optimize FID/INP - Code splitting
import { lazy, Suspense } from 'react' ;
const HeavyComponent = lazy (() => import ( './HeavyComponent' ));
function App () {
return (
< Suspense fallback = {< Loading />}>
< HeavyComponent />
</ Suspense >
);
}
// Use Web Workers for heavy computation
// worker.js
self. addEventListener ( 'message' , ( e ) => {
const result = heavyComputation (e.data);
self. postMessage (result);
});
// main.js
const worker = new Worker ( 'worker.js' );
worker. postMessage (data);
worker. addEventListener ( 'message' , ( e ) => {
console. log ( 'Result:' , e.data);
});
// Optimize TTFB - Next.js with CDN
// next.config.js
module . exports = {
images: {
domains: [ 'cdn.example.com' ],
},
headers : async () => [
{
source: '/:path*' ,
headers: [
{
key: 'Cache-Control' ,
value: 'public, max-age=31536000, immutable' ,
},
],
},
],
};
// Lighthouse CI for continuous monitoring
// .lighthouserc.js
module . exports = {
ci: {
collect: {
url: [ 'http://localhost:3000' ],
numberOfRuns: 3 ,
},
assert: {
preset: 'lighthouse:recommended' ,
assertions: {
'largest-contentful-paint' : [ 'error' , { maxNumericValue: 2500 }],
'cumulative-layout-shift' : [ 'error' , { maxNumericValue: 0.1 }],
'first-input-delay' : [ 'error' , { maxNumericValue: 100 }],
},
},
upload: {
target: 'temporary-public-storage' ,
},
},
};
// Performance Observer API
const observer = new PerformanceObserver (( list ) => {
for ( const entry of list. getEntries ()) {
console. log (entry.name, entry.startTime, entry.duration);
}
});
// Observe different entry types
observer. observe ({ entryTypes: [ 'measure' , 'navigation' , 'resource' ] });
// Custom performance marks
performance. mark ( 'start-api-call' );
await fetchData ();
performance. mark ( 'end-api-call' );
performance. measure ( 'api-call-duration' , 'start-api-call' , 'end-api-call' );
// Next.js built-in Web Vitals reporting
// pages/_app.js
export function reportWebVitals ( metric ) {
console. log (metric);
// Send to analytics service
if (metric.label === 'web-vital' ) {
gtag ( 'event' , metric.name, {
value: Math. round (metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true ,
});
}
}
9.2 Code Splitting React.lazy Suspense
Technique
Implementation
Description
Use Case
React.lazy
lazy(() => import('./Component'))
Dynamic import for components
Route-based splitting
Suspense
<Suspense fallback={...}>
Loading boundary for lazy components
Show loading state
Route-based
Split by routes
Load route code on navigation
Multi-page apps
Component-based
Split heavy components
Load on demand (modal, chart)
Conditional features
Webpack magic comments
/* webpackChunkName: "name" */
Name chunks, prefetch/preload
Better caching
Dynamic import
import('module').then()
Load modules conditionally
Polyfills, libraries
Example: Code splitting strategies
// Basic React.lazy with Suspense
import { lazy, Suspense } from 'react' ;
const Dashboard = lazy (() => import ( './Dashboard' ));
const Settings = lazy (() => import ( './Settings' ));
function App () {
return (
< Suspense fallback = {< div >Loading...</ div >}>
< Routes >
< Route path = "/dashboard" element = {< Dashboard />} />
< Route path = "/settings" element = {< Settings />} />
</ Routes >
</ Suspense >
);
}
// Route-based code splitting with React Router
import { BrowserRouter, Routes, Route } from 'react-router-dom' ;
import { lazy, Suspense } from 'react' ;
const Home = lazy (() => import ( './pages/Home' ));
const About = lazy (() => import ( './pages/About' ));
const Contact = lazy (() => import ( './pages/Contact' ));
const Dashboard = lazy (() => import ( './pages/Dashboard' ));
function App () {
return (
< BrowserRouter >
< Suspense fallback = {< PageLoader />}>
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/about" element = {< About />} />
< Route path = "/contact" element = {< Contact />} />
< Route path = "/dashboard/*" element = {< Dashboard />} />
</ Routes >
</ Suspense >
</ BrowserRouter >
);
}
// Component-based splitting (modal, heavy chart)
function ProductPage () {
const [ showModal , setShowModal ] = useState ( false );
// Only load modal when needed
const Modal = lazy (() => import ( './Modal' ));
return (
< div >
< button onClick = {() => setShowModal ( true )}>Open</ button >
{showModal && (
< Suspense fallback = {< ModalSkeleton />}>
< Modal onClose = {() => setShowModal ( false )} />
</ Suspense >
)}
</ div >
);
}
// Webpack magic comments for chunk naming
const Dashboard = lazy (() =>
import ( /* webpackChunkName: "dashboard" */ './Dashboard' )
);
// Prefetch on hover (load before needed)
const Dashboard = lazy (() =>
import ( /* webpackPrefetch: true */ './Dashboard' )
);
// Preload (load in parallel with parent)
const Dashboard = lazy (() =>
import ( /* webpackPreload: true */ './Dashboard' )
);
// Conditional loading based on feature flags
async function loadFeature () {
if (featureFlags.newDashboard) {
const { NewDashboard } = await import ( './NewDashboard' );
return NewDashboard;
} else {
const { OldDashboard } = await import ( './OldDashboard' );
return OldDashboard;
}
}
// Library splitting (load polyfills conditionally)
async function loadPolyfills () {
if ( ! window.IntersectionObserver) {
await import ( 'intersection-observer' );
}
if ( ! window.fetch) {
await import ( 'whatwg-fetch' );
}
}
// Multiple suspense boundaries
function App () {
return (
< div >
< Header />
{ /* Critical content */ }
< Suspense fallback = {< HeroSkeleton />}>
< Hero />
</ Suspense >
{ /* Non-critical content */ }
< Suspense fallback = {< ChartSkeleton />}>
< AnalyticsChart />
</ Suspense >
< Suspense fallback = {< TableSkeleton />}>
< DataTable />
</ Suspense >
</ div >
);
}
// Error boundary with lazy loading
import { ErrorBoundary } from 'react-error-boundary' ;
function LazyRoute ({ path , component }) {
const Component = lazy (component);
return (
< ErrorBoundary fallback = {< ErrorPage />}>
< Suspense fallback = {< Loading />}>
< Component />
</ Suspense >
</ ErrorBoundary >
);
}
// Next.js dynamic imports
import dynamic from 'next/dynamic' ;
const DynamicComponent = dynamic (() => import ( './Component' ), {
loading : () => < p >Loading...</ p >,
ssr: false , // Disable server-side rendering
});
// With named export
const DynamicChart = dynamic (() =>
import ( './Charts' ). then ( mod => mod.LineChart)
);
// Vite code splitting
// Vite automatically splits by dynamic imports
const AdminPanel = () => import ( './AdminPanel.vue' );
// Manual chunk configuration (vite.config.js)
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: [ 'react' , 'react-dom' ],
router: [ 'react-router-dom' ],
ui: [ '@mui/material' , '@emotion/react' ],
},
},
},
},
};
9.3 React.memo useMemo useCallback
Hook/HOC
Purpose
When to Use
Caveat
React.memo
Memoize component
Expensive renders, pure components
Shallow prop comparison
useMemo
Memoize computed value
Expensive calculations
Don't overuse, memory cost
useCallback
Memoize function
Pass to memoized children, deps
Useful with React.memo
useMemo vs useCallback
Value vs function
useMemo(() => fn) = useCallback(fn)
useCallback is shorthand
Custom compare
React.memo(Comp, areEqual)
Deep comparison, specific props
Complex prop structures
Example: Memoization techniques in React
// React.memo - prevent unnecessary re-renders
import { memo } from 'react' ;
const ExpensiveComponent = memo (({ data , onClick }) => {
console. log ( 'Rendering ExpensiveComponent' );
return (
< div >
{data. map ( item => (
< div key = {item.id} onClick = {() => onClick (item)}>
{item.name}
</ div >
))}
</ div >
);
});
// Parent component
function Parent () {
const [ count , setCount ] = useState ( 0 );
const [ data ] = useState ([ /* large array */ ]);
// Without memo, ExpensiveComponent re-renders when count changes
// With memo, it only re-renders when data or onClick changes
return (
< div >
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
< ExpensiveComponent data = {data} onClick = {handleClick} />
</ div >
);
}
// useCallback - memoize function reference
function Parent () {
const [ count , setCount ] = useState ( 0 );
const [ items , setItems ] = useState ([]);
// Without useCallback, new function on every render
// const handleClick = (item) => {
// console.log(item);
// };
// With useCallback, same function reference
const handleClick = useCallback (( item ) => {
console. log (item);
// Use items from closure
}, []); // Dependencies
return (
< div >
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
< ExpensiveComponent items = {items} onClick = {handleClick} />
</ div >
);
}
// useMemo - memoize expensive computation
function DataProcessor ({ rawData }) {
// Without useMemo, processes on every render
// const processedData = expensiveProcessing(rawData);
// With useMemo, only recomputes when rawData changes
const processedData = useMemo (() => {
console. log ( 'Processing data...' );
return expensiveProcessing (rawData);
}, [rawData]);
return < div >{processedData. length } items</ div >;
}
// Expensive computation example
const filteredAndSortedList = useMemo (() => {
return items
. filter ( item => item.active)
. sort (( a , b ) => a.name. localeCompare (b.name));
}, [items]);
// Custom comparison function for React.memo
const ComplexComponent = memo (
({ user , settings }) => {
return < div >{user.name}</ div >;
},
( prevProps , nextProps ) => {
// Return true if props are equal (skip re-render)
return (
prevProps.user.id === nextProps.user.id &&
prevProps.settings.theme === nextProps.settings.theme
);
}
);
// Real-world example: Search with debounce
function SearchComponent ({ onSearch }) {
const [ query , setQuery ] = useState ( '' );
// Memoize debounced function
const debouncedSearch = useMemo (
() => debounce (( value ) => onSearch (value), 300 ),
[onSearch]
);
const handleChange = ( e ) => {
const value = e.target.value;
setQuery (value);
debouncedSearch (value);
};
return < input value = {query} onChange = {handleChange} />;
}
// Context with memoization
const UserContext = createContext ();
function UserProvider ({ children }) {
const [ user , setUser ] = useState ( null );
// Memoize context value to prevent unnecessary re-renders
const value = useMemo (
() => ({
user,
setUser,
isAuthenticated: !! user,
}),
[user]
);
return < UserContext.Provider value = {value}>{children}</ UserContext.Provider >;
}
// When NOT to use memoization
// ❌ Don't memoize everything
function SimpleComponent ({ name }) {
// This is fine without useMemo
const greeting = `Hello, ${ name }!` ;
return < div >{greeting}</ div >;
}
// ❌ Primitive props don't benefit from React.memo
const SimpleChild = memo (({ text , count }) => {
// React already optimizes primitive comparisons
return < div >{text} {count}</ div >;
});
// ✅ DO use for expensive renders or large prop objects
const DataGrid = memo (({ data , columns , onSort }) => {
// Expensive table rendering with thousands of rows
return < table >...</ table >;
});
// Combined example: Optimized list component
const ListItem = memo (({ item , onDelete }) => {
return (
< li >
{item.name}
< button onClick = {() => onDelete (item.id)}>Delete</ button >
</ li >
);
});
function List ({ items }) {
const [ selectedId , setSelectedId ] = useState ( null );
// Memoize callback to prevent ListItem re-renders
const handleDelete = useCallback (( id ) => {
// Delete logic
}, []);
// Memoize filtered items
const filteredItems = useMemo (
() => items. filter ( item => item.visible),
[items]
);
return (
< ul >
{filteredItems. map ( item => (
< ListItem key = {item.id} item = {item} onDelete = {handleDelete} />
))}
</ ul >
);
}
9.4 Webpack Bundle Analyzer Optimization
Tool
Purpose
Installation
Insight
webpack-bundle-analyzer
Visualize bundle size
npm i -D webpack-bundle-analyzer
Interactive treemap
source-map-explorer
Analyze with source maps
npm i -D source-map-explorer
See original file sizes
Bundle Buddy
Find duplicate code
Web tool
Identify optimization opportunities
Tree Shaking
Remove dead code
Built into Webpack 4+
Requires ES6 modules
Code Splitting
Split into chunks
Webpack config
Parallel loading
Example: Bundle analysis and optimization
// Install webpack-bundle-analyzer
npm install -- save - dev webpack - bundle - analyzer
// Webpack configuration
const { BundleAnalyzerPlugin } = require ( 'webpack-bundle-analyzer' );
module . exports = {
plugins: [
new BundleAnalyzerPlugin ({
analyzerMode: 'static' ,
reportFilename: 'bundle-report.html' ,
openAnalyzer: false ,
generateStatsFile: true ,
statsFilename: 'bundle-stats.json' ,
}),
],
optimization: {
splitChunks: {
chunks: 'all' ,
cacheGroups: {
vendor: {
test: / [ \\ /] node_modules [ \\ /] / ,
name: 'vendors' ,
priority: 10 ,
},
common: {
minChunks: 2 ,
priority: 5 ,
reuseExistingChunk: true ,
},
},
},
},
};
// package.json scripts
{
"scripts" : {
"build" : "webpack --mode production" ,
"analyze" : "webpack --mode production --profile --json > stats.json && webpack-bundle-analyzer stats.json"
}
}
// Create React App with analyzer
npm install -- save - dev cra - bundle - analyzer
// package.json
{
"scripts" : {
"analyze" : "npm run build && npx cra-bundle-analyzer"
}
}
// Next.js with bundle analyzer
npm install @next / bundle - analyzer
// next.config.js
const withBundleAnalyzer = require ( '@next/bundle-analyzer' )({
enabled: process.env. ANALYZE === 'true' ,
});
module . exports = withBundleAnalyzer ({
// Next.js config
});
// package.json
{
"scripts" : {
"analyze" : "ANALYZE=true npm run build"
}
}
// Vite with rollup-plugin-visualizer
npm install -- save - dev rollup - plugin - visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer' ;
export default {
plugins: [
visualizer ({
open: true ,
gzipSize: true ,
brotliSize: true ,
}),
],
};
// Source map explorer
npm install -- save - dev source - map - explorer
// package.json
{
"scripts" : {
"analyze" : "source-map-explorer 'build/static/js/*.js'"
}
}
// Optimization strategies based on bundle analysis
// 1. Replace heavy libraries
// ❌ moment.js (288KB)
import moment from 'moment' ;
const date = moment (). format ( 'YYYY-MM-DD' );
// ✅ date-fns (13KB with tree shaking)
import { format } from 'date-fns' ;
const date = format ( new Date (), 'yyyy-MM-dd' );
// 2. Import only what you need
// ❌ Import entire library
import _ from 'lodash' ;
const result = _. debounce (fn, 300 );
// ✅ Import specific function
import debounce from 'lodash/debounce' ;
const result = debounce (fn, 300 );
// 3. Use dynamic imports for large libraries
// ❌ Import upfront
import { Chart } from 'chart.js' ;
// ✅ Load on demand
const loadChart = async () => {
const { Chart } = await import ( 'chart.js' );
return Chart;
};
// 4. Configure externals (CDN)
// webpack.config.js
module . exports = {
externals: {
react: 'React' ,
'react-dom' : 'ReactDOM' ,
},
};
// HTML
< script crossorigin src = "https://unpkg.com/react@18/umd/react.production.min.js" ></ script >
// 5. Optimize images
// Use next/image or similar
import Image from 'next/image' ;
< Image
src = "/hero.jpg"
width = { 800 }
height = { 600 }
alt = "Hero"
placeholder = "blur"
/>
// 6. Remove unused CSS
// Use PurgeCSS or Tailwind's built-in purge
// tailwind.config.js
module . exports = {
content: [ './src/**/*.{js,jsx,ts,tsx}' ],
// CSS not used in these files will be removed
};
// 7. Minify and compress
// webpack.config.js
const TerserPlugin = require ( 'terser-webpack-plugin' );
const CompressionPlugin = require ( 'compression-webpack-plugin' );
module . exports = {
optimization: {
minimize: true ,
minimizer: [
new TerserPlugin ({
terserOptions: {
compress: {
drop_console: true , // Remove console.log
},
},
}),
],
},
plugins: [
new CompressionPlugin ({
algorithm: 'brotliCompress' ,
test: / \. (js | css | html | svg) $ / ,
}),
],
};
// 8. Analyze and set performance budgets
// webpack.config.js
module . exports = {
performance: {
maxAssetSize: 244 * 1024 , // 244 KB
maxEntrypointSize: 244 * 1024 ,
hints: 'error' , // or 'warning'
},
};
// Custom bundle size check
const fs = require ( 'fs' );
const path = require ( 'path' );
const buildFolder = path. join (__dirname, 'build/static/js' );
const files = fs. readdirSync (buildFolder);
let totalSize = 0 ;
files. forEach ( file => {
if (file. endsWith ( '.js' )) {
const stats = fs. statSync (path. join (buildFolder, file));
totalSize += stats.size;
}
});
console. log ( `Total JS bundle size: ${ ( totalSize / 1024 ). toFixed ( 2 ) } KB` );
if (totalSize > 500 * 1024 ) {
console. error ( 'Bundle size exceeds 500KB!' );
process. exit ( 1 );
}
9.5 Image Optimization next/image WebP
Technique
Implementation
Benefit
Browser Support
WebP Format
Modern image format
25-35% smaller than JPEG
96%+ (with fallback)
AVIF Format
Newest format
50% smaller than JPEG
90%+ (newer browsers)
Lazy Loading
loading="lazy"
Load images as needed
Native browser support
Responsive Images
srcset, sizes
Load appropriate size
Universal support
next/image
Next.js Image component
Automatic optimization
Works everywhere
CDN
Image transformation API
On-the-fly optimization
Cloudinary, Imgix
Example: Modern image optimization techniques
// Next.js Image component (automatic optimization)
import Image from 'next/image';
function Hero() {
return (
< Image
src = "/hero.jpg"
alt = "Hero image"
width = {1200}
height = {600}
priority // Load immediately (above fold)
placeholder = "blur" // Show blur while loading
blurDataURL = "data:image/jpeg;base64,..." // Low-res placeholder
/>
);
}
// Responsive images with next/image
< Image
src = "/product.jpg"
alt = "Product"
width = {800}
height = {600}
sizes = "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
// Automatically generates srcset
/>
// External images (configure in next.config.js)
// next.config.js
module.exports = {
images: {
domains: ['cdn.example.com', 'images.unsplash.com'],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
// Native responsive images with picture element
< picture >
< source
type = "image/avif"
srcSet = "
/image-small.avif 400w,
/image-medium.avif 800w,
/image-large.avif 1200w
"
sizes = "(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
/>
< source
type = "image/webp"
srcSet = "
/image-small.webp 400w,
/image-medium.webp 800w,
/image-large.webp 1200w
"
sizes = "(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
/>
< img
src = "/image-large.jpg"
alt = "Description"
loading = "lazy"
decoding = "async"
/>
</ picture >
// Native lazy loading
< img
src = "image.jpg"
alt = "Description"
loading = "lazy"
decoding = "async"
width = "800"
height = "600"
/>
// Blur placeholder with CSS
.image-container {
background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);
position: relative;
}
.image-container::before {
content: '';
position: absolute;
inset: 0;
background-image: url('data:image/jpeg;base64,...'); // Tiny base64
background-size: cover;
filter: blur(10px);
transition: opacity 0.3s;
}
.image-container.loaded::before {
opacity: 0;
}
// Convert images to WebP/AVIF with sharp
npm install sharp
// generateImages.js
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
async function convertImage(inputPath, outputDir) {
const filename = path.basename(inputPath, path.extname(inputPath));
// Generate WebP
await sharp(inputPath)
.webp({ quality: 80 })
.toFile(path.join(outputDir, `${filename}.webp`));
// Generate AVIF
await sharp(inputPath)
.avif({ quality: 65 })
.toFile(path.join(outputDir, `${filename}.avif`));
// Generate responsive sizes
const sizes = [400, 800, 1200];
for (const size of sizes) {
await sharp(inputPath)
.resize(size)
.webp({ quality: 80 })
.toFile(path.join(outputDir, `${filename}-${size}.webp`));
}
}
// Cloudinary integration
< img
src = "https://res.cloudinary.com/demo/image/upload/w_400,f_auto,q_auto/sample.jpg"
alt = "Optimized"
loading = "lazy"
/>
// w_400: width 400px
// f_auto: automatic format (WebP/AVIF)
// q_auto: automatic quality
// React component for optimized images
function OptimizedImage({ src, alt, width, height }) {
const [loaded, setLoaded] = useState(false);
return (
< div className = "image-wrapper" >
< picture >
< source
type = "image/avif"
srcSet = { `${src}.avif`}
/>
< source
type = "image/webp"
srcSet = { `${src}.webp`}
/>
< img
src = { `${src}.jpg`}
alt = {alt}
width = {width}
height = {height}
loading = "lazy"
decoding = "async"
onLoad = {() = > setLoaded(true)}
className={loaded ? 'loaded' : ''}
/>
</ picture >
</ div >
);
}
// Intersection Observer for custom lazy loading
function LazyImage({ src, alt }) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ rootMargin: '50px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
< img
ref = {imgRef}
src = {isInView ? src : ''}
alt = {alt}
onLoad = {() = > setIsLoaded(true)}
style={{ opacity: isLoaded ? 1 : 0 }}
/>
);
}
// Progressive JPEG loading
// Use progressive JPEG encoding for better perceived performance
// ImageMagick: convert input.jpg -interlace Plane output.jpg
// Sharp: sharp(input).jpeg({ progressive: true }).toFile(output)
// Image compression before upload
async function compressImage(file) {
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
};
const compressedFile = await imageCompression(file, options);
return compressedFile;
}
9.6 Service Worker Caching Workbox
Strategy
Description
Use Case
Workbox Method
Cache First
Cache, fallback to network
Static assets (CSS, JS, images)
CacheFirst
Network First
Network, fallback to cache
API calls, dynamic content
NetworkFirst
Stale While Revalidate
Cache first, update in background
Frequent updates (avatars, feeds)
StaleWhileRevalidate
Network Only
Always use network
Real-time data
NetworkOnly
Cache Only
Only use cache
Pre-cached resources
CacheOnly
Precaching
Cache during install
App shell, critical resources
precacheAndRoute
Example: Service Worker with Workbox caching strategies
// Install Workbox
npm install workbox - webpack - plugin
// webpack.config.js
const { GenerateSW } = require ( 'workbox-webpack-plugin' );
module . exports = {
plugins: [
new GenerateSW ({
clientsClaim: true ,
skipWaiting: true ,
runtimeCaching: [
{
urlPattern: / \. (?:png | jpg | jpeg | svg | gif) $ / ,
handler: 'CacheFirst' ,
options: {
cacheName: 'images' ,
expiration: {
maxEntries: 50 ,
maxAgeSeconds: 30 * 24 * 60 * 60 , // 30 days
},
},
},
{
urlPattern: / ^ https: \/\/ api \. example \. com/ ,
handler: 'NetworkFirst' ,
options: {
cacheName: 'api-cache' ,
networkTimeoutSeconds: 10 ,
expiration: {
maxEntries: 50 ,
maxAgeSeconds: 5 * 60 , // 5 minutes
},
},
},
],
}),
],
};
// Custom service worker with Workbox
// service-worker.js
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 generated assets
precacheAndRoute (self.__WB_MANIFEST);
// Cache images with CacheFirst strategy
registerRoute (
({ request }) => request.destination === 'image' ,
new CacheFirst ({
cacheName: 'images' ,
plugins: [
new ExpirationPlugin ({
maxEntries: 60 ,
maxAgeSeconds: 30 * 24 * 60 * 60 , // 30 days
}),
],
})
);
// Cache CSS and JavaScript with StaleWhileRevalidate
registerRoute (
({ request }) =>
request.destination === 'style' ||
request.destination === 'script' ,
new StaleWhileRevalidate ({
cacheName: 'static-resources' ,
})
);
// Cache API responses with NetworkFirst
registerRoute (
({ url }) => url.pathname. startsWith ( '/api/' ),
new NetworkFirst ({
cacheName: 'api-cache' ,
networkTimeoutSeconds: 10 ,
plugins: [
new CacheableResponsePlugin ({
statuses: [ 0 , 200 ],
}),
new ExpirationPlugin ({
maxEntries: 50 ,
maxAgeSeconds: 5 * 60 , // 5 minutes
}),
],
})
);
// Cache Google Fonts
registerRoute (
({ url }) => url.origin === 'https://fonts.googleapis.com' ,
new StaleWhileRevalidate ({
cacheName: 'google-fonts-stylesheets' ,
})
);
registerRoute (
({ url }) => url.origin === 'https://fonts.gstatic.com' ,
new CacheFirst ({
cacheName: 'google-fonts-webfonts' ,
plugins: [
new CacheableResponsePlugin ({
statuses: [ 0 , 200 ],
}),
new ExpirationPlugin ({
maxAgeSeconds: 60 * 60 * 24 * 365 , // 1 year
maxEntries: 30 ,
}),
],
})
);
// Register service worker
// main.js
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);
});
});
}
// Create React App with Workbox
// src/service-worker.js
import { clientsClaim } from 'workbox-core' ;
import { ExpirationPlugin } from 'workbox-expiration' ;
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching' ;
import { registerRoute } from 'workbox-routing' ;
import { StaleWhileRevalidate } from 'workbox-strategies' ;
clientsClaim ();
precacheAndRoute (self.__WB_MANIFEST);
const fileExtensionRegexp = new RegExp ( "/[^/?]+ \\ .[^/]+$" );
registerRoute (
({ request , url }) => {
if (request.mode !== 'navigate' ) return false ;
if (url.pathname. startsWith ( '/_' )) return false ;
if (url.pathname. match (fileExtensionRegexp)) return false ;
return true ;
},
createHandlerBoundToURL (process.env. PUBLIC_URL + '/index.html' )
);
registerRoute (
({ url }) =>
url.origin === self.location.origin && url.pathname. endsWith ( '.png' ),
new StaleWhileRevalidate ({
cacheName: 'images' ,
plugins: [ new ExpirationPlugin ({ maxEntries: 50 })],
})
);
self. addEventListener ( 'message' , ( event ) => {
if (event.data && event.data.type === 'SKIP_WAITING' ) {
self. skipWaiting ();
}
});
// Register in index.js
import * as serviceWorkerRegistration from './serviceWorkerRegistration' ;
serviceWorkerRegistration. register ();
// Next.js with next-pwa
npm install next - pwa
// next.config.js
const withPWA = require ( 'next-pwa' )({
dest: 'public' ,
register: true ,
skipWaiting: true ,
runtimeCaching: [
{
urlPattern: / ^ https: \/\/ fonts \. (?:googleapis | gstatic) \. com \/ . * / i ,
handler: 'CacheFirst' ,
options: {
cacheName: 'google-fonts' ,
expiration: {
maxEntries: 4 ,
maxAgeSeconds: 365 * 24 * 60 * 60 , // 365 days
},
},
},
{
urlPattern: / \. (?:eot | otf | ttc | ttf | woff | woff2 | font . css) $ / i ,
handler: 'StaleWhileRevalidate' ,
options: {
cacheName: 'static-font-assets' ,
expiration: {
maxEntries: 4 ,
maxAgeSeconds: 7 * 24 * 60 * 60 , // 7 days
},
},
},
{
urlPattern: / \. (?:jpg | jpeg | gif | png | svg | ico | webp) $ / i ,
handler: 'StaleWhileRevalidate' ,
options: {
cacheName: 'static-image-assets' ,
expiration: {
maxEntries: 64 ,
maxAgeSeconds: 24 * 60 * 60 , // 24 hours
},
},
},
],
});
module . exports = withPWA ({
// Next.js config
});
// Offline fallback page
// service-worker.js
import { offlineFallback } from 'workbox-recipes' ;
offlineFallback ({
pageFallback: '/offline.html' ,
});
// Background sync for failed requests
import { BackgroundSyncPlugin } from 'workbox-background-sync' ;
const bgSyncPlugin = new BackgroundSyncPlugin ( 'api-queue' , {
maxRetentionTime: 24 * 60 , // Retry for up to 24 hours (in minutes)
});
registerRoute (
/ \/ api \/ . * \/ * . json/ ,
new NetworkOnly ({
plugins: [bgSyncPlugin],
}),
'POST'
);
// Cache analytics requests
import { Queue } from 'workbox-background-sync' ;
const queue = new Queue ( 'analytics-queue' );
self. addEventListener ( 'fetch' , ( event ) => {
if (event.request.url. includes ( '/analytics' )) {
const promiseChain = fetch (event.request. clone ()). catch (() => {
return queue. pushRequest ({ request: event.request });
});
event. waitUntil (promiseChain);
}
});
Core Web Vitals - Measure LCP (<2.5s), CLS (<0.1), FID/INP
(<100ms/200ms) using web-vitals library, optimize with preload, proper dimensions, code splitting
Code Splitting - Use React.lazy + Suspense for route/component splitting,
webpack magic comments for chunk naming, prefetch/preload for predictive loading
Memoization - React.memo for expensive component renders, useMemo for
computed values, useCallback for function references passed to children
Bundle Analysis - Use webpack-bundle-analyzer or rollup visualizer,
identify large dependencies, replace heavy libraries, tree shake unused code
Image Optimization - Use WebP/AVIF formats, next/image for automatic
optimization, responsive images with srcset, lazy loading, blur placeholders
Service Workers - Implement caching strategies with Workbox: CacheFirst for
static assets, NetworkFirst for API, StaleWhileRevalidate for frequently updated content
Best Practices - Set performance budgets, monitor with Lighthouse CI,
compress assets (gzip/brotli), use CDN, minimize main thread work
10. Frontend Security Implementation Practices
10.1 XSS Prevention DOMPurify Sanitization
Attack Type
Prevention Method
Implementation
Example
Stored XSS
Sanitize on input/output
DOMPurify, validator.js
Clean user-generated content
Reflected XSS
Escape user input
Template literals, React escape
URL parameters, search queries
DOM-based XSS
Avoid dangerous APIs
Avoid innerHTML, eval, document.write
Use textContent, createElement
CSP Headers
Restrict script sources
Content-Security-Policy header
Prevent inline scripts
HTTP-only Cookies
Prevent JS access
Set HttpOnly flag
Protect session tokens
React dangerouslySetInnerHTML
Sanitize before use
DOMPurify + dangerouslySetInnerHTML
Rich text editors
Example: XSS prevention with DOMPurify
// Install DOMPurify
npm install dompurify
npm install -- save - dev @types / dompurify
// Sanitize user input with DOMPurify
import DOMPurify from 'dompurify' ;
function SafeHTML ({ html }) {
const cleanHTML = DOMPurify. sanitize (html, {
ALLOWED_TAGS: [ 'b' , 'i' , 'em' , 'strong' , 'a' , 'p' , 'br' ],
ALLOWED_ATTR: [ 'href' , 'title' , 'target' ],
});
return (
< div
dangerouslySetInnerHTML = {{ __html: cleanHTML }}
/>
);
}
// Usage
const userContent = '<script>alert("XSS")</script><p>Safe content</p>' ;
< SafeHTML html = {userContent} />
// Output: <p>Safe content</p> (script removed)
// React custom hook for sanitization
import { useMemo } from 'react' ;
import DOMPurify from 'dompurify' ;
function useSanitizedHTML ( html : string ) {
return useMemo (() => DOMPurify. sanitize (html), [html]);
}
// Usage
function RichTextDisplay ({ content }) {
const cleanContent = useSanitizedHTML (content);
return < div dangerouslySetInnerHTML = {{ __html: cleanContent }} />;
}
// Strict sanitization (remove all HTML)
const strictClean = DOMPurify. sanitize (html, { ALLOWED_TAGS: [] });
// Custom sanitization with hooks
DOMPurify. addHook ( 'afterSanitizeAttributes' , ( node ) => {
// Force all links to open in new tab
if (node.tagName === 'A' ) {
node. setAttribute ( 'target' , '_blank' );
node. setAttribute ( 'rel' , 'noopener noreferrer' );
}
});
// Avoid dangerous DOM APIs
// ❌ Dangerous - XSS vulnerable
element.innerHTML = userInput;
eval (userInput);
document. write (userInput);
new Function (userInput)();
// ✅ Safe alternatives
element.textContent = userInput;
element.innerText = userInput;
const node = document. createTextNode (userInput);
element. appendChild (node);
// React automatically escapes
function SafeComponent ({ userInput }) {
// React escapes this automatically
return < div >{userInput}</ div >;
}
// Validate and sanitize URL inputs
import validator from 'validator' ;
function validateURL ( url : string ) {
if ( ! validator. isURL (url, { protocols: [ 'http' , 'https' ] })) {
throw new Error ( 'Invalid URL' );
}
// Additional check for javascript: protocol
if (url. toLowerCase (). startsWith ( 'javascript:' )) {
throw new Error ( 'JavaScript URLs not allowed' );
}
return url;
}
// Safe link component
function SafeLink ({ href , children }) {
const [ isValid , setIsValid ] = useState ( false );
useEffect (() => {
try {
validateURL (href);
setIsValid ( true );
} catch {
setIsValid ( false );
}
}, [href]);
if ( ! isValid) {
return < span >{children}</ span >;
}
return (
< a
href = {href}
target = "_blank"
rel = "noopener noreferrer"
>
{children}
</ a >
);
}
// Content Security Policy in Next.js
// next.config.js
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
` ;
module . exports = {
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Content-Security-Policy' ,
value: ContentSecurityPolicy. replace ( / \s {2,} / g , ' ' ). trim (),
},
{
key: 'X-Frame-Options' ,
value: 'DENY' ,
},
{
key: 'X-Content-Type-Options' ,
value: 'nosniff' ,
},
{
key: 'Referrer-Policy' ,
value: 'strict-origin-when-cross-origin' ,
},
],
},
];
},
};
// Server-side sanitization (Node.js)
const express = require ( 'express' );
const { body , validationResult } = require ( 'express-validator' );
const DOMPurify = require ( 'isomorphic-dompurify' );
app. post ( '/comment' ,
body ( 'content' ). trim (). escape (),
( req , res ) => {
const errors = validationResult (req);
if ( ! errors. isEmpty ()) {
return res. status ( 400 ). json ({ errors: errors. array () });
}
const cleanContent = DOMPurify. sanitize (req.body.content);
// Save cleanContent to database
}
);
// Vue 3 safe rendering
< template >
<!-- Safe by default -->
< div >{{ userInput }}</ div >
<!-- Dangerous - sanitize first -->
< div v-html = "sanitizedHTML" ></ div >
</ template >
< script setup >
import DOMPurify from 'dompurify';
import { computed } from 'vue';
const props = defineProps(['userInput']);
const sanitizedHTML = computed(() =>
DOMPurify.sanitize(props.userInput)
);
</ script >
// Angular safe HTML pipe
import { Pipe, PipeTransform } from '@angular/core' ;
import { DomSanitizer, SafeHtml } from '@angular/platform-browser' ;
import DOMPurify from 'dompurify' ;
@ Pipe ({ name: 'safeHtml' })
export class SafeHtmlPipe implements PipeTransform {
constructor ( private sanitizer : DomSanitizer ) {}
transform ( html : string ) : SafeHtml {
const cleanHTML = DOMPurify. sanitize (html);
return this .sanitizer. bypassSecurityTrustHtml (cleanHTML);
}
}
// Usage
< div [innerHTML]="userContent | safeHtml"></div>
10.2 CSRF Protection SameSite Cookies
Protection Method
Implementation
Description
Browser Support
SameSite Cookie
Set-Cookie: SameSite=Strict
Prevent cross-site cookie sending
All modern browsers
CSRF Token
Synchronizer token pattern
Unique token per session/request
Universal
Double Submit Cookie
Token in cookie + request header
Compare values server-side
Universal
Custom Header
X-Requested-With: XMLHttpRequest
CORS prevents cross-origin
AJAX requests only
Origin Header Check
Verify Origin/Referer header
Validate request source
Server-side validation
Example: CSRF protection implementation
// Set SameSite cookies (Express.js)
const express = require ( 'express' );
const session = require ( 'express-session' );
app. use ( session ({
secret: process.env. SESSION_SECRET ,
cookie: {
httpOnly: true ,
secure: true , // HTTPS only
sameSite: 'strict' , // or 'lax' for better UX
maxAge: 24 * 60 * 60 * 1000 , // 24 hours
},
}));
// SameSite options:
// - Strict: Cookie not sent on any cross-site request
// - Lax: Cookie sent on top-level navigation (GET)
// - None: Cookie sent on all requests (requires Secure flag)
// CSRF Token with csurf middleware
const csrf = require ( 'csurf' );
const cookieParser = require ( 'cookie-parser' );
app. use ( cookieParser ());
app. use ( csrf ({ cookie: true }));
// Send token to client
app. get ( '/form' , ( req , res ) => {
res. render ( 'form' , { csrfToken: req. csrfToken () });
});
// Validate token on POST
app. post ( '/submit' , ( req , res ) => {
// Token automatically validated by middleware
// If invalid, 403 error is thrown
res. json ({ success: true });
});
// React CSRF token implementation
// Store token in meta tag (server-rendered)
< head >
< meta name = "csrf-token" content = "{{ csrfToken }}" />
</ head >
// Read and include in requests
function getCSRFToken () {
const meta = document. querySelector ( 'meta[name="csrf-token"]' );
return meta ? meta. getAttribute ( 'content' ) : '' ;
}
// Axios interceptor for CSRF token
import axios from 'axios' ;
axios.interceptors.request. use (( config ) => {
const token = getCSRFToken ();
if (token) {
config.headers[ 'X-CSRF-Token' ] = token;
}
return config;
});
// Fetch with CSRF token
async function submitForm ( data ) {
const response = await fetch ( '/api/submit' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-CSRF-Token' : getCSRFToken (),
},
body: JSON . stringify (data),
credentials: 'same-origin' , // Include cookies
});
return response. json ();
}
// React hook for CSRF protection
function useCSRFToken () {
const [ token , setToken ] = useState ( '' );
useEffect (() => {
// Fetch token from server
fetch ( '/api/csrf-token' )
. then ( res => res. json ())
. then ( data => setToken (data.token));
}, []);
return token;
}
// Usage
function SecureForm () {
const csrfToken = useCSRFToken ();
const handleSubmit = async ( data ) => {
await fetch ( '/api/submit' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-CSRF-Token' : csrfToken,
},
body: JSON . stringify (data),
});
};
return < form onSubmit = {handleSubmit}>...</ form >;
}
// Next.js API route with CSRF protection
// pages/api/submit.js
import { csrf } from 'lib/csrf' ;
export default csrf ( async ( req , res ) => {
if (req.method !== 'POST' ) {
return res. status ( 405 ). json ({ error: 'Method not allowed' });
}
// Token validated by middleware
// Process request
res. json ({ success: true });
});
// Custom CSRF middleware
// lib/csrf.js
import { randomBytes } from 'crypto' ;
const tokens = new Map ();
export function generateCSRFToken ( sessionId ) {
const token = randomBytes ( 32 ). toString ( 'hex' );
tokens. set (sessionId, token);
return token;
}
export function validateCSRFToken ( sessionId , token ) {
const storedToken = tokens. get (sessionId);
return storedToken && storedToken === token;
}
export function csrf ( handler ) {
return async ( req , res ) => {
if (req.method === 'POST' ) {
const token = req.headers[ 'x-csrf-token' ];
const sessionId = req.cookies.sessionId;
if ( ! validateCSRFToken (sessionId, token)) {
return res. status ( 403 ). json ({ error: 'Invalid CSRF token' });
}
}
return handler (req, res);
};
}
// Double Submit Cookie pattern
app. post ( '/api/submit' , ( req , res ) => {
const cookieToken = req.cookies.csrfToken;
const headerToken = req.headers[ 'x-csrf-token' ];
if ( ! cookieToken || cookieToken !== headerToken) {
return res. status ( 403 ). json ({ error: 'CSRF validation failed' });
}
// Process request
});
// Origin header validation
app. use (( req , res , next ) => {
const origin = req.headers.origin || req.headers.referer;
const allowedOrigins = [
'https://example.com' ,
'https://www.example.com' ,
];
if (req.method !== 'GET' && req.method !== 'HEAD' ) {
if ( ! origin || ! allowedOrigins. some ( allowed => origin. startsWith (allowed))) {
return res. status ( 403 ). json ({ error: 'Invalid origin' });
}
}
next ();
});
// Custom header check (AJAX only)
app. use (( req , res , next ) => {
if (req.method !== 'GET' && req.method !== 'HEAD' ) {
const xhr = req.headers[ 'x-requested-with' ] === 'XMLHttpRequest' ;
if ( ! xhr) {
return res. status ( 403 ). json ({ error: 'Invalid request' });
}
}
next ();
});
// React Context for CSRF token
const CSRFContext = createContext ( '' );
export function CSRFProvider ({ children }) {
const [ token , setToken ] = useState ( '' );
useEffect (() => {
fetch ( '/api/csrf-token' )
. then ( res => res. json ())
. then ( data => setToken (data.token));
}, []);
return (
< CSRFContext.Provider value = {token}>
{children}
</ CSRFContext.Provider >
);
}
export const useCSRF = () => useContext (CSRFContext);
Directive
Purpose
Example Value
Protection
default-src
Fallback for all directives
'self'
Restrict all resources
script-src
JavaScript sources
'self' 'nonce-{random}'
Prevent inline scripts
style-src
CSS sources
'self' 'unsafe-inline'
Control stylesheets
img-src
Image sources
'self' data: https:
Restrict image loading
connect-src
XHR, WebSocket, fetch
'self' https://api.example.com
Control API endpoints
frame-ancestors
Embedding pages
'none'
Prevent clickjacking
Example: Comprehensive CSP implementation
// Strict CSP policy
Content - Security - Policy :
default- src 'self' ;
script - src 'self' 'nonce-{random}' ;
style - src 'self' 'nonce-{random}' ;
img - src 'self' data : https :;
font - src 'self' data :;
connect - src 'self' https : //api.example.com;
frame - src 'none' ;
frame - ancestors 'none' ;
base - uri 'self' ;
form - action 'self' ;
upgrade - insecure - requests;
block - all - mixed - content;
// Express.js CSP middleware
const helmet = require ( 'helmet' );
app. use (
helmet. contentSecurityPolicy ({
directives: {
defaultSrc: [ "'self'" ],
scriptSrc: [ "'self'" , "'unsafe-inline'" , "'unsafe-eval'" ],
styleSrc: [ "'self'" , "'unsafe-inline'" ],
imgSrc: [ "'self'" , "data:" , "https:" ],
connectSrc: [ "'self'" , "https://api.example.com" ],
fontSrc: [ "'self'" , "data:" ],
objectSrc: [ "'none'" ],
mediaSrc: [ "'self'" ],
frameSrc: [ "'none'" ],
frameAncestors: [ "'none'" ],
upgradeInsecureRequests: [],
},
})
);
// Next.js CSP with nonce
// next.config.js
const crypto = require ( 'crypto' );
module . exports = {
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Content-Security-Policy' ,
value: generateCSP (),
},
],
},
];
},
};
function generateCSP () {
const nonce = crypto. randomBytes ( 16 ). toString ( 'base64' );
const csp = {
'default-src' : [ "'self'" ],
'script-src' : [ "'self'" , `'nonce-${ nonce }'` , "'strict-dynamic'" ],
'style-src' : [ "'self'" , "'unsafe-inline'" ],
'img-src' : [ "'self'" , "data:" , "https:" ],
'font-src' : [ "'self'" , "data:" ],
'connect-src' : [ "'self'" , "https://api.example.com" ],
'frame-ancestors' : [ "'none'" ],
'base-uri' : [ "'self'" ],
'form-action' : [ "'self'" ],
};
return Object. entries (csp)
. map (([ key , values ]) => `${ key } ${ values . join ( ' ' ) }` )
. join ( '; ' );
}
// Use nonce in scripts
< script nonce = "{nonce}" >
console.log('Allowed script');
</ script >
// 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' 'unsafe-inline'"
/>
</ Helmet >
< div >App content</ div >
</>
);
}
// CSP Report-Only mode (testing)
Content - Security - Policy - Report - Only : default- src 'self' ; report - uri / csp - report
// CSP violation reporting endpoint
app. post ( '/csp-report' , express. json ({ type: 'application/csp-report' }), ( req , res ) => {
console. log ( 'CSP Violation:' , req.body);
// Log to monitoring service
res. status ( 204 ). end ();
});
// CSP violation handler (client-side)
document. addEventListener ( 'securitypolicyviolation' , ( e ) => {
console. error ( 'CSP Violation:' , {
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
originalPolicy: e.originalPolicy,
});
// Send to analytics
fetch ( '/api/csp-violation' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
}),
});
});
// Next.js 13 App Router with CSP
// middleware.ts
import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
export function middleware ( request : NextRequest ) {
const nonce = Buffer. from (crypto. randomUUID ()). toString ( 'base64' );
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${ nonce }' 'strict-dynamic';
style-src 'self' 'nonce-${ nonce }';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
` . replace ( / \s {2,} / g , ' ' ). trim ();
const requestHeaders = new Headers (request.headers);
requestHeaders. set ( 'x-nonce' , nonce);
requestHeaders. set ( 'Content-Security-Policy' , cspHeader);
const response = NextResponse. next ({
request: {
headers: requestHeaders,
},
});
response.headers. set ( 'Content-Security-Policy' , cspHeader);
return response;
}
// Vite CSP plugin
// vite.config.ts
import { defineConfig } from 'vite' ;
import { createHtmlPlugin } from 'vite-plugin-html' ;
export default defineConfig ({
plugins: [
createHtmlPlugin ({
inject: {
data: {
csp: "default-src 'self'; script-src 'self' 'unsafe-inline'" ,
},
},
}),
],
server: {
headers: {
'Content-Security-Policy' : "default-src 'self'; script-src 'self'" ,
},
},
});
// Additional security headers
app. use (( req , res , next ) => {
res. setHeader ( 'X-Content-Type-Options' , 'nosniff' );
res. setHeader ( 'X-Frame-Options' , 'DENY' );
res. setHeader ( 'X-XSS-Protection' , '1; mode=block' );
res. setHeader ( 'Referrer-Policy' , 'strict-origin-when-cross-origin' );
res. setHeader ( 'Permissions-Policy' , 'geolocation=(), microphone=()' );
next ();
});
10.4 JWT Token HttpOnly Storage
Storage Method
Security Level
Pros
Cons
HttpOnly Cookie
🟢 High
XSS protection, automatic sending
CSRF risk (mitigated with SameSite)
localStorage
🔴 Low
Easy access, persists
XSS vulnerable, no auto-sending
sessionStorage
🟡 Medium
Tab-scoped, clears on close
XSS vulnerable
Memory (React state)
🟢 High
XSS resistant, cleared on refresh
Lost on refresh, needs refresh token
Secure Cookie + CSRF Token
🟢 Highest
XSS + CSRF protection
Complex implementation
Example: Secure JWT token storage and handling
// ✅ RECOMMENDED: HttpOnly cookie with refresh token
// Server-side (Express.js)
const jwt = require ( 'jsonwebtoken' );
// Login endpoint
app. post ( '/auth/login' , async ( req , res ) => {
const { email , password } = req.body;
// Validate credentials
const user = await validateUser (email, password);
if ( ! user) {
return res. status ( 401 ). json ({ error: 'Invalid credentials' });
}
// Generate access token (short-lived)
const accessToken = jwt. sign (
{ userId: user.id, email: user.email },
process.env. ACCESS_TOKEN_SECRET ,
{ expiresIn: '15m' }
);
// Generate refresh token (long-lived)
const refreshToken = jwt. sign (
{ userId: user.id },
process.env. REFRESH_TOKEN_SECRET ,
{ expiresIn: '7d' }
);
// Store refresh token in database
await storeRefreshToken (user.id, refreshToken);
// Set HttpOnly cookies
res. cookie ( 'accessToken' , accessToken, {
httpOnly: true ,
secure: true , // HTTPS only
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: '/auth/refresh' , // Only sent to refresh endpoint
});
res. json ({ success: true , user: { id: user.id, email: user.email } });
});
// Verify token middleware
function authenticateToken ( req , res , next ) {
const token = req.cookies.accessToken;
if ( ! token) {
return res. status ( 401 ). json ({ error: 'No token provided' });
}
jwt. verify (token, process.env. ACCESS_TOKEN_SECRET , ( err , user ) => {
if (err) {
return res. status ( 403 ). json ({ error: 'Invalid token' });
}
req.user = user;
next ();
});
}
// Protected route
app. get ( '/api/profile' , authenticateToken, ( req , res ) => {
res. json ({ user: req.user });
});
// Refresh token endpoint
app. post ( '/auth/refresh' , async ( req , res ) => {
const refreshToken = req.cookies.refreshToken;
if ( ! refreshToken) {
return res. status ( 401 ). json ({ error: 'No refresh token' });
}
// Verify refresh token
jwt. verify (refreshToken, process.env. REFRESH_TOKEN_SECRET , async ( err , user ) => {
if (err) {
return res. status ( 403 ). json ({ error: 'Invalid refresh token' });
}
// Check if token exists in database
const storedToken = await getRefreshToken (user.userId);
if (storedToken !== refreshToken) {
return res. status ( 403 ). json ({ error: 'Token revoked' });
}
// Generate new access token
const newAccessToken = jwt. sign (
{ userId: user.userId },
process.env. ACCESS_TOKEN_SECRET ,
{ expiresIn: '15m' }
);
res. cookie ( 'accessToken' , newAccessToken, {
httpOnly: true ,
secure: true ,
sameSite: 'strict' ,
maxAge: 15 * 60 * 1000 ,
});
res. json ({ success: true });
});
});
// Logout endpoint
app. post ( '/auth/logout' , async ( req , res ) => {
const refreshToken = req.cookies.refreshToken;
// Remove from database
if (refreshToken) {
await revokeRefreshToken (refreshToken);
}
// Clear cookies
res. clearCookie ( 'accessToken' );
res. clearCookie ( 'refreshToken' );
res. json ({ success: true });
});
// Client-side (React)
// Auth context with automatic refresh
import { createContext, useContext, useState, useEffect } from 'react' ;
const AuthContext = createContext ( null );
export function AuthProvider ({ children }) {
const [ user , setUser ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
// Check authentication on mount
checkAuth ();
// Set up automatic token refresh
const interval = setInterval (() => {
refreshAccessToken ();
}, 14 * 60 * 1000 ); // Refresh every 14 minutes
return () => clearInterval (interval);
}, []);
const checkAuth = async () => {
try {
const response = await fetch ( '/api/profile' , {
credentials: 'include' , // Include cookies
});
if (response.ok) {
const data = await response. json ();
setUser (data.user);
}
} catch (error) {
console. error ( 'Auth check failed:' , error);
} finally {
setLoading ( false );
}
};
const refreshAccessToken = async () => {
try {
await fetch ( '/auth/refresh' , {
method: 'POST' ,
credentials: 'include' ,
});
} catch (error) {
console. error ( 'Token refresh failed:' , error);
setUser ( null );
}
};
const login = async ( email , password ) => {
const response = await fetch ( '/auth/login' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email, password }),
credentials: 'include' ,
});
if (response.ok) {
const data = await response. json ();
setUser (data.user);
return true ;
}
return false ;
};
const logout = async () => {
await fetch ( '/auth/logout' , {
method: 'POST' ,
credentials: 'include' ,
});
setUser ( null );
};
return (
< AuthContext.Provider value = {{ user, login, logout, loading }}>
{children}
</ AuthContext.Provider >
);
}
export const useAuth = () => useContext (AuthContext);
// Axios interceptor for automatic token refresh
import axios from 'axios' ;
axios.defaults.withCredentials = true ;
let isRefreshing = false ;
let failedQueue = [];
const processQueue = ( error , token = null ) => {
failedQueue. forEach ( prom => {
if (error) {
prom. reject (error);
} else {
prom. resolve (token);
}
});
failedQueue = [];
};
axios.interceptors.response. use (
( response ) => response,
async ( error ) => {
const originalRequest = error.config;
if (error.response?.status === 401 && ! originalRequest._retry) {
if (isRefreshing) {
return new Promise (( resolve , reject ) => {
failedQueue. push ({ resolve, reject });
}). then (() => axios (originalRequest));
}
originalRequest._retry = true ;
isRefreshing = true ;
try {
await axios. post ( '/auth/refresh' );
processQueue ( null );
return axios (originalRequest);
} catch (refreshError) {
processQueue (refreshError);
// Redirect to login
window.location.href = '/login' ;
return Promise . reject (refreshError);
} finally {
isRefreshing = false ;
}
}
return Promise . reject (error);
}
);
// Next.js middleware for token validation
// middleware.ts
import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
import { jwtVerify } from 'jose' ;
export async function middleware ( request : NextRequest ) {
const token = request.cookies. get ( 'accessToken' )?.value;
if ( ! token) {
return NextResponse. redirect ( new URL ( '/login' , request.url));
}
try {
await jwtVerify (
token,
new TextEncoder (). encode (process.env. ACCESS_TOKEN_SECRET ! )
);
return NextResponse. next ();
} catch {
return NextResponse. redirect ( new URL ( '/login' , request.url));
}
}
export const config = {
matcher: [ '/dashboard/:path*' , '/profile/:path*' ],
};
Library
Ecosystem
Features
Use Case
Joi
Node.js backend
Rich schema, custom messages
API validation
Yup
React frontend
Schema validation, TypeScript
Form validation
Zod
Full-stack TypeScript
Type inference, parse
tRPC, type-safe APIs
express-validator
Express.js
Middleware chain
Request validation
class-validator
NestJS, TypeScript
Decorator-based
DTO validation
Ajv
JSON Schema
Fast, standard-based
Configuration validation
// Joi validation (Backend/Node.js)
npm install joi
const Joi = require ( 'joi' );
// Define schema
const userSchema = Joi. object ({
username: Joi. string ()
. alphanum ()
. min ( 3 )
. max ( 30 )
. required ()
. messages ({
'string.min' : 'Username must be at least 3 characters' ,
'any.required' : 'Username is required' ,
}),
email: Joi. string ()
. email ({ minDomainSegments: 2 })
. required (),
password: Joi. string ()
. pattern ( / ^ (?= . * [a-z] )(?= . * [A-Z] )(?= . * \d )(?= . * [@$!%*?&] ) [A-Za-z\d@$!%*?&] {8,}$ / )
. required ()
. messages ({
'string.pattern.base' : 'Password must contain uppercase, lowercase, number, and special character' ,
}),
age: Joi. number ()
. integer ()
. min ( 18 )
. max ( 120 )
. optional (),
website: Joi. string ()
. uri ()
. optional (),
birthdate: Joi. date ()
. iso ()
. max ( 'now' )
. optional (),
role: Joi. string ()
. valid ( 'user' , 'admin' , 'moderator' )
. default ( 'user' ),
});
// Validate in Express route
app. post ( '/api/users' , async ( req , res ) => {
try {
const value = await userSchema. validateAsync (req.body, {
abortEarly: false , // Return all errors
});
// Value is validated and sanitized
const user = await createUser (value);
res. json (user);
} catch (error) {
if (error.isJoi) {
return res. status ( 400 ). json ({
errors: error.details. map ( detail => ({
field: detail.path. join ( '.' ),
message: detail.message,
})),
});
}
res. status ( 500 ). json ({ error: 'Internal server error' });
}
});
// Yup validation (Frontend/React)
npm install yup
import * as yup from 'yup' ;
// Define schema
const loginSchema = yup. object ({
email: yup
. string ()
. email ( 'Invalid email address' )
. required ( 'Email is required' ),
password: yup
. string ()
. min ( 8 , 'Password must be at least 8 characters' )
. matches (
/ ^ (?= . * [a-z] )(?= . * [A-Z] )(?= . * \d )/ ,
'Password must contain uppercase, lowercase, and number'
)
. required ( 'Password is required' ),
rememberMe: yup. boolean (). default ( false ),
});
// React Hook Form with Yup
import { useForm } from 'react-hook-form' ;
import { yupResolver } from '@hookform/resolvers/yup' ;
function LoginForm () {
const {
register ,
handleSubmit ,
formState : { errors },
} = useForm ({
resolver: yupResolver (loginSchema),
});
const onSubmit = async ( data ) => {
// Data is validated
await login (data);
};
return (
< form onSubmit = { handleSubmit (onSubmit)}>
< input { ... register ( 'email' )} />
{errors.email && < span >{errors.email.message}</ span >}
< input type = "password" { ... register ( 'password' )} />
{errors.password && < span >{errors.password.message}</ span >}
< button type = "submit" >Login</ button >
</ form >
);
}
// Zod validation (Full-stack TypeScript)
npm install zod
import { z } from 'zod' ;
// Define schema with type inference
const userSchema = z. object ({
username: z. string (). min ( 3 ). max ( 30 ),
email: z. string (). email (),
password: z. string (). min ( 8 ),
age: z. number (). int (). min ( 18 ). optional (),
role: z. enum ([ 'user' , 'admin' , 'moderator' ]). default ( 'user' ),
});
// TypeScript type automatically inferred
type User = z . infer < typeof userSchema>;
// Validate
const result = userSchema. safeParse (data);
if ( ! result.success) {
console. error (result.error.issues);
} else {
const user : User = result.data;
}
// tRPC with Zod
import { router, publicProcedure } from './trpc' ;
export const userRouter = router ({
create: publicProcedure
. input (userSchema)
. mutation ( async ({ input }) => {
// input is typed as User
return await createUser (input);
}),
});
// express-validator
npm install express - validator
const { body , validationResult } = require ( 'express-validator' );
app. post ( '/api/users' ,
// Validation middleware
body ( 'email' ). isEmail (). normalizeEmail (),
body ( 'password' ). isLength ({ min: 8 }),
body ( 'username' ). trim (). isLength ({ min: 3 , max: 30 }). escape (),
// Handler
( req , res ) => {
const errors = validationResult (req);
if ( ! errors. isEmpty ()) {
return res. status ( 400 ). json ({ errors: errors. array () });
}
// Process validated data
const user = createUser (req.body);
res. json (user);
}
);
// Custom validation with Yup
const passwordMatchSchema = yup. object ({
password: yup. string (). required (),
confirmPassword: yup
. string ()
. oneOf ([yup. ref ( 'password' )], 'Passwords must match' )
. required (),
});
// Conditional validation
const addressSchema = yup. object ({
country: yup. string (). required (),
state: yup. string (). when ( 'country' , {
is: 'US' ,
then : ( schema ) => schema. required ( 'State is required for US' ),
otherwise : ( schema ) => schema. optional (),
}),
});
// Array validation
const orderSchema = yup. object ({
items: yup. array (). of (
yup. object ({
productId: yup. string (). required (),
quantity: yup. number (). positive (). integer (). required (),
price: yup. number (). positive (). required (),
})
). min ( 1 , 'At least one item required' ),
});
// File validation
const uploadSchema = yup. object ({
file: yup
. mixed ()
. required ( 'File is required' )
. test ( 'fileSize' , 'File too large' , ( value ) => {
return value && value.size <= 5000000 ; // 5MB
})
. test ( 'fileType' , 'Invalid file type' , ( value ) => {
return value && [ 'image/jpeg' , 'image/png' ]. includes (value.type);
}),
});
// Sanitization helpers
function sanitizeInput ( input ) {
return input
. trim ()
. replace ( / [<>"' \/ ] / g , ( char ) => {
const entities = {
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'/' : '/' ,
};
return entities[char];
});
}
// SQL injection prevention with parameterized queries
// ❌ Vulnerable
const query = `SELECT * FROM users WHERE email = '${ email }'` ;
// ✅ Safe
const query = 'SELECT * FROM users WHERE email = ?' ;
db. query (query, [email]);
// NoSQL injection prevention
// ❌ Vulnerable
const user = await User. findOne ({ email: req.body.email });
// ✅ Safe with validation
const emailSchema = yup. string (). email (). required ();
const email = await emailSchema. validate (req.body.email);
const user = await User. findOne ({ email });
10.6 HTTPS Certificate Pinning Security
Security Measure
Implementation
Purpose
Level
HTTPS/TLS
SSL/TLS certificate
Encrypt data in transit
Essential
Certificate Pinning
Pin public key/certificate
Prevent MITM attacks
Advanced
HSTS
Strict-Transport-Security header
Force HTTPS
Recommended
TLS 1.3
Modern protocol version
Latest encryption standards
Best practice
Certificate Transparency
CT logs monitoring
Detect rogue certificates
Advanced
Secure Ciphers
Strong cipher suites
Prevent weak encryption
Essential
Example: HTTPS and security configuration
// HTTPS server setup (Node.js)
const https = require ( 'https' );
const fs = require ( 'fs' );
const express = require ( 'express' );
const app = express ();
const options = {
key: fs. readFileSync ( '/path/to/private-key.pem' ),
cert: fs. readFileSync ( '/path/to/certificate.pem' ),
ca: fs. readFileSync ( '/path/to/ca-certificate.pem' ),
// TLS options
minVersion: 'TLSv1.3' ,
ciphers: [
'TLS_AES_128_GCM_SHA256' ,
'TLS_AES_256_GCM_SHA384' ,
'TLS_CHACHA20_POLY1305_SHA256' ,
]. join ( ':' ),
honorCipherOrder: true ,
};
https. createServer (options, app). listen ( 443 );
// HSTS (HTTP Strict Transport Security)
app. use (( req , res , next ) => {
res. setHeader (
'Strict-Transport-Security' ,
'max-age=31536000; includeSubDomains; preload'
);
next ();
});
// Redirect HTTP to HTTPS
const http = require ( 'http' );
http. createServer (( req , res ) => {
res. writeHead ( 301 , {
Location: `https://${ req . headers . host }${ req . url }` ,
});
res. end ();
}). listen ( 80 );
// Next.js security headers
// next.config.js
module . exports = {
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Strict-Transport-Security' ,
value: 'max-age=63072000; includeSubDomains; preload' ,
},
{
key: 'X-Content-Type-Options' ,
value: 'nosniff' ,
},
{
key: 'X-Frame-Options' ,
value: 'SAMEORIGIN' ,
},
{
key: 'X-XSS-Protection' ,
value: '1; mode=block' ,
},
{
key: 'Referrer-Policy' ,
value: 'strict-origin-when-cross-origin' ,
},
{
key: 'Permissions-Policy' ,
value: 'camera=(), microphone=(), geolocation=()' ,
},
],
},
];
},
};
// Helmet.js for Express security headers
npm install helmet
const helmet = require ( 'helmet' );
app. use ( helmet ({
hsts: {
maxAge: 31536000 ,
includeSubDomains: true ,
preload: true ,
},
contentSecurityPolicy: {
directives: {
defaultSrc: [ "'self'" ],
scriptSrc: [ "'self'" , "'unsafe-inline'" ],
},
},
frameguard: {
action: 'deny' ,
},
}));
// Certificate pinning (React Native / Mobile)
// Note: Not commonly used in web browsers, mainly mobile apps
const fetch = require ( 'node-fetch' );
const https = require ( 'https' );
const crypto = require ( 'crypto' );
const expectedFingerprint = 'AA:BB:CC:DD:EE:FF...' ;
const agent = new https. Agent ({
checkServerIdentity : ( hostname , cert ) => {
const fingerprint = crypto
. createHash ( 'sha256' )
. update (cert.raw)
. digest ( 'hex' )
. toUpperCase ()
. match ( / . {2} / g )
. join ( ':' );
if (fingerprint !== expectedFingerprint) {
throw new Error ( 'Certificate fingerprint mismatch' );
}
},
});
fetch ( 'https://api.example.com' , { agent });
// Subresource Integrity (SRI) for CDN resources
< script
src = "https://cdn.example.com/library.js"
integrity = "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin = "anonymous"
></ script >
// Generate SRI hash
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 }` ;
}
// React component for SRI
function SecureScript ({ src , integrity }) {
useEffect (() => {
const script = document. createElement ( 'script' );
script.src = src;
script.integrity = integrity;
script.crossOrigin = 'anonymous' ;
document.body. appendChild (script);
return () => {
document.body. removeChild (script);
};
}, [src, integrity]);
return null ;
}
// Nginx SSL configuration
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate / path / to / certificate.crt;
ssl_certificate_key / path / to / private.key;
# TLS settings
ssl_protocols TLSv1. 2 TLSv1. 3 ;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256' ;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict - Transport - Security "max-age=31536000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate / path / to / ca.crt;
# Security headers
add_header X - Frame - Options "SAMEORIGIN" always;
add_header X - Content - Type - Options "nosniff" always;
add_header X - XSS - Protection "1; mode=block" always;
}
// Let's Encrypt certificate automation
npm install greenlock - express
const greenlock = require ( 'greenlock-express' );
greenlock
. init ({
packageRoot: __dirname,
configDir: './greenlock.d' ,
maintainerEmail: 'admin@example.com' ,
cluster: false ,
})
. serve (app);
// Certificate monitoring
const https = require ( 'https' );
const tls = require ( 'tls' );
function checkCertificate ( hostname ) {
return new Promise (( resolve , reject ) => {
const socket = tls. connect ( 443 , hostname, () => {
const cert = socket. getPeerCertificate ();
if ( ! cert || ! Object. keys (cert). length ) {
reject ( new Error ( 'No certificate' ));
return ;
}
const validTo = new Date (cert.valid_to);
const daysRemaining = Math. floor ((validTo - new Date ()) / ( 1000 * 60 * 60 * 24 ));
resolve ({
subject: cert.subject,
issuer: cert.issuer,
validFrom: cert.valid_from,
validTo: cert.valid_to,
daysRemaining,
});
socket. end ();
});
socket. on ( 'error' , reject);
});
}
// Usage
checkCertificate ( 'example.com' ). then ( cert => {
console. log ( `Certificate expires in ${ cert . daysRemaining } days` );
if (cert.daysRemaining < 30 ) {
console. warn ( 'Certificate expiring soon!' );
// Send alert
}
});
Frontend Security Best Practices Summary
XSS Prevention - Use DOMPurify for sanitization, avoid
innerHTML/eval/document.write, implement CSP headers, escape user input automatically (React/Vue)
CSRF Protection - Set SameSite cookies (Strict/Lax), implement CSRF tokens,
validate Origin/Referer headers, use custom headers for AJAX
Content Security Policy - Define strict CSP directives, use nonces for
inline scripts, implement CSP violation reporting, prevent inline script execution
JWT Security - Store tokens in HttpOnly cookies, implement refresh token
rotation, use short-lived access tokens (15min), automatic token refresh mechanism
Input Validation - Use Joi/Yup/Zod schemas, validate on both client and
server, sanitize inputs, use parameterized queries for SQL, prevent injection attacks
HTTPS & TLS - Enforce HTTPS with HSTS headers, use TLS 1.3, strong
cipher suites, certificate monitoring, Subresource Integrity for CDN resources
Defense in Depth - Layer multiple security controls, use helmet.js for
Express, implement rate limiting, monitor security violations, regular security audits
11. API Integration Modern Implementation
11.1 Axios Fetch API Error Handling
Feature
Axios
Fetch API
Advantage
HTTP Client
Third-party library
Native browser API
Fetch: No dependency
Error Handling
Automatic error throwing
Manual response.ok check
Axios: Simpler errors
Request/Response Interceptors
Built-in
Manual wrapper needed
Axios: More features
JSON Parsing
Automatic
Manual .json() call
Axios: Convenience
Timeout
Built-in timeout option
AbortController needed
Axios: Native timeout
Request Cancellation
CancelToken / AbortController
AbortController
Both support cancellation
Example: Axios vs Fetch API with comprehensive error handling
// Install Axios
npm install axios
// Axios configuration with interceptors
import axios from 'axios' ;
const apiClient = axios. create ({
baseURL: 'https://api.example.com' ,
timeout: 10000 ,
headers: {
'Content-Type' : 'application/json' ,
},
});
// Request interceptor
apiClient.interceptors.request. use (
( config ) => {
// Add auth token
const token = localStorage. getItem ( 'token' );
if (token) {
config.headers.Authorization = `Bearer ${ token }` ;
}
// Add timestamp
config.metadata = { startTime: new Date () };
return config;
},
( error ) => {
return Promise . reject (error);
}
);
// Response interceptor
apiClient.interceptors.response. use (
( response ) => {
// Log response time
const endTime = new Date ();
const duration = endTime - response.config.metadata.startTime;
console. log ( `Request to ${ response . config . url } took ${ duration }ms` );
return response.data;
},
async ( error ) => {
const originalRequest = error.config;
// Handle 401 - Refresh token
if (error.response?.status === 401 && ! originalRequest._retry) {
originalRequest._retry = true ;
try {
const { data } = await axios. post ( '/auth/refresh' );
localStorage. setItem ( 'token' , data.token);
originalRequest.headers.Authorization = `Bearer ${ data . token }` ;
return apiClient (originalRequest);
} catch (refreshError) {
// Redirect to login
window.location.href = '/login' ;
return Promise . reject (refreshError);
}
}
// Handle different error types
if (error.response) {
// Server responded with error status
console. error ( 'Response error:' , {
status: error.response.status,
data: error.response.data,
headers: error.response.headers,
});
} else if (error.request) {
// Request made but no response
console. error ( 'Network error:' , error.request);
} else {
// Error in request setup
console. error ( 'Request setup error:' , error.message);
}
return Promise . reject (error);
}
);
// Usage examples
async function fetchUsers () {
try {
const users = await apiClient. get ( '/users' );
return users;
} catch (error) {
if (axios. isAxiosError (error)) {
if (error.code === 'ECONNABORTED' ) {
console. error ( 'Request timeout' );
} else if (error.response?.status === 404 ) {
console. error ( 'Users not found' );
}
}
throw error;
}
}
// Parallel requests
async function fetchDashboardData () {
try {
const [ users , posts , comments ] = await Promise . all ([
apiClient. get ( '/users' ),
apiClient. get ( '/posts' ),
apiClient. get ( '/comments' ),
]);
return { users, posts, comments };
} catch (error) {
console. error ( 'Dashboard fetch failed:' , error);
throw error;
}
}
// Request cancellation with Axios
const controller = new AbortController ();
apiClient. get ( '/users' , {
signal: controller.signal,
})
. then ( data => console. log (data))
. catch ( error => {
if (error.code === 'ERR_CANCELED' ) {
console. log ( 'Request cancelled' );
}
});
// Cancel the request
controller. abort ();
// Fetch API with error handling
async function fetchWithErrorHandling ( url , options = {}) {
const controller = new AbortController ();
const timeoutId = setTimeout (() => controller. abort (), 10000 );
try {
const response = await fetch (url, {
... options,
signal: controller.signal,
headers: {
'Content-Type' : 'application/json' ,
... options.headers,
},
});
clearTimeout (timeoutId);
// Check for HTTP errors
if ( ! response.ok) {
const errorData = await response. json (). catch (() => ({}));
throw new Error (errorData.message || `HTTP error! status: ${ response . status }` );
}
const data = await response. json ();
return data;
} catch (error) {
clearTimeout (timeoutId);
if (error.name === 'AbortError' ) {
throw new Error ( 'Request timeout' );
}
throw error;
}
}
// Fetch wrapper with interceptors
class APIClient {
constructor ( baseURL ) {
this .baseURL = baseURL;
this .requestInterceptors = [];
this .responseInterceptors = [];
}
addRequestInterceptor ( fn ) {
this .requestInterceptors. push (fn);
}
addResponseInterceptor ( fn ) {
this .responseInterceptors. push (fn);
}
async request ( endpoint , options = {}) {
let url = `${ this . baseURL }${ endpoint }` ;
let config = { ... options };
// Apply request interceptors
for ( const interceptor of this .requestInterceptors) {
const result = await interceptor ({ url, config });
url = result.url;
config = result.config;
}
try {
const response = await fetch (url, config);
if ( ! response.ok) {
throw new Error ( `HTTP ${ response . status }: ${ response . statusText }` );
}
let data = await response. json ();
// Apply response interceptors
for ( const interceptor of this .responseInterceptors) {
data = await interceptor (data);
}
return data;
} catch (error) {
console. error ( 'Request failed:' , error);
throw error;
}
}
get ( endpoint , options ) {
return this . request (endpoint, { ... options, method: 'GET' });
}
post ( endpoint , body , options ) {
return this . request (endpoint, {
... options,
method: 'POST' ,
body: JSON . stringify (body),
headers: {
'Content-Type' : 'application/json' ,
... options?.headers,
},
});
}
}
// Usage
const api = new APIClient ( 'https://api.example.com' );
api. addRequestInterceptor (({ url , config }) => {
const token = localStorage. getItem ( 'token' );
if (token) {
config.headers = {
... config.headers,
Authorization: `Bearer ${ token }` ,
};
}
return { url, config };
});
api. addResponseInterceptor (( data ) => {
console. log ( 'Response received:' , data);
return data;
});
// React hook for API calls
import { useState, useEffect } from 'react' ;
function useAPI ( url , options = {}) {
const [ data , setData ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
const [ error , setError ] = useState ( null );
useEffect (() => {
const controller = new AbortController ();
async function fetchData () {
try {
setLoading ( true );
const response = await apiClient. get (url, {
... options,
signal: controller.signal,
});
setData (response);
setError ( null );
} catch (err) {
if (err.name !== 'CanceledError' ) {
setError (err);
}
} finally {
setLoading ( false );
}
}
fetchData ();
return () => controller. abort ();
}, [url]);
return { data, loading, error };
}
// Usage
function UsersList () {
const { data : users , loading , error } = useAPI ( '/users' );
if (loading) return < div >Loading...</ div >;
if (error) return < div >Error: {error.message}</ div >;
return (
< ul >
{users. map ( user => (
< li key = {user.id}>{user.name}</ li >
))}
</ ul >
);
}
11.2 GraphQL Apollo Client Codegen
Feature
Implementation
Benefit
Tool
Apollo Client
GraphQL client library
Caching, state management
@apollo/client
Code Generation
Types from schema
Type safety, autocomplete
graphql-codegen
Type-safe Hooks
Generated React hooks
Typed queries/mutations
@graphql-codegen/typescript-react-apollo
Normalized Cache
Automatic data normalization
Efficient updates, consistency
InMemoryCache
Optimistic Updates
Update UI before response
Better UX
optimisticResponse option
Error Handling
GraphQL + network errors
Granular error info
onError link
Example: GraphQL with Apollo Client and code generation
// Install dependencies
npm install @apollo / client graphql
npm install -- save - dev @graphql - codegen / cli @graphql - codegen / typescript @graphql - codegen / typescript - operations @graphql - codegen / typescript - react - apollo
// codegen.yml configuration
schema : 'https://api.example.com/graphql'
documents : 'src/**/*.graphql'
generates :
src / generated / graphql.ts:
plugins :
- typescript
- typescript - operations
- typescript - react - apollo
config :
withHooks : true
withHOC : false
withComponent : false
// package.json scripts
{
"scripts" : {
"codegen" : "graphql-codegen --config codegen.yml" ,
"codegen:watch" : "graphql-codegen --config codegen.yml --watch"
}
}
// GraphQL queries (src/queries/users.graphql)
query GetUsers {
users {
id
name
email
avatar
}
}
query GetUser ($id: ID ! ) {
user (id: $id) {
id
name
email
posts {
id
title
content
}
}
}
mutation CreateUser ($input: CreateUserInput ! ) {
createUser (input: $input) {
id
name
email
}
}
mutation UpdateUser ($id: ID ! , $input: UpdateUserInput ! ) {
updateUser (id: $id, input: $input) {
id
name
email
}
}
// Apollo Client setup
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '@apollo/client' ;
import { setContext } from '@apollo/client/link/context' ;
import { onError } from '@apollo/client/link/error' ;
// HTTP link
const httpLink = createHttpLink ({
uri: 'https://api.example.com/graphql' ,
});
// Auth link
const authLink = setContext (( _ , { headers }) => {
const token = localStorage. getItem ( 'token' );
return {
headers: {
... headers,
authorization: token ? `Bearer ${ token }` : '' ,
},
};
});
// Error link
const errorLink = onError (({ graphQLErrors , networkError }) => {
if (graphQLErrors) {
graphQLErrors. forEach (({ message , locations , path }) => {
console. error ( `[GraphQL error]: Message: ${ message }, Location: ${ locations }, Path: ${ path }` );
});
}
if (networkError) {
console. error ( `[Network error]: ${ networkError }` );
}
});
// Create Apollo Client
const client = new ApolloClient ({
link: errorLink. concat (authLink. concat (httpLink)),
cache: new InMemoryCache ({
typePolicies: {
User: {
keyFields: [ 'id' ],
},
Post: {
keyFields: [ 'id' ],
},
},
}),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network' ,
errorPolicy: 'all' ,
},
query: {
fetchPolicy: 'network-only' ,
errorPolicy: 'all' ,
},
mutate: {
errorPolicy: 'all' ,
},
},
});
// App with ApolloProvider
function App () {
return (
< ApolloProvider client = {client} >
< YourApp />
</ ApolloProvider >
);
}
// Use generated hooks (TypeScript)
import { useGetUsersQuery, useGetUserQuery, useCreateUserMutation } from './generated/graphql' ;
function UsersList () {
const { data , loading , error , refetch } = useGetUsersQuery ({
pollInterval: 5000 , // Poll every 5 seconds
});
if (loading) return < div >Loading ...</ div > ;
if (error) return < div >Error: {error.message} </ div > ;
return (
< div >
< button onClick = {() => refetch ()} > Refresh </ button >
< ul >
{ data ?. users . map ( user => (
< li key = {user.id} > {user.name} - { user . email } </ li >
))}
</ ul >
</ div >
);
}
// Single user with variables
function UserProfile ({ userId } : { userId : string }) {
const { data , loading , error } = useGetUserQuery ({
variables: { id: userId },
skip: ! userId, // Skip query if no userId
});
if (loading) return < div >Loading ...</ div > ;
if (error) return < div >Error: {error.message} </ div > ;
if ( ! data?.user) return < div >User not found </ div > ;
return (
< div >
< h1 >{data.user.name} </ h1 >
< p >{data.user.email} </ p >
< h2 >Posts </ h2 >
< ul >
{ data . user . posts . map ( post => (
< li key = {post.id} > {post.title} </ li >
))}
</ ul >
</ div >
);
}
// Mutation with optimistic response
function CreateUserForm () {
const [ createUser , { loading , error }] = useCreateUserMutation ({
update ( cache , { data }) {
// Update cache after mutation
cache. modify ({
fields: {
users ( existingUsers = []) {
const newUserRef = cache. writeFragment ({
data: data?.createUser,
fragment: gql `
fragment NewUser on User {
id
name
email
}
` ,
});
return [ ... existingUsers, newUserRef];
},
},
});
},
optimisticResponse: {
createUser: {
__typename: 'User' ,
id: 'temp-id' ,
name: 'Loading...' ,
email: 'loading@example.com' ,
},
},
});
const handleSubmit = async ( e : React . FormEvent ) => {
e. preventDefault ();
const formData = new FormData (e.target as HTMLFormElement );
try {
await createUser ({
variables: {
input: {
name: formData. get ( 'name' ) as string ,
email: formData. get ( 'email' ) as string ,
},
},
});
} catch (err) {
console. error ( 'Mutation error:' , err);
}
};
return (
< form onSubmit = {handleSubmit} >
< input name = "name" placeholder = "Name" required />
< input name = "email" type = "email" placeholder = "Email" required />
< button type = "submit" disabled = {loading} >
{ loading ? 'Creating...' : 'Create User' }
</ button >
{ error && < div > Error : { error . message }</ div >}
</ form >
);
}
// Lazy query (load on demand)
import { useGetUserLazyQuery } from './generated/graphql' ;
function SearchUser () {
const [ getUser , { data , loading }] = useGetUserLazyQuery ();
const handleSearch = ( userId : string ) => {
getUser ({ variables: { id: userId } });
};
return (
< div >
< button onClick = {() => handleSearch ( '123' )} > Load User </ button >
{ loading && < div > Loading ...</ div >}
{ data && < div >{ data . user ?. name }</ div >}
</ div >
);
}
// Subscription example
subscription OnUserCreated {
userCreated {
id
name
email
}
}
// Use subscription
import { useOnUserCreatedSubscription } from './generated/graphql' ;
function RealtimeUsers () {
const { data } = useOnUserCreatedSubscription ();
useEffect (() => {
if (data?.userCreated) {
console. log ( 'New user created:' , data.userCreated);
}
}, [data]);
return < div >Listening for new users ...</ div > ;
}
// Fragment colocation
const USER_FRAGMENT = gql `
fragment UserFields on User {
id
name
email
avatar
}
` ;
const GET_USERS_WITH_FRAGMENT = gql `
${ USER_FRAGMENT }
query GetUsers {
users {
...UserFields
}
}
` ;
// Cache manipulation
import { useApolloClient } from '@apollo/client' ;
function UserActions () {
const client = useApolloClient ();
const updateUserInCache = ( userId : string , updates : Partial < User >) => {
client.cache. modify ({
id: client.cache. identify ({ __typename: 'User' , id: userId }),
fields: {
name : () => updates.name,
email : () => updates.email,
},
});
};
return < button onClick ={() => updateUserInCache ( '1' , { name : 'Updated' })}>Update </ button > ;
}
11.3 tRPC Type-safe API Calls
Feature
Description
Benefit
Use Case
End-to-end Type Safety
TypeScript from server to client
No manual type definitions
Full-stack TypeScript apps
No Code Generation
Direct type inference
Simpler setup vs GraphQL
Monorepo projects
React Query Integration
Built on @tanstack/react-query
Caching, optimistic updates
Modern React apps
Zod Validation
Runtime input validation
Type-safe validation
Robust API contracts
Lightweight
Small bundle size
Better performance
Bundle-conscious apps
Autocomplete
Full IDE support
Developer experience
All TypeScript projects
Example: tRPC full-stack type-safe implementation
// Install tRPC
npm install @trpc / server @trpc / client @trpc / react - query @trpc / next @tanstack / react - query zod
// Server setup (server/trpc.ts)
import { initTRPC } from '@trpc/server' ;
import { z } from 'zod' ;
// Create tRPC context
export const createContext = async ({ req , res }) => {
return {
user: req.user,
prisma: prisma, // or your database client
};
};
type Context = Awaited < ReturnType < typeof createContext>>;
// Initialize tRPC
const t = initTRPC. context < Context >(). create ();
// Export reusable router and procedure helpers
export const router = t.router;
export const publicProcedure = t.procedure;
// Protected procedure with auth middleware
const isAuthed = t. middleware (({ ctx , next }) => {
if ( ! ctx.user) {
throw new Error ( 'Not authenticated' );
}
return next ({
ctx: {
user: ctx.user,
},
});
});
export const protectedProcedure = t.procedure. use (isAuthed);
// Define router (server/routers/user.ts)
import { router, publicProcedure, protectedProcedure } from '../trpc' ;
import { z } from 'zod' ;
const userRouter = router ({
// Query - get all users
getAll: publicProcedure
. query ( async ({ ctx }) => {
return await ctx.prisma.user. findMany ();
}),
// Query with input - get user by ID
getById: publicProcedure
. input (z. object ({
id: z. string (),
}))
. query ( async ({ ctx , input }) => {
return await ctx.prisma.user. findUnique ({
where: { id: input.id },
});
}),
// Mutation - create user
create: protectedProcedure
. input (z. object ({
name: z. string (). min ( 3 ). max ( 50 ),
email: z. string (). email (),
age: z. number (). int (). min ( 18 ). optional (),
}))
. mutation ( async ({ ctx , input }) => {
return await ctx.prisma.user. create ({
data: input,
});
}),
// Mutation - update user
update: protectedProcedure
. input (z. object ({
id: z. string (),
name: z. string (). min ( 3 ). max ( 50 ). optional (),
email: z. string (). email (). optional (),
}))
. mutation ( async ({ ctx , input }) => {
const { id , ... data } = input;
return await ctx.prisma.user. update ({
where: { id },
data,
});
}),
// Mutation - delete user
delete: protectedProcedure
. input (z. object ({
id: z. string (),
}))
. mutation ( async ({ ctx , input }) => {
return await ctx.prisma.user. delete ({
where: { id: input.id },
});
}),
});
// Main app router (server/routers/index.ts)
import { router } from '../trpc' ;
import { userRouter } from './user' ;
import { postRouter } from './post' ;
export const appRouter = router ({
user: userRouter,
post: postRouter,
});
export type AppRouter = typeof appRouter;
// Next.js API route (pages/api/trpc/[trpc].ts)
import { createNextApiHandler } from '@trpc/server/adapters/next' ;
import { appRouter } from '../../../server/routers' ;
import { createContext } from '../../../server/trpc' ;
export default createNextApiHandler ({
router: appRouter,
createContext,
onError : ({ path , error }) => {
console. error ( `tRPC Error on '${ path }':` , error);
},
});
// Client setup (utils/trpc.ts)
import { httpBatchLink } from '@trpc/client' ;
import { createTRPCNext } from '@trpc/next' ;
import type { AppRouter } from '../server/routers' ;
export const trpc = createTRPCNext < AppRouter >({
config ({ ctx }) {
return {
links: [
httpBatchLink ({
url: '/api/trpc' ,
headers () {
return {
authorization: `Bearer ${ localStorage . getItem ( 'token' ) }` ,
};
},
}),
],
queryClientConfig: {
defaultOptions: {
queries: {
staleTime: 60 * 1000 , // 1 minute
},
},
},
};
},
ssr: false ,
});
// Wrap app with tRPC provider (pages/_app.tsx)
import { trpc } from '../utils/trpc' ;
import type { AppProps } from 'next/app' ;
function MyApp ({ Component , pageProps } : AppProps ) {
return < Component { ... pageProps } />;
}
export default trpc. withTRPC (MyApp);
// Use tRPC in components - fully type-safe!
import { trpc } from '../utils/trpc' ;
function UsersList () {
// Query - automatically typed!
const { data : users , isLoading , error } = trpc.user.getAll. useQuery ();
if (isLoading) return < div >Loading ...</ div > ;
if (error) return < div >Error: {error.message} </ div > ;
return (
< ul >
{ users ?. map ( user => (
< li key = {user.id} > {user.name} - { user . email } </ li >
))}
</ ul >
);
}
// Query with input
function UserProfile ({ userId } : { userId : string }) {
const { data : user } = trpc.user.getById. useQuery (
{ id: userId },
{ enabled: !! userId } // Only fetch if userId exists
);
return < div >{user?.name} </ div > ;
}
// Mutation
function CreateUserForm () {
const utils = trpc. useContext ();
const createUser = trpc.user.create. useMutation ({
onSuccess : () => {
// Invalidate and refetch users list
utils.user.getAll. invalidate ();
},
onError : ( error ) => {
console. error ( 'Failed to create user:' , error);
},
});
const handleSubmit = ( e : React . FormEvent < HTMLFormElement >) => {
e. preventDefault ();
const formData = new FormData (e.currentTarget);
createUser. mutate ({
name: formData. get ( 'name' ) as string ,
email: formData. get ( 'email' ) as string ,
});
};
return (
< form onSubmit = {handleSubmit} >
< input name = "name" required />
< input name = "email" type = "email" required />
< button type = "submit" disabled = {createUser.isLoading} >
{ createUser . isLoading ? 'Creating...' : 'Create' }
</ button >
{ createUser . error && < div >{ createUser . error . message }</ div >}
</ form >
);
}
// Optimistic updates
function UpdateUserForm ({ userId } : { userId : string }) {
const utils = trpc. useContext ();
const updateUser = trpc.user.update. useMutation ({
onMutate : async ( newData ) => {
// Cancel outgoing refetches
await utils.user.getById. cancel ({ id: userId });
// Snapshot previous value
const previousUser = utils.user.getById. getData ({ id: userId });
// Optimistically update
utils.user.getById. setData ({ id: userId }, ( old ) => ({
... old ! ,
... newData,
}));
return { previousUser };
},
onError : ( err , newData , context ) => {
// Rollback on error
utils.user.getById. setData ({ id: userId }, context?.previousUser);
},
onSettled : () => {
// Refetch after error or success
utils.user.getById. invalidate ({ id: userId });
},
});
return < div > ...</ div > ;
}
// Prefetch for faster navigation
function UserLink ({ userId } : { userId : string }) {
const utils = trpc. useContext ();
const handleMouseEnter = () => {
// Prefetch user data on hover
utils.user.getById. prefetch ({ id: userId });
};
return (
< a href = { `/users/${ userId }` } onMouseEnter = {handleMouseEnter} >
View User
</ a >
);
}
// Infinite query for pagination
const infiniteUserRouter = router ({
getInfinite: publicProcedure
. input (z. object ({
limit: z. number (). min ( 1 ). max ( 100 ). default ( 10 ),
cursor: z. string (). optional (),
}))
. query ( async ({ ctx , input }) => {
const users = await ctx.prisma.user. findMany ({
take: input.limit + 1 ,
cursor: input.cursor ? { id: input.cursor } : undefined ,
});
let nextCursor : string | undefined ;
if (users. length > input.limit) {
const nextItem = users. pop ();
nextCursor = nextItem ! .id;
}
return {
users,
nextCursor,
};
}),
});
// Use infinite query
function InfiniteUsersList () {
const {
data ,
fetchNextPage ,
hasNextPage ,
isFetchingNextPage ,
} = trpc.user.getInfinite. useInfiniteQuery (
{ limit: 10 },
{
getNextPageParam : ( lastPage ) => lastPage.nextCursor,
}
);
return (
< div >
{ data ?. pages . map (( page , i ) => (
< div key = {i} >
{ page . users . map ( user => (
< div key = {user.id} > {user.name} </ div >
))}
</ div >
))}
{ hasNextPage && (
< button onClick = {() => fetchNextPage ()} disabled = {isFetchingNextPage} >
{ isFetchingNextPage ? 'Loading...' : 'Load More' }
</ button >
)}
</ div >
);
}
11.4 React Query Infinite Queries
Feature
Hook
Purpose
Use Case
useQuery
Fetch data
GET requests, caching
List, detail views
useMutation
Modify data
POST/PUT/DELETE
Forms, updates
useInfiniteQuery
Paginated data
Load more pattern
Infinite scroll
Optimistic Updates
Update before response
Instant UI feedback
Likes, votes, edits
Cache Invalidation
Refetch stale data
Keep data fresh
After mutations
Prefetching
Load data early
Faster navigation
Hover, route changes
Example: React Query comprehensive implementation
// Install React Query
npm install @tanstack / react - query @tanstack / react - query - devtools
// Setup QueryClient (App.tsx)
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' ;
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' ;
const queryClient = new QueryClient ({
defaultOptions: {
queries: {
staleTime: 60 * 1000 ,
cacheTime: 5 * 60 * 1000 ,
refetchOnWindowFocus: false ,
retry: 3 ,
},
},
});
function App () {
return (
< QueryClientProvider client = {queryClient}>
< YourApp />
< ReactQueryDevtools initialIsOpen = { false } />
</ QueryClientProvider >
);
}
// useQuery - Basic fetch
import { useQuery } from '@tanstack/react-query' ;
function UsersList () {
const { data : users , isLoading , error } = useQuery ({
queryKey: [ 'users' ],
queryFn : () => fetch ( '/api/users' ). then ( res => res. json ()),
});
if (isLoading) return < div >Loading...</ div >;
if (error) return < div >Error: {error.message}</ div >;
return (
< ul >
{users. map ( user => (
< li key = {user.id}>{user.name}</ li >
))}
</ ul >
);
}
// useMutation - POST/PUT/DELETE
import { useMutation, useQueryClient } from '@tanstack/react-query' ;
function CreateUserForm () {
const queryClient = useQueryClient ();
const createUser = useMutation ({
mutationFn : ( newUser ) => {
return fetch ( '/api/users' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (newUser),
}). then ( res => res. json ());
},
onSuccess : () => {
queryClient. invalidateQueries ({ queryKey: [ 'users' ] });
},
});
return (
< form onSubmit = {( e ) => {
e. preventDefault ();
createUser. mutate ({ name: 'John' , email: 'john@example.com' });
}}>
< button type = "submit" >Create</ button >
</ form >
);
}
// useInfiniteQuery - Pagination
import { useInfiniteQuery } from '@tanstack/react-query' ;
function InfinitePostsList () {
const {
data ,
fetchNextPage ,
hasNextPage ,
isFetchingNextPage ,
} = useInfiniteQuery ({
queryKey: [ 'posts' ],
queryFn : ({ pageParam = 0 }) => {
return fetch ( `/api/posts?cursor=${ pageParam }` ). then ( res => res. json ());
},
getNextPageParam : ( lastPage ) => lastPage.nextCursor,
initialPageParam: 0 ,
});
return (
< div >
{data?.pages. map (( page , i ) => (
< div key = {i}>
{page.posts. map ( post => (
< div key = {post.id}>{post.title}</ div >
))}
</ div >
))}
< button
onClick = {() => fetchNextPage ()}
disabled = { ! hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More' }
</ button >
</ div >
);
}
// Optimistic Updates
function LikeButton ({ postId }) {
const queryClient = useQueryClient ();
const likeMutation = useMutation ({
mutationFn : ( postId ) => fetch ( `/api/posts/${ postId }/like` , { method: 'POST' }),
onMutate : async ( postId ) => {
await queryClient. cancelQueries ({ queryKey: [ 'post' , postId] });
const previousPost = queryClient. getQueryData ([ 'post' , postId]);
queryClient. setQueryData ([ 'post' , postId], ( old ) => ({
... old,
likes: old.likes + 1 ,
}));
return { previousPost };
},
onError : ( err , postId , context ) => {
queryClient. setQueryData ([ 'post' , postId], context.previousPost);
},
});
return < button onClick = {() => likeMutation. mutate (postId)}>Like</ button >;
}
// Prefetch on hover
function PostLink ({ postId }) {
const queryClient = useQueryClient ();
return (
< a
href = { `/posts/${ postId }` }
onMouseEnter = {() => {
queryClient. prefetchQuery ({
queryKey: [ 'post' , postId],
queryFn : () => fetch ( `/api/posts/${ postId }` ). then ( res => res. json ()),
});
}}
>
View Post
</ a >
);
}
11.5 MSW Mock Service Worker
Feature
Purpose
Environment
Benefit
Service Worker API
Intercept network requests
Browser (dev/test)
No code changes
Node.js Integration
Mock in tests
Jest, Vitest
Test isolation
Type-safe Handlers
TypeScript support
All environments
Catch errors early
REST & GraphQL
Both API types
Universal
Flexible mocking
Stateful Mocking
Simulate state changes
Development
Realistic behavior
Network Simulation
Delays, errors
Testing
Edge case testing
Example: MSW for API mocking
// Install MSW
npm install msw -- save - dev
npx msw init public / -- save
// Create handlers (src/mocks/handlers.ts)
import { http, HttpResponse, graphql } from 'msw' ;
export const handlers = [
// REST handlers
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' },
]);
}),
http. get ( '/api/users/:id' , ({ params }) => {
const { id } = params;
return HttpResponse. json ({
id,
name: 'John Doe' ,
email: 'john@example.com' ,
});
}),
http. post ( '/api/users' , async ({ request }) => {
const newUser = await request. json ();
return HttpResponse. json (
{ id: Math. random (). toString (), ... newUser },
{ status: 201 }
);
}),
// Simulate errors
http. get ( '/api/error' , () => {
return HttpResponse. json (
{ error: 'Something went wrong' },
{ status: 500 }
);
}),
// Simulate delay
http. get ( '/api/slow' , async () => {
await delay ( 3000 );
return HttpResponse. json ({ data: 'slow response' });
}),
// GraphQL handlers
graphql. query ( 'GetUsers' , () => {
return HttpResponse. json ({
data: {
users: [
{ id: '1' , name: 'John' },
{ id: '2' , name: 'Jane' },
],
},
});
}),
];
// Browser setup (src/mocks/browser.ts)
import { setupWorker } from 'msw/browser' ;
import { handlers } from './handlers' ;
export const worker = setupWorker ( ... handlers);
// Start in development (src/main.tsx)
async function enableMocking () {
if (process.env. NODE_ENV !== 'development' ) return ;
const { worker } = await import ( './mocks/browser' );
return worker. start ();
}
enableMocking (). then (() => {
ReactDOM. createRoot (document. getElementById ( 'root' ) ! ). render (< App />);
});
// Test setup (src/mocks/server.ts)
import { setupServer } from 'msw/node' ;
import { handlers } from './handlers' ;
export const server = setupServer ( ... handlers);
// Setup tests (src/setupTests.ts)
import { beforeAll, afterEach, afterAll } from 'vitest' ;
import { server } from './mocks/server' ;
beforeAll (() => server. listen ());
afterEach (() => server. resetHandlers ());
afterAll (() => server. close ());
// Test with MSW
import { render, screen, waitFor } from '@testing-library/react' ;
import { server } from './mocks/server' ;
import { http, HttpResponse } from 'msw' ;
test ( 'displays users' , async () => {
render (< UsersList />);
await waitFor (() => {
expect (screen. getByText ( 'John Doe' )). toBeInTheDocument ();
});
});
test ( 'handles error' , async () => {
server. use (
http. get ( '/api/users' , () => {
return HttpResponse. json ({ error: 'Failed' }, { status: 500 });
})
);
render (< UsersList />);
await waitFor (() => {
expect (screen. getByText ( /error/ i )). toBeInTheDocument ();
});
});
// Stateful mocking with database
import { factory, primaryKey } from '@mswjs/data' ;
const db = factory ({
user: {
id: primaryKey (String),
name: String,
email: String,
},
});
db.user. create ({ id: '1' , name: 'John' , email: 'john@example.com' });
export const statefulHandlers = [
http. get ( '/api/users' , () => {
return HttpResponse. json (db.user. getAll ());
}),
http. post ( '/api/users' , async ({ request }) => {
const newUser = await request. json ();
const user = db.user. create ({ id: Math. random (). toString (), ... newUser });
return HttpResponse. json (user, { status: 201 });
}),
];
11.6 API Rate Limiting Retry Logic
Strategy
Implementation
Use Case
Consideration
Exponential Backoff
Delay *= 2 after each retry
429 rate limit errors
Avoid thundering herd
Retry-After Header
Respect server's retry time
Server-specified delays
Most accurate timing
Jitter
Add randomness to delay
Prevent synchronized retries
Better distribution
Circuit Breaker
Stop after threshold failures
Protect failing services
Fail fast when needed
Request Queue
Throttle outgoing requests
Client-side rate limiting
Prevent hitting limits
Token Bucket
Allow burst then limit
Smooth traffic patterns
Balance speed and limits
Example: Advanced retry logic and rate limiting
// Exponential backoff with jitter
async function fetchWithRetry ( url , options = {}, maxRetries = 3 ) {
for ( let attempt = 0 ; attempt <= maxRetries; attempt ++ ) {
try {
const response = await fetch (url, options);
if (response.ok) return response;
if (response.status === 429 || response.status >= 500 ) {
const retryAfter = response.headers. get ( 'Retry-After' );
let delay;
if (retryAfter) {
delay = parseInt (retryAfter) * 1000 ;
} else {
const exponentialDelay = Math. pow ( 2 , attempt) * 1000 ;
const jitter = Math. random () * 1000 ;
delay = exponentialDelay + jitter;
}
await new Promise ( resolve => setTimeout (resolve, delay));
continue ;
}
throw new Error ( `HTTP ${ response . status }` );
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
}
// Circuit Breaker
class CircuitBreaker {
constructor ( threshold = 5 , timeout = 60000 ) {
this .failures = 0 ;
this .threshold = threshold;
this .timeout = timeout;
this .state = 'CLOSED' ;
this .lastFailureTime = null ;
}
async execute ( fn ) {
if ( this .state === 'OPEN' ) {
const timeSinceFailure = Date. now () - this .lastFailureTime;
if (timeSinceFailure > this .timeout) {
this .state = 'HALF_OPEN' ;
} else {
throw new Error ( 'Circuit breaker is OPEN' );
}
}
try {
const result = await fn ();
if ( this .state === 'HALF_OPEN' ) {
this .state = 'CLOSED' ;
this .failures = 0 ;
}
return result;
} catch (error) {
this .failures ++ ;
this .lastFailureTime = Date. now ();
if ( this .failures >= this .threshold) {
this .state = 'OPEN' ;
}
throw error;
}
}
}
const breaker = new CircuitBreaker ();
await breaker. execute (() => fetch ( '/api/data' ));
// Request Queue with Rate Limiting
class RequestQueue {
constructor ( maxRequestsPerWindow = 10 , windowMs = 1000 ) {
this .queue = [];
this .processing = false ;
this .requestsInWindow = 0 ;
this .windowStart = Date. now ();
this .maxRequestsPerWindow = maxRequestsPerWindow;
this .windowMs = windowMs;
}
async add ( fn ) {
return new Promise (( resolve , reject ) => {
this .queue. push ( async () => {
try {
resolve ( await fn ());
} catch (error) {
reject (error);
}
});
this . process ();
});
}
async process () {
if ( this .processing || this .queue. length === 0 ) return ;
this .processing = true ;
while ( this .queue. length > 0 ) {
const now = Date. now ();
const elapsed = now - this .windowStart;
if (elapsed >= this .windowMs) {
this .requestsInWindow = 0 ;
this .windowStart = now;
}
if ( this .requestsInWindow >= this .maxRequestsPerWindow) {
await new Promise ( resolve => setTimeout (resolve, this .windowMs - elapsed));
continue ;
}
const fn = this .queue. shift ();
this .requestsInWindow ++ ;
await fn ();
}
this .processing = false ;
}
}
const queue = new RequestQueue ( 10 , 1000 );
for ( let i = 0 ; i < 100 ; i ++ ) {
queue. add (() => fetch ( `/api/data/${ i }` ));
}
// Token Bucket
class TokenBucket {
constructor ( capacity , refillRate ) {
this .tokens = capacity;
this .capacity = capacity;
this .refillRate = refillRate;
this .lastRefill = Date. now ();
}
async consume ( tokens = 1 ) {
this . refill ();
if ( this .tokens >= tokens) {
this .tokens -= tokens;
return ;
}
const tokensNeeded = tokens - this .tokens;
const waitTime = (tokensNeeded / this .refillRate) * 1000 ;
await new Promise ( resolve => setTimeout (resolve, waitTime));
this . refill ();
this .tokens -= tokens;
}
refill () {
const now = Date. now ();
const elapsed = (now - this .lastRefill) / 1000 ;
const tokensToAdd = elapsed * this .refillRate;
this .tokens = Math. min ( this .capacity, this .tokens + tokensToAdd);
this .lastRefill = now;
}
}
const bucket = new TokenBucket ( 10 , 2 );
await bucket. consume ( 1 );
await fetch ( '/api/data' );
// React Query with retry
import { useQuery } from '@tanstack/react-query' ;
function UserData ({ userId }) {
const { data } = useQuery ({
queryKey: [ 'user' , userId],
queryFn : () => fetch ( `/api/users/${ userId }` ). then ( res => res. json ()),
retry : ( failureCount , error ) => {
if (error.response?.status === 404 ) return false ;
return failureCount < 3 ;
},
retryDelay : ( attemptIndex ) => Math. min ( 1000 * 2 ** attemptIndex, 30000 ),
});
return < div >{data?.name}</ div >;
}
API Integration Best Practices Summary
Axios vs Fetch - Axios provides interceptors, automatic JSON parsing,
built-in timeout; Fetch is native, needs manual error/timeout handling
GraphQL Apollo - Use @graphql-codegen for type-safe hooks, normalize cache,
implement optimistic updates, leverage fragments
tRPC Type Safety - End-to-end TypeScript without codegen, Zod validation,
built on React Query, automatic autocomplete
React Query Patterns - useQuery for fetching, useMutation for updates,
useInfiniteQuery for pagination, optimistic updates, prefetch on hover
MSW Mocking - Mock APIs in browser with Service Worker, stateful mocking,
test without backend, simulate delays/errors
Retry & Rate Limiting - Exponential backoff with jitter, respect
Retry-After headers, circuit breaker, request queue, token bucket
Production Ready - Comprehensive error handling, automatic token refresh,
request cancellation, proper TypeScript types, monitoring
12. Frontend Testing Implementation Stack
12.1 Jest Vitest Unit Testing Setup
Feature
Jest
Vitest
Use Case
Configuration
jest.config.js
vitest.config.ts
Test environment setup
Speed
Standard
Fast (Vite)
Large test suites
Watch Mode
--watch flag
Built-in HMR
Development workflow
Mocking
jest.mock()
vi.mock()
Module mocking
Snapshot Testing
toMatchSnapshot()
toMatchSnapshot()
UI regression
Coverage
Built-in Istanbul
c8 or Istanbul
Code coverage reports
Example: Jest and Vitest comprehensive unit testing setup
// Jest Configuration (jest.config.js)
module . exports = {
preset: 'ts-jest' ,
testEnvironment: 'jsdom' ,
setupFilesAfterEnv: [ '<rootDir>/src/setupTests.ts' ],
moduleNameMapper: {
" \\ .(css|less|scss|sass)$" : "identity-obj-proxy" ,
"^@/(.*)$" : "<rootDir>/src/$1" ,
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}' ,
'!src/**/*.d.ts' ,
'!src/main.tsx' ,
],
coverageThreshold: {
global: {
branches: 80 ,
functions: 80 ,
lines: 80 ,
statements: 80 ,
},
},
};
// 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: './src/setupTests.ts' ,
css: true ,
coverage: {
provider: 'c8' ,
reporter: [ 'text' , 'json' , 'html' ],
exclude: [ 'node_modules/' , 'src/setupTests.ts' ],
},
},
resolve: {
alias: {
'@' : '/src' ,
},
},
});
// Setup Tests (src/setupTests.ts)
import '@testing-library/jest-dom' ;
import { cleanup } from '@testing-library/react' ;
import { afterEach } from 'vitest' ;
afterEach (() => {
cleanup ();
});
// Basic Component Test
import { render, screen } from '@testing-library/react' ;
import { describe, it, expect } from 'vitest' ;
import { Button } from './Button' ;
describe ( 'Button' , () => {
it ( 'renders with text' , () => {
render (< Button >Click me</ Button >);
expect (screen. getByText ( 'Click me' )). toBeInTheDocument ();
});
it ( 'handles click events' , async () => {
const handleClick = vi. fn ();
render (< Button onClick = {handleClick}>Click</ Button >);
const button = screen. getByRole ( 'button' );
await userEvent. click (button);
expect (handleClick). toHaveBeenCalledTimes ( 1 );
});
it ( 'is disabled when disabled prop is true' , () => {
render (< Button disabled >Disabled</ Button >);
expect (screen. getByRole ( 'button' )). toBeDisabled ();
});
});
// Async Test
import { waitFor } from '@testing-library/react' ;
describe ( 'UserList' , () => {
it ( 'fetches and displays users' , async () => {
render (< UserList />);
expect (screen. getByText ( 'Loading...' )). toBeInTheDocument ();
await waitFor (() => {
expect (screen. getByText ( 'John Doe' )). toBeInTheDocument ();
});
});
});
// Mock Functions
import { vi } from 'vitest' ;
const mockFetch = vi. fn ();
global.fetch = mockFetch;
mockFetch. mockResolvedValueOnce ({
json : async () => ({ name: 'John' }),
});
// Snapshot Testing
it ( 'matches snapshot' , () => {
const { container } = render (< Card title = "Test" />);
expect (container.firstChild). toMatchSnapshot ();
});
12.2 React Testing Library User Events
Method
Purpose
Async
Use Case
userEvent.click()
Simulate click
Yes
Buttons, links
userEvent.type()
Simulate typing
Yes
Input fields
userEvent.clear()
Clear input
Yes
Reset forms
userEvent.selectOptions()
Select dropdown
Yes
Select elements
userEvent.upload()
File upload
Yes
File inputs
userEvent.hover()
Hover element
Yes
Tooltips, dropdowns
Example: React Testing Library comprehensive user interaction tests
// Install
npm install -- save - dev @testing - library / react @testing - library / user - event @testing - library / jest - dom
// Form Testing
import { render, screen } from '@testing-library/react' ;
import userEvent from '@testing-library/user-event' ;
import { LoginForm } from './LoginForm' ;
describe ( 'LoginForm' , () => {
it ( 'submits form with user input' , async () => {
const user = userEvent. setup ();
const handleSubmit = vi. fn ();
render (< LoginForm onSubmit = {handleSubmit} />);
const emailInput = screen. getByLabelText ( /email/ i );
const passwordInput = screen. getByLabelText ( /password/ i );
const submitButton = screen. getByRole ( 'button' , { name: /login/ i });
await user. type (emailInput, 'test@example.com' );
await user. type (passwordInput, 'password123' );
await user. click (submitButton);
expect (handleSubmit). toHaveBeenCalledWith ({
email: 'test@example.com' ,
password: 'password123' ,
});
});
it ( 'shows validation errors' , async () => {
const user = userEvent. setup ();
render (< LoginForm />);
const submitButton = screen. getByRole ( 'button' , { name: /login/ i });
await user. click (submitButton);
expect (screen. getByText ( /email is required/ i )). toBeInTheDocument ();
expect (screen. getByText ( /password is required/ i )). toBeInTheDocument ();
});
});
// Query Methods
// getBy* - Throws error if not found (use for elements that should exist)
const button = screen. getByRole ( 'button' , { name: /submit/ i });
// queryBy* - Returns null if not found (use for elements that shouldn't exist)
const error = screen. queryByText ( /error/ i );
expect (error).not. toBeInTheDocument ();
// findBy* - Async, waits for element (use for elements that appear after async operations)
const message = await screen. findByText ( /success/ i );
// Select Dropdown Testing
it ( 'selects option from dropdown' , async () => {
const user = userEvent. setup ();
render (< CountrySelector />);
const select = screen. getByRole ( 'combobox' );
await user. selectOptions (select, 'usa' );
expect (screen. getByRole ( 'option' , { name: 'USA' }).selected). toBe ( true );
});
// File Upload Testing
it ( 'uploads file' , async () => {
const user = userEvent. setup ();
const file = new File ([ 'hello' ], 'hello.png' , { type: 'image/png' });
render (< FileUpload />);
const input = screen. getByLabelText ( /upload file/ i );
await user. upload (input, file);
expect (input.files[ 0 ]). toBe (file);
expect (input.files). toHaveLength ( 1 );
});
// Checkbox and Radio Testing
it ( 'toggles checkbox' , async () => {
const user = userEvent. setup ();
render (< TermsCheckbox />);
const checkbox = screen. getByRole ( 'checkbox' );
expect (checkbox).not. toBeChecked ();
await user. click (checkbox);
expect (checkbox). toBeChecked ();
await user. click (checkbox);
expect (checkbox).not. toBeChecked ();
});
// Hover and Focus Testing
it ( 'shows tooltip on hover' , async () => {
const user = userEvent. setup ();
render (< TooltipButton />);
const button = screen. getByRole ( 'button' );
await user. hover (button);
expect ( await screen. findByRole ( 'tooltip' )). toBeInTheDocument ();
await user. unhover (button);
expect (screen. queryByRole ( 'tooltip' )).not. toBeInTheDocument ();
});
// Keyboard Navigation
it ( 'navigates with keyboard' , async () => {
const user = userEvent. setup ();
render (< TabNavigation />);
const firstTab = screen. getByRole ( 'tab' , { name: /first/ i });
firstTab. focus ();
await user. keyboard ( '{ArrowRight}' );
expect (screen. getByRole ( 'tab' , { name: /second/ i })). toHaveFocus ();
await user. keyboard ( '{Enter}' );
expect (screen. getByRole ( 'tabpanel' )). toHaveTextContent ( 'Second content' );
});
// Custom Queries
import { within } from '@testing-library/react' ;
it ( 'finds nested elements' , () => {
render (< UserCard />);
const card = screen. getByRole ( 'article' );
const heading = within (card). getByRole ( 'heading' );
expect (heading). toHaveTextContent ( 'John Doe' );
});
12.3 Cypress Playwright E2E Automation
Feature
Cypress
Playwright
Best For
Browser Support
Chrome, Firefox, Edge
All + Safari WebKit
Cross-browser testing
Execution
In-browser
Node.js
Speed and reliability
Auto-waiting
Built-in
Built-in
Flaky test reduction
Debugging
Time-travel UI
Inspector, trace viewer
Test development
Parallelization
Paid (Dashboard)
Free (built-in)
CI/CD speed
Mobile Testing
Viewport only
Device emulation
Mobile workflows
Example: Cypress and Playwright E2E testing
// Cypress Installation and Setup
npm install -- save - dev cypress
// cypress.config.ts
import { defineConfig } from 'cypress' ;
export default defineConfig ({
e2e: {
baseUrl: 'http://localhost:3000' ,
viewportWidth: 1280 ,
viewportHeight: 720 ,
video: false ,
screenshotOnRunFailure: true ,
},
});
// Cypress Test (cypress/e2e/login.cy.ts)
describe ( 'Login Flow' , () => {
beforeEach (() => {
cy. visit ( '/login' );
});
it ( 'logs in successfully' , () => {
cy. get ( '[data-testid="email-input"]' ). type ( 'test@example.com' );
cy. get ( '[data-testid="password-input"]' ). type ( 'password123' );
cy. get ( '[data-testid="login-button"]' ). click ();
cy. url (). should ( 'include' , '/dashboard' );
cy. contains ( 'Welcome back' ). should ( 'be.visible' );
});
it ( 'shows error for invalid credentials' , () => {
cy. get ( '[data-testid="email-input"]' ). type ( 'wrong@example.com' );
cy. get ( '[data-testid="password-input"]' ). type ( 'wrong' );
cy. get ( '[data-testid="login-button"]' ). click ();
cy. contains ( 'Invalid credentials' ). should ( 'be.visible' );
});
});
// Cypress Custom Commands (cypress/support/commands.ts)
Cypress.Commands. add ( 'login' , ( email , password ) => {
cy. session ([email, password], () => {
cy. visit ( '/login' );
cy. get ( '[data-testid="email-input"]' ). type (email);
cy. get ( '[data-testid="password-input"]' ). type (password);
cy. get ( '[data-testid="login-button"]' ). click ();
cy. url (). should ( 'include' , '/dashboard' );
});
});
// Use custom command
it ( 'accesses protected page' , () => {
cy. login ( 'test@example.com' , 'password123' );
cy. visit ( '/profile' );
cy. contains ( 'Profile' ). should ( 'be.visible' );
});
// Playwright Installation and Setup
npm install -- save - dev @playwright / test
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test' ;
export default defineConfig ({
testDir: './tests' ,
fullyParallel: true ,
forbidOnly: !! process.env. CI ,
retries: process.env. CI ? 2 : 0 ,
workers: process.env. CI ? 1 : undefined ,
reporter: 'html' ,
use: {
baseURL: 'http://localhost:3000' ,
trace: 'on-first-retry' ,
screenshot: 'only-on-failure' ,
},
projects: [
{
name: 'chromium' ,
use: { ... devices[ 'Desktop Chrome' ] },
},
{
name: 'firefox' ,
use: { ... devices[ 'Desktop Firefox' ] },
},
{
name: 'webkit' ,
use: { ... devices[ 'Desktop Safari' ] },
},
],
webServer: {
command: 'npm run dev' ,
url: 'http://localhost:3000' ,
reuseExistingServer: ! process.env. CI ,
},
});
// Playwright Test (tests/login.spec.ts)
import { test, expect } from '@playwright/test' ;
test. describe ( 'Login Flow' , () => {
test ( 'logs in successfully' , async ({ page }) => {
await page. goto ( '/login' );
await page. fill ( '[data-testid="email-input"]' , 'test@example.com' );
await page. fill ( '[data-testid="password-input"]' , 'password123' );
await page. click ( '[data-testid="login-button"]' );
await expect (page). toHaveURL ( / . * dashboard/ );
await expect (page. locator ( 'text=Welcome back' )). toBeVisible ();
});
test ( 'handles network errors' , async ({ page }) => {
await page. route ( '**/api/login' , route => route. abort ());
await page. goto ( '/login' );
await page. fill ( '[data-testid="email-input"]' , 'test@example.com' );
await page. fill ( '[data-testid="password-input"]' , 'password123' );
await page. click ( '[data-testid="login-button"]' );
await expect (page. locator ( 'text=Network error' )). toBeVisible ();
});
});
// Playwright Fixtures (Custom Setup)
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 ( '[data-testid="email-input"]' , 'test@example.com' );
await page. fill ( '[data-testid="password-input"]' , 'password123' );
await page. click ( '[data-testid="login-button"]' );
await page. waitForURL ( '**/dashboard' );
await use (page);
},
});
// Use fixture
test ( 'accesses profile' , async ({ authenticatedPage }) => {
await authenticatedPage. goto ( '/profile' );
await expect (authenticatedPage. locator ( 'h1' )). toContainText ( 'Profile' );
});
12.4 Storybook Component Testing
Feature
Purpose
Benefit
Use Case
Component Isolation
Develop in isolation
Focus on single component
UI development
Stories
Component states
Document all variants
Design system
Controls
Interactive props
Live editing
Testing variations
Actions
Event logging
Track interactions
Event handlers
Docs
Auto-generated docs
Component documentation
Team collaboration
Addons
Extend functionality
A11y, testing, design
Enhanced workflow
Example: Storybook setup and component stories
// Install Storybook
npx storybook@latest init
// .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' ,
],
framework: {
name: '@storybook/react-vite' ,
options: {},
},
};
export default config;
// Button Component (src/components/Button.tsx)
import React from 'react' ;
interface ButtonProps {
variant ?: 'primary' | 'secondary' | 'danger' ;
size ?: 'sm' | 'md' | 'lg' ;
children : React . ReactNode ;
onClick ?: () => void ;
disabled ?: boolean ;
}
export const Button : React . FC < ButtonProps > = ({
variant = 'primary' ,
size = 'md' ,
children,
onClick,
disabled,
}) => {
return (
< button
className = { `btn btn-${ variant } btn-${ size }` }
onClick = {onClick}
disabled = {disabled}
>
{ children }
</ button >
);
};
// Button Stories (src/components/Button.stories.tsx)
import type { Meta, StoryObj } from '@storybook/react' ;
import { fn } from '@storybook/test' ;
import { Button } from './Button' ;
const meta : Meta < typeof Button> = {
title: 'Components/Button' ,
component: Button,
parameters: {
layout: 'centered' ,
},
tags: [ 'autodocs' ],
argTypes: {
variant: {
control: 'select' ,
options: [ 'primary' , 'secondary' , 'danger' ],
},
size: {
control: 'select' ,
options: [ 'sm' , 'md' , 'lg' ],
},
},
args: {
onClick: fn (),
},
};
export default meta;
type Story = StoryObj < typeof Button>;
export const Primary : Story = {
args: {
variant: 'primary' ,
children: 'Primary Button' ,
},
};
export const Secondary : Story = {
args: {
variant: 'secondary' ,
children: 'Secondary Button' ,
},
};
export const Danger : Story = {
args: {
variant: 'danger' ,
children: 'Danger Button' ,
},
};
export const Small : Story = {
args: {
size: 'sm' ,
children: 'Small Button' ,
},
};
export const Large : Story = {
args: {
size: 'lg' ,
children: 'Large Button' ,
},
};
export const Disabled : Story = {
args: {
disabled: true ,
children: 'Disabled Button' ,
},
};
// Interaction Testing
import { within, userEvent } from '@storybook/test' ;
export const WithInteractions : Story = {
args: {
children: 'Click me' ,
},
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
const button = canvas. getByRole ( 'button' );
await userEvent. click (button);
},
};
// Complex Story with Decorators
export const WithContext : Story = {
args: {
children: 'Themed Button' ,
},
decorators: [
( Story ) => (
< div style = {{ padding : '3rem' , background : '#f0f0f0' }} >
< Story />
</ div >
),
],
};
// Form Story (src/components/LoginForm.stories.tsx)
import { LoginForm } from './LoginForm' ;
const meta : Meta < typeof LoginForm> = {
title: 'Forms/LoginForm' ,
component: LoginForm,
};
export default meta;
export const Default : Story = {};
export const WithError : Story = {
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
await userEvent. click (canvas. getByRole ( 'button' , { name: /login/ i }));
// Verify error messages appear
await canvas. findByText ( /email is required/ i );
},
};
export const SuccessfulLogin : Story = {
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
await userEvent. type (
canvas. getByLabelText ( /email/ i ),
'test@example.com'
);
await userEvent. type (
canvas. getByLabelText ( /password/ i ),
'password123'
);
await userEvent. click (canvas. getByRole ( 'button' , { name: /login/ i }));
},
};
12.5 Istanbul Coverage.js Test Coverage
Metric
Definition
Threshold
Importance
Line Coverage
% of lines executed
80-90%
Basic coverage metric
Branch Coverage
% of if/else branches
75-85%
Logic coverage
Function Coverage
% of functions called
80-90%
API coverage
Statement Coverage
% of statements executed
80-90%
Code execution
Uncovered Lines
Lines not executed
Minimize
Gap identification
Coverage Reports
HTML/JSON/LCOV
CI integration
Tracking trends
Example: Istanbul coverage configuration and reporting
// Jest with Coverage (package.json)
{
"scripts" : {
"test" : "jest" ,
"test:coverage" : "jest --coverage" ,
"test:coverage:watch" : "jest --coverage --watchAll"
},
"jest" : {
"collectCoverageFrom" : [
"src/**/*.{js,jsx,ts,tsx}" ,
"!src/**/*.d.ts" ,
"!src/**/*.stories.tsx" ,
"!src/main.tsx" ,
"!src/vite-env.d.ts"
],
"coverageThreshold" : {
"global" : {
"branches" : 80 ,
"functions" : 80 ,
"lines" : 80 ,
"statements" : 80
},
"src/components/" : {
"branches" : 90 ,
"functions" : 90 ,
"lines" : 90 ,
"statements" : 90
}
},
"coverageReporters" : [
"text" ,
"text-summary" ,
"html" ,
"lcov" ,
"json"
],
"coveragePathIgnorePatterns" : [
"/node_modules/" ,
"/dist/" ,
"/coverage/"
]
}
}
// Vitest with c8 Coverage (vitest.config.ts)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test : {
coverage : {
provider : 'c 8 ' ,
reporter : [ 'text' , 'json' , 'html' , 'lcov' ],
exclude : [
'node_modules/' ,
'src/setupTests.ts' ,
'** /*.d.ts',
'**/ *.config.ts' ,
'**/mockData' ,
'** /*.stories.tsx',
],
all: true,
lines: 80,
functions: 80,
branches: 80,
statements: 80,
perFile: true,
},
},
});
// GitHub Actions CI with Coverage (..github/workflows/test.yml)
name: Test Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
- name: Comment PR with coverage
uses: romeovs/lcov-reporter-action@v0.3.1
with:
lcov-file: ./coverage/lcov.info
github-token: ${{ secrets.GITHUB_TOKEN }}
// Custom Coverage Script (scripts/coverage-check.js)
const fs = require('fs');
const path = require('path');
const coverageSummary = JSON.parse(
fs.readFileSync(path.join(__dirname, '../coverage/coverage-summary.json'), 'utf8')
);
const thresholds = {
lines: 80,
statements: 80,
functions: 80,
branches: 80,
};
let failed = false;
Object.entries(thresholds).forEach(([metric, threshold]) => {
const coverage = coverageSummary.total[metric].pct;
console.log(`${metric}: ${coverage}% (threshold: ${threshold}%)`);
if (coverage < threshold) {
console.error(`❌ ${metric} coverage ${coverage}% is below threshold ${threshold}%`);
failed = true;
} else {
console.log(`✅ ${metric} coverage passed`);
}
});
if (failed) {
process.exit(1);
}
// Exclude files from coverage
/* istanbul ignore next */
function debugOnlyFunction() {
console.log('Debug info');
}
// Ignore specific lines
/* istanbul ignore next */
if (process.env.NODE_ENV === 'development') {
console.log('Dev mode');
}
// Ignore branches
/* istanbul ignore else */
if (condition) {
doSomething();
}
// Package.json scripts for coverage gates
{
"scripts" : {
"test:coverage" : "vitest run --coverage" ,
"test:coverage:check" : "npm run test:coverage && node scripts/coverage-check.js" ,
"precommit" : "npm run test:coverage:check"
}
}
12.6 Visual Regression Testing Chromatic
Tool
Method
Platform
Use Case
Chromatic
Cloud-based snapshots
Storybook integration
UI review workflow
Percy
Visual diffs
CI/CD integration
Pull request reviews
BackstopJS
Screenshot comparison
Headless browser
Local testing
Playwright Screenshots
Built-in snapshots
Test suite
E2E visual testing
Jest Image Snapshot
Image comparison
Unit tests
Component snapshots
Applitools
AI-powered diffs
Cross-browser
Enterprise testing
Example: Visual regression testing setup
// Chromatic Setup
npm install -- save - dev chromatic
// package.json
{
"scripts" : {
"chromatic" : "chromatic --project-token=<your-project-token>"
}
}
// .github/workflows/chromatic.yml
name : Chromatic
on : push
jobs :
visual - regression :
runs - on : ubuntu - latest
steps :
- uses : actions / checkout@v3
with :
fetch - depth : 0
- name : Install dependencies
run : npm ci
- name : Publish to Chromatic
uses : chromaui / action@v1
with :
projectToken : ${{ secrets. CHROMATIC_PROJECT_TOKEN }}
autoAcceptChanges : main
// Playwright Visual Regression
import { test, expect } from '@playwright/test' ;
test ( 'homepage visual regression' , async ({ page }) => {
await page. goto ( '/' );
await expect (page). toHaveScreenshot ( 'homepage.png' );
});
test ( 'button states visual regression' , async ({ page }) => {
await page. goto ( '/components/button' );
const button = page. locator ( '[data-testid="primary-button"]' );
await expect (button). toHaveScreenshot ( 'button-default.png' );
await button. hover ();
await expect (button). toHaveScreenshot ( 'button-hover.png' );
await button. focus ();
await expect (button). toHaveScreenshot ( 'button-focus.png' );
});
// Playwright Config for Visual Tests
import { defineConfig } from '@playwright/test' ;
export default defineConfig ({
expect: {
toHaveScreenshot: {
maxDiffPixels: 100 ,
threshold: 0.2 ,
},
},
use: {
screenshot: 'only-on-failure' ,
},
});
// Update screenshots command
// npx playwright test --update-snapshots
// BackstopJS Configuration (backstop.json)
{
"id" : "frontend_visual_test" ,
"viewports" : [
{
"label" : "desktop" ,
"width" : 1280 ,
"height" : 720
},
{
"label" : "tablet" ,
"width" : 768 ,
"height" : 1024
},
{
"label" : "mobile" ,
"width" : 375 ,
"height" : 667
}
],
"scenarios" : [
{
"label" : "Homepage" ,
"url" : "http://localhost:3000" ,
"referenceUrl" : "" ,
"readyEvent" : "" ,
"readySelector" : "" ,
"delay" : 500 ,
"hideSelectors" : [],
"removeSelectors" : [],
"hoverSelector" : "" ,
"clickSelector" : "" ,
"postInteractionWait" : 0 ,
"selectors" : [ "document" ],
"selectorExpansion" : true ,
"misMatchThreshold" : 0.1
},
{
"label" : "Button Hover" ,
"url" : "http://localhost:3000/components" ,
"hoverSelector" : ".btn-primary" ,
"delay" : 200
}
],
"paths" : {
"bitmaps_reference" : "backstop_data/bitmaps_reference" ,
"bitmaps_test" : "backstop_data/bitmaps_test" ,
"engine_scripts" : "backstop_data/engine_scripts" ,
"html_report" : "backstop_data/html_report" ,
"ci_report" : "backstop_data/ci_report"
},
"report" : [ "browser" , "CI" ],
"engine" : "puppeteer"
}
// BackstopJS Commands
// Reference (baseline): npx backstop reference
// Test: npx backstop test
// Approve changes: npx backstop approve
// Jest Image Snapshot
npm install -- save - dev jest - image - snapshot
// setupTests.ts
import { toMatchImageSnapshot } from 'jest-image-snapshot' ;
expect. extend ({ toMatchImageSnapshot });
// Component visual test
import { render } from '@testing-library/react' ;
import { Button } from './Button' ;
it ( 'matches visual snapshot' , () => {
const { container } = render (< Button >Click me</ Button >);
expect (container.firstChild). toMatchImageSnapshot ({
customSnapshotsDir: '__image_snapshots__' ,
customDiffDir: '__image_snapshots__/__diff_output__' ,
failureThreshold: 0.01 ,
failureThresholdType: 'percent' ,
});
});
// Percy Configuration (.percy.yml)
version : 2
static :
files : '**/*.html'
ignore - files : '**/node_modules/**'
snapshot :
widths :
- 375
- 768
- 1280
min - height : 1024
percy - css : |
.animation { animation : none ! important; }
.transition { transition : none ! important; }
// Percy with Storybook
npx percy storybook http : //localhost:6006
Frontend Testing Best Practices Summary
Unit Testing - Jest/Vitest for fast unit tests, mock dependencies, 80%+
coverage, test pure functions and business logic
Component Testing - React Testing Library with userEvent for realistic user
interactions, avoid implementation details, query by role/label
E2E Testing - Cypress for quick setup with time-travel debugging,
Playwright for multi-browser with built-in parallelization
Storybook - Develop components in isolation, document all states,
interaction testing, visual testing with Chromatic
Coverage Metrics - Track line, branch, function, statement coverage; set
thresholds per directory; integrate with CI/CD
Visual Regression - Chromatic for Storybook, Playwright screenshots, Percy
for PR reviews, catch unintended UI changes
Testing Strategy - Testing pyramid: many unit tests, some integration
tests, few E2E tests; fast feedback, reliable CI
13. Modern Styling Implementation Patterns
13.1 CSS-in-JS Styled-components Emotion
CSS-in-JS libraries enable writing CSS directly in JavaScript with dynamic styling, scoped styles, and
TypeScript support.
Library
Features
Use Case
Bundle Impact
styled-components
Tagged templates, theming, SSR, dynamic props, attrs
React apps with dynamic theming
~15KB gzipped
Emotion
css prop, styled API, composition, framework agnostic
Performance-critical apps
~7KB gzipped
Styled JSX
Next.js built-in, scoped styles, zero runtime
Next.js projects
Build-time only
Linaria
Zero-runtime, CSS extraction, static styles
Performance-first projects
0KB runtime
Example: Styled-components with dynamic theming
// theme.ts
export const theme = {
colors: {
primary: '#007acc' ,
secondary: '#6c757d' ,
danger: '#dc3545'
},
spacing : ( n : number ) => `${ n * 8 }px`
};
// Button.tsx
import styled from 'styled-components' ;
const Button = styled. button <{ variant ?: 'primary' | 'secondary' }> `
padding: ${ props => props . theme . spacing ( 2 ) };
background: ${ props => props . theme . colors [ props . variant || 'primary' ] };
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
` ;
// App.tsx
import { ThemeProvider } from 'styled-components' ;
function App () {
return (
< ThemeProvider theme = {theme}>
< Button variant = "primary" >Submit</ Button >
< Button variant = "secondary" >Cancel</ Button >
</ ThemeProvider >
);
}
Example: Emotion with css prop (faster than styled-components)
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react' ;
const buttonStyle = css `
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
transition: transform 0.2s;
&:hover {
transform: translateY(-2px);
}
` ;
function Button ({ children }) {
return < button css = {buttonStyle}>{children}</ button >;
}
// Composition
const primaryButton = css `
${ buttonStyle };
background: #007acc;
` ;
const dangerButton = css `
${ buttonStyle };
background: #dc3545;
` ;
Note: Styled-components offers better DX with DevTools and component names in DOM. Emotion is
50% smaller and faster but requires css prop setup. Linaria for zero-runtime SSG sites.
13.2 Tailwind CSS Utility-first Design
Utility-first CSS framework for rapid UI development with consistent design system and minimal custom CSS.
Category
Utilities
Example
Output CSS
Layout
flex, grid, container, box-sizing
flex items-center justify-between
display: flex; align-items: center; justify-content: space-between
Spacing
p-*, m-*, space-*, gap-*
p-4 m-2 gap-6
padding: 1rem; margin: 0.5rem; gap: 1.5rem
Typography
text-*, font-*, leading-*, tracking-*
text-xl font-bold text-gray-900
font-size: 1.25rem; font-weight: 700; color: #111827
Colors
bg-*, text-*, border-*, from-*, to-*
bg-blue-500 text-white
background: #3b82f6; color: #ffffff
Responsive
sm:*, md:*, lg:*, xl:*, 2xl:*
sm:w-full md:w-1/2 lg:w-1/3
Media query breakpoints: 640px, 768px, 1024px, 1280px, 1536px
States
hover:*, focus:*, active:*, disabled:*
hover:bg-blue-600 focus:ring-2
Pseudo-class variants
Example: Tailwind configuration and custom utilities
// tailwind.config.js
module . exports = {
content: [ './src/**/*.{js,jsx,ts,tsx}' ],
theme: {
extend: {
colors: {
brand: {
50 : '#f0f9ff' ,
500 : '#0ea5e9' ,
900 : '#0c4a6e'
}
},
spacing: {
'18' : '4.5rem' ,
'88' : '22rem'
},
fontFamily: {
sans: [ 'Inter' , 'system-ui' , 'sans-serif' ]
}
}
},
plugins: [
require ( '@tailwindcss/forms' ),
require ( '@tailwindcss/typography' ),
require ( '@tailwindcss/aspect-ratio' )
]
};
// Component usage
function Card () {
return (
< div className = "bg-white rounded-lg shadow-lg p-6 hover:shadow-xl transition-shadow" >
< h2 className = "text-2xl font-bold text-gray-900 mb-4" >Card Title</ h2 >
< p className = "text-gray-600 leading-relaxed" >Card content</ p >
< button className = "mt-4 px-6 py-2 bg-brand-500 text-white rounded-md
hover:bg-brand-600 focus:ring-2 focus:ring-brand-500
focus:ring-offset-2 disabled:opacity-50" >
Action
</ button >
</ div >
);
}
Example: Responsive design with Tailwind
// Mobile-first responsive layout
function ResponsiveGrid() {
return (
< div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" >
< div className = "bg-gray-100 p-4 rounded-lg" >Item 1</ div >
< div className = "bg-gray-100 p-4 rounded-lg" >Item 2</ div >
< div className = "bg-gray-100 p-4 rounded-lg" >Item 3</ div >
</ div >
);
}
// Dark mode support
function DarkModeButton() {
return (
< button className = "bg-white dark:bg-gray-800 text-gray-900 dark:text-white
border border-gray-300 dark:border-gray-700 px-4 py-2 rounded-md" >
Toggle
</ button >
);
}
Tailwind Best Practices: Use @apply for repeated patterns, configure PurgeCSS
for production builds (~3KB final CSS), use JIT mode for instant compilation, leverage plugins ecosystem.
13.3 CSS Variables Custom Properties
Native CSS custom properties for dynamic theming, runtime updates, and component-scoped styling without
JavaScript overhead.
Feature
Syntax
Scope
Use Case
Definition
--variable-name: value;
:root, element, inline
Define reusable values
Usage
var(--variable-name, fallback)
Any CSS property
Apply variable with fallback
Inheritance
Cascades to descendants
DOM tree
Component theming
JavaScript Access
getComputedStyle(), setProperty()
Runtime
Dynamic theme switching
Media Queries
Change values per breakpoint
Responsive
Adaptive spacing/sizing
Example: Design system with CSS variables
/* styles/variables.css */
:root {
/* Colors */
--color-primary : #007acc ;
--color-secondary : #6c757d ;
--color-success : #28a745 ;
--color-danger : #dc3545 ;
--color-warning : #ffc107 ;
/* Spacing scale */
--space-xs : 0.25 rem ;
--space-sm : 0.5 rem ;
--space-md : 1 rem ;
--space-lg : 1.5 rem ;
--space-xl : 2 rem ;
/* Typography */
--font-sans : -apple-system , BlinkMacSystemFont, 'Segoe UI' , sans-serif ;
--font-mono : 'Fira Code' , monospace ;
--font-size-sm : 0.875 rem ;
--font-size-base : 1 rem ;
--font-size-lg : 1.25 rem ;
/* Shadows */
--shadow-sm : 0 1 px 2 px rgba ( 0 , 0 , 0 , 0.05 );
--shadow-md : 0 4 px 6 px rgba ( 0 , 0 , 0 , 0.1 );
--shadow-lg : 0 10 px 15 px rgba ( 0 , 0 , 0 , 0.1 );
/* Border radius */
--radius-sm : 0.25 rem ;
--radius-md : 0.5 rem ;
--radius-lg : 1 rem ;
/* Transitions */
--transition-fast : 150 ms ease ;
--transition-base : 250 ms ease ;
}
/* Component styles */
.button {
padding : var ( --space-md ) var ( --space-lg );
background : var ( --color-primary );
color : white ;
border : none ;
border-radius : var ( --radius-md );
font-size : var ( --font-size-base );
transition : all var ( --transition-base );
box-shadow : var ( --shadow-sm );
}
.button:hover {
box-shadow : var ( --shadow-md );
transform : translateY ( -1 px );
}
.card {
padding : var ( --space-lg );
background : white ;
border-radius : var ( --radius-lg );
box-shadow : var ( --shadow-md );
}
Example: Runtime theme switching with JavaScript
// themes.ts
export const lightTheme = {
'--color-bg' : '#ffffff' ,
'--color-text' : '#1a1a1a' ,
'--color-border' : '#e5e5e5'
};
export const darkTheme = {
'--color-bg' : '#1a1a1a' ,
'--color-text' : '#ffffff' ,
'--color-border' : '#333333'
};
// ThemeProvider.tsx
function ThemeProvider ({ children }) {
const [ theme , setTheme ] = useState ( 'light' );
useEffect (() => {
const root = document.documentElement;
const themeVars = theme === 'light' ? lightTheme : darkTheme;
Object. entries (themeVars). forEach (([ key , value ]) => {
root.style. setProperty (key, value);
});
}, [theme]);
return (
< ThemeContext.Provider value = {{ theme, setTheme }}>
{children}
</ ThemeContext.Provider >
);
}
// Component usage
function ThemedCard () {
return (
< div style = {{
background: 'var(--color-bg)' ,
color: 'var(--color-text)' ,
border: '1px solid var(--color-border)'
}}>
Themed content
</ div >
);
}
Browser Support: Excellent - All modern browsers support CSS
custom properties. Use PostCSS plugins for older browser fallbacks.
13.4 Design Tokens Figma Integration
Design tokens are platform-agnostic design decisions (colors, spacing, typography) synced between design tools
and code.
Tool/Format
Purpose
Integration
Output
Figma Tokens Plugin
Extract tokens from Figma designs
JSON export, GitHub sync
tokens.json with color, spacing, typography
Style Dictionary
Transform tokens to platform formats
Build system integration
CSS, SCSS, JS, iOS, Android
Tokens Studio
Manage multi-brand token sets
Figma plugin with Git sync
Semantic + alias tokens
Theo (Salesforce)
Design token transformation
CLI, Node API
Multiple formats from single source
Example: Design tokens structure (tokens.json)
{
"color" : {
"brand" : {
"primary" : { "value" : "#007acc" , "type" : "color" },
"secondary" : { "value" : "#6c757d" , "type" : "color" }
},
"semantic" : {
"success" : { "value" : "{color.brand.primary}" , "type" : "color" },
"danger" : { "value" : "#dc3545" , "type" : "color" }
}
},
"spacing" : {
"xs" : { "value" : "4px" , "type" : "spacing" },
"sm" : { "value" : "8px" , "type" : "spacing" },
"md" : { "value" : "16px" , "type" : "spacing" },
"lg" : { "value" : "24px" , "type" : "spacing" },
"xl" : { "value" : "32px" , "type" : "spacing" }
},
"typography" : {
"fontFamily" : {
"sans" : { "value" : "Inter, system-ui, sans-serif" , "type" : "fontFamily" },
"mono" : { "value" : "Fira Code, monospace" , "type" : "fontFamily" }
},
"fontSize" : {
"sm" : { "value" : "14px" , "type" : "fontSize" },
"base" : { "value" : "16px" , "type" : "fontSize" },
"lg" : { "value" : "20px" , "type" : "fontSize" },
"xl" : { "value" : "24px" , "type" : "fontSize" }
},
"fontWeight" : {
"regular" : { "value" : "400" , "type" : "fontWeight" },
"medium" : { "value" : "500" , "type" : "fontWeight" },
"bold" : { "value" : "700" , "type" : "fontWeight" }
}
},
"borderRadius" : {
"sm" : { "value" : "4px" , "type" : "borderRadius" },
"md" : { "value" : "8px" , "type" : "borderRadius" },
"lg" : { "value" : "16px" , "type" : "borderRadius" },
"full" : { "value" : "9999px" , "type" : "borderRadius" }
}
}
Example: Style Dictionary configuration and build
// style-dictionary.config.js
module . exports = {
source: [ 'tokens/**/*.json' ],
platforms: {
css: {
transformGroup: 'css' ,
buildPath: 'src/styles/' ,
files: [{
destination: 'variables.css' ,
format: 'css/variables'
}]
},
js: {
transformGroup: 'js' ,
buildPath: 'src/tokens/' ,
files: [{
destination: 'tokens.js' ,
format: 'javascript/es6'
}]
},
typescript: {
transformGroup: 'js' ,
buildPath: 'src/tokens/' ,
files: [{
destination: 'tokens.ts' ,
format: 'typescript/es6-declarations'
}]
}
}
};
// Generated CSS output (variables.css)
:root {
-- color - brand - primary : #007acc;
-- color - brand - secondary : #6c757d;
-- spacing - xs : 4px;
-- spacing - sm : 8px;
-- spacing - md : 16px;
-- font - family - sans : Inter, system - ui, sans - serif;
-- font - size - base : 16px;
-- font - weight - bold : 700 ;
-- border - radius - md : 8px;
}
// Generated TypeScript output (tokens.ts)
export const tokens = {
color: {
brand: {
primary: '#007acc' ,
secondary: '#6c757d'
}
},
spacing: {
xs: '4px' ,
sm: '8px' ,
md: '16px'
}
};
Workflow: Design in Figma → Export tokens via plugin → Transform with Style Dictionary →
Generate CSS/JS/iOS/Android → Import in codebase. Automate with CI/CD for design-dev sync.
13.5 Dark Mode Light Mode Toggle
Implement theme switching with system preference detection, persistent storage, and smooth transitions.
Approach
Implementation
Pros
Cons
CSS Media Query
@media (prefers-color-scheme: dark)
Native, automatic, no JS
No manual toggle
Data Attribute
[data-theme="dark"] on root
Easy CSS targeting, semantic
Requires JS for toggle
Class Toggle
.dark class on root
Simple, Tailwind compatible
Flash of wrong theme (FOUT)
CSS Variables
Change variable values dynamically
Smooth transitions, runtime
More complex setup
Example: Complete dark mode implementation with persistence
// theme.css
:root {
-- color - bg : #ffffff;
-- color - text : #1a1a1a;
-- color - border : #e5e5e5;
-- color - card - bg : #f8f9fa;
}
[data - theme = "dark" ] {
-- color - bg : #1a1a1a;
-- color - text : #ffffff;
-- color - border : # 333333 ;
-- color - card - bg : #2d2d2d;
}
body {
background : var (--color-bg);
color : var (--color-text);
transition : background - color 0.3s ease, color 0.3s ease;
}
// useTheme.ts hook
import { useEffect, useState } from 'react' ;
type Theme = 'light' | 'dark' | 'system' ;
export function useTheme () {
const [ theme , setTheme ] = useState < Theme >(() => {
const stored = localStorage. getItem ( 'theme' ) as Theme ;
return stored || 'system' ;
});
useEffect (() => {
const root = document.documentElement;
const systemPreference = window. matchMedia ( '(prefers-color-scheme: dark)' );
const applyTheme = ( newTheme : Theme ) => {
let appliedTheme : 'light' | 'dark' ;
if (newTheme === 'system' ) {
appliedTheme = systemPreference.matches ? 'dark' : 'light' ;
} else {
appliedTheme = newTheme;
}
root. setAttribute ( 'data-theme' , appliedTheme);
localStorage. setItem ( 'theme' , newTheme);
};
applyTheme (theme);
// Listen for system preference changes
const handleChange = ( e : MediaQueryListEvent ) => {
if (theme === 'system' ) {
root. setAttribute ( 'data-theme' , e.matches ? 'dark' : 'light' );
}
};
systemPreference. addEventListener ( 'change' , handleChange);
return () => systemPreference. removeEventListener ( 'change' , handleChange);
}, [theme]);
return { theme, setTheme };
}
// ThemeToggle.tsx component
function ThemeToggle () {
const { theme , setTheme } = useTheme ();
return (
< div className = "theme-toggle" >
< button
onClick = {() => setTheme ( 'light' )}
aria-pressed = {theme === 'light' }
>
☀️ Light
</ button >
< button
onClick = {() => setTheme ( 'dark' )}
aria-pressed = {theme === 'dark' }
>
🌙 Dark
</ button >
< button
onClick = {() => setTheme ( 'system' )}
aria-pressed = {theme === 'system' }
>
💻 System
</ button >
</ div >
);
}
Example: Prevent flash of wrong theme (critical inline script)
<!-- Place in < head > before any CSS -->
< script >
(function() {
const theme = localStorage. getItem ( 'theme' ) || 'system' ;
const systemPreference = window. matchMedia ( '(prefers-color-scheme: dark)' ).matches;
let appliedTheme;
if (theme === 'system' ) {
appliedTheme = systemPreference ? 'dark' : 'light' ;
} else {
appliedTheme = theme;
}
document.documentElement. setAttribute ( 'data-theme' , appliedTheme);
})();
</ script >
<!-- Next.js implementation in _document.tsx -->
import { Html, Head, Main, NextScript } from 'next/document' ;
export default function Document () {
return (
< Html >
< Head />
< body >
< script dangerouslySetInnerHTML = {{
__html: `
(function() {
const theme = localStorage.getItem('theme') || 'system';
const dark = theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
})();
`
}} />
< Main />
< NextScript />
</ body >
</ Html >
);
}
Performance: Avoid transition: all on theme toggle (causes layout thrashing). Target specific
properties: background-color, color, border-color. Use prefers-reduced-motion for accessibility.
13.6 Component Library MUI Chakra UI
Production-ready component libraries with theming, accessibility, and extensive component catalogs.
Library
Key Features
Best For
Bundle Size
Material-UI (MUI)
Material Design, comprehensive components, theming system
Enterprise apps, admin panels
~80KB core + components
Chakra UI
Accessible, composable, dark mode, styled-system
Modern apps, fast development
~45KB + components
Ant Design
Enterprise UI, rich components, internationalization
Data-heavy dashboards
~60KB core + components
Mantine
Full-featured, hooks library, 120+ components
Complete solution, TypeScript
~50KB + components
Radix UI
Unstyled, accessible primitives, headless
Custom designs, full control
Minimal, per-component
shadcn/ui
Copy-paste components, Radix + Tailwind
Full customization, no dependencies
Only what you use
Example: Material-UI (MUI) with custom theme
// theme.ts
import { createTheme } from '@mui/material/styles' ;
export const theme = createTheme ({
palette: {
primary: {
main: '#007acc' ,
light: '#42a5f5' ,
dark: '#005fa3'
},
secondary: {
main: '#6c757d'
},
mode: 'light' // or 'dark'
},
typography: {
fontFamily: 'Inter, system-ui, sans-serif' ,
h1: {
fontSize: '2.5rem' ,
fontWeight: 700
}
},
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8 ,
textTransform: 'none' ,
boxShadow: 'none'
}
},
defaultProps: {
disableElevation: true
}
}
}
});
// App.tsx
import { ThemeProvider } from '@mui/material/styles' ;
import { Button, TextField, Card, CardContent } from '@mui/material' ;
function App () {
return (
< ThemeProvider theme = {theme}>
< Card >
< CardContent >
< TextField
label = "Email"
variant = "outlined"
fullWidth
margin = "normal"
/>
< Button variant = "contained" color = "primary" >
Submit
</ Button >
</ CardContent >
</ Card >
</ ThemeProvider >
);
}
Example: Chakra UI with composition and dark mode
// App.tsx
import { ChakraProvider, extendTheme, Box, Button, Input, Stack } from '@chakra-ui/react' ;
const theme = extendTheme ({
colors: {
brand: {
50 : '#e3f2fd' ,
500 : '#007acc' ,
900 : '#003d66'
}
},
config: {
initialColorMode: 'light' ,
useSystemColorMode: true
}
});
function App () {
return (
< ChakraProvider theme = {theme}>
< Box p = { 8 } bg = "gray.50" minH = "100vh" >
< Stack spacing = { 4 } maxW = "md" >
< Input placeholder = "Email" size = "lg" />
< Button
colorScheme = "brand"
size = "lg"
_hover = {{ transform: 'translateY(-2px)' , boxShadow: 'lg' }}
>
Submit
</ Button >
</ Stack >
</ Box >
</ ChakraProvider >
);
}
// ColorModeToggle.tsx
import { useColorMode, IconButton } from '@chakra-ui/react' ;
import { SunIcon, MoonIcon } from '@chakra-ui/icons' ;
function ColorModeToggle () {
const { colorMode , toggleColorMode } = useColorMode ();
return (
< IconButton
aria-label = "Toggle color mode"
icon = {colorMode === 'light' ? < MoonIcon /> : < SunIcon />}
onClick = {toggleColorMode}
/>
);
}
Example: shadcn/ui with Radix primitives (copy-paste approach)
// components/ui/button.tsx (copied from shadcn/ui)
import * as React from "react" ;
import { Slot } from "@radix-ui/react-slot" ;
import { cva, type VariantProps } from "class-variance-authority" ;
import { cn } from "@/lib/utils" ;
const buttonVariants = cva (
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors" ,
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90" ,
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90" ,
outline: "border border-input hover:bg-accent hover:text-accent-foreground" ,
ghost: "hover:bg-accent hover:text-accent-foreground"
},
size: {
default: "h-10 px-4 py-2" ,
sm: "h-9 rounded-md px-3" ,
lg: "h-11 rounded-md px-8"
}
},
defaultVariants: {
variant: "default" ,
size: "default"
}
}
);
export interface ButtonProps
extends React . ButtonHTMLAttributes < HTMLButtonElement >,
VariantProps < typeof buttonVariants> {
asChild ?: boolean ;
}
const Button = React. forwardRef < HTMLButtonElement , ButtonProps >(
({ className , variant , size , asChild = false , ... props }, ref ) => {
const Comp = asChild ? Slot : "button" ;
return (
< Comp
className = { cn ( buttonVariants ({ variant , size , className }))}
ref = {ref}
{ ... props }
/>
);
}
);
// Usage
import { Button } from "@/components/ui/button" ;
function MyComponent () {
return (
<>
< Button variant = "default" > Default </ Button >
< Button variant = "outline" > Outline </ Button >
< Button variant = "ghost" size = "sm" > Small Ghost </ Button >
</>
);
}
MUI Pros: Material Design consistency, massive component library (100+), strong TypeScript
support, excellent documentation, MUI X for advanced components (DataGrid, Date Pickers).
Chakra UI Pros: Excellent DX with style props, built-in dark mode, smaller bundle, composable
design, fast setup. Great for startups and modern apps.
Styling Strategy Decision Matrix
Use Case
Recommended Approach
Rationale
Rapid prototyping
Tailwind CSS + shadcn/ui
Fast development, no custom CSS, full control
Enterprise dashboard
MUI or Ant Design
Rich components, data tables, consistency
Design system from scratch
Radix UI + CSS Variables + Tokens
Full customization, brand consistency
Performance-critical
Tailwind CSS + Linaria
Zero-runtime CSS, optimal bundle size
Dynamic theming
CSS Variables + Chakra UI
Runtime theme switching, easy dark mode
14. Internationalization Implementation Stack
14.1 React-intl i18next Setup Configuration
Comprehensive i18n solutions for React apps with translation management, formatting, and pluralization support.
Library
Core Features
Best For
Bundle Size
react-intl (FormatJS)
ICU message format, date/number formatting, React hooks
Enterprise apps, complex formatting
~45KB
react-i18next
Plugin ecosystem, lazy loading, backend integration
Large projects, dynamic translations
~30KB + i18next core
next-intl
Next.js optimized, SSR/SSG support, type-safe
Next.js apps
~15KB
LinguiJS
CLI extraction, compile-time optimization, minimal runtime
Performance-critical apps
~5KB runtime
Example: react-i18next setup with TypeScript
// i18n.ts - Configuration
import i18n from 'i18next' ;
import { initReactI18next } from 'react-i18next' ;
import LanguageDetector from 'i18next-browser-languagedetector' ;
import Backend from 'i18next-http-backend' ;
i18n
. use (Backend) // Load translations from backend
. use (LanguageDetector) // Detect user language
. use (initReactI18next) // Pass i18n to react-i18next
. init ({
fallbackLng: 'en' ,
debug: process.env. NODE_ENV === 'development' ,
interpolation: {
escapeValue: false // React already escapes
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
},
ns: [ 'common' , 'auth' , 'dashboard' ],
defaultNS: 'common' ,
react: {
useSuspense: true
}
});
export default i18n;
// locales/en/common.json
{
"welcome" : "Welcome, {{name}}!" ,
"itemCount" : "You have {{count}} item" ,
"itemCount_plural" : "You have {{count}} items" ,
"updated" : "Last updated: {{date, datetime}}"
}
// locales/es/common.json
{
"welcome" : "¡Bienvenido, {{name}}!" ,
"itemCount" : "Tienes {{count}} artículo" ,
"itemCount_plural" : "Tienes {{count}} artículos" ,
"updated" : "Última actualización: {{date, datetime}}"
}
// App.tsx
import './i18n' ;
import { Suspense } from 'react' ;
function App () {
return (
< Suspense fallback = "Loading..." >
< MainApp />
</ Suspense >
);
}
// Component usage
import { useTranslation } from 'react-i18next' ;
function Dashboard () {
const { t , i18n } = useTranslation ( 'common' );
return (
< div >
< h1 >{ t ( 'welcome' , { name : 'John' })} </ h1 >
< p >{ t ( 'itemCount' , { count : 5 })} </ p >
< p >{ t ( 'updated' , { date : new Date () })} </ p >
< button onClick = {() => i18n.changeLanguage( 'es' )}>
Español
</button>
<button onClick={() => i18n.changeLanguage( 'en' )}>
English
</button>
</div>
);
}
// App.tsx
import { IntlProvider } from 'react-intl' ;
import { useState } from 'react' ;
import enMessages from './locales/en.json' ;
import esMessages from './locales/es.json' ;
const messages = {
en: enMessages,
es: esMessages
};
function App () {
const [ locale , setLocale ] = useState ( 'en' );
return (
< IntlProvider
messages = {messages[locale]}
locale = {locale}
defaultLocale = "en"
>
< Dashboard onLocaleChange = {setLocale} />
</ IntlProvider >
);
}
// Component with react-intl hooks
import {
useIntl,
FormattedMessage,
FormattedNumber,
FormattedDate
} from 'react-intl' ;
function Dashboard ({ onLocaleChange }) {
const intl = useIntl ();
return (
< div >
< h1 >
< FormattedMessage
id = "welcome"
defaultMessage = "Welcome, {name}!"
values = {{ name: 'John' }}
/>
</ h1 >
< p >
< FormattedNumber
value = { 1234.56 }
style = "currency"
currency = "USD"
/>
</ p >
< p >
< FormattedDate
value = { new Date ()}
year = "numeric"
month = "long"
day = "numeric"
/>
</ p >
{ /* Imperative usage */ }
< input
placeholder = {intl. formatMessage ({
id: 'search.placeholder' ,
defaultMessage: 'Search...'
})}
/>
</ div >
);
}
Comparison: Use react-i18next for flexibility and ecosystem.
Use react-intl for standardized ICU formatting. Use LinguiJS
for smallest bundle and compile-time extraction.
14.2 Dynamic Locale Loading Lazy i18n
Load translation files on-demand to reduce initial bundle size and improve performance for multi-language apps.
Strategy
Implementation
Benefits
Trade-offs
Code Splitting
Dynamic import() per locale
Smaller initial bundle
Network request on language change
Namespace Splitting
Split by feature/page
Load only needed translations
More HTTP requests
Backend Loading
Fetch from API/CDN
No rebuild for translation updates
Runtime dependency
Preloading
Prefetch likely locales
Instant switching
Additional bandwidth usage
Example: Dynamic locale loading with react-i18next
// i18n.ts - Lazy loading configuration
import i18n from 'i18next' ;
import { initReactI18next } from 'react-i18next' ;
import LanguageDetector from 'i18next-browser-languagedetector' ;
// Custom backend for dynamic imports
const loadResources = async ( lng : string , ns : string ) => {
try {
const resources = await import ( `./locales/${ lng }/${ ns }.json` );
return resources.default;
} catch (error) {
console. error ( `Failed to load ${ lng }/${ ns }` , error);
return {};
}
};
i18n
. use (LanguageDetector)
. use (initReactI18next)
. use ({
type: 'backend' ,
read ( language , namespace , callback ) {
loadResources (language, namespace)
. then ( resources => callback ( null , resources))
. catch ( error => callback (error, null ));
}
})
. init ({
fallbackLng: 'en' ,
ns: [ 'common' , 'auth' , 'dashboard' , 'settings' ],
defaultNS: 'common' ,
react: {
useSuspense: true
},
// Preload common namespaces
preload: [ 'en' ],
load: 'languageOnly' // 'en-US' -> 'en'
});
export default i18n;
// Hook for namespace loading
import { useTranslation } from 'react-i18next' ;
import { useEffect } from 'react' ;
function useLazyTranslation ( namespace : string ) {
const { t , i18n , ready } = useTranslation (namespace, { useSuspense: false });
useEffect (() => {
if ( ! i18n. hasResourceBundle (i18n.language, namespace)) {
i18n. loadNamespaces (namespace);
}
}, [i18n, namespace]);
return { t, ready };
}
// Component usage
function SettingsPage () {
const { t , ready } = useLazyTranslation ( 'settings' );
if ( ! ready) return < div >Loading translations...</ div >
return (
< div >
< h1 >{ t ( 'title' )}</ h1 >
< p >{ t ( 'description' )}</ p >
</ div >
);
}
// loadLocale.ts
export async function loadLocale ( locale : string ) {
// Webpack magic comments for chunk naming
const messages = await import (
/* webpackChunkName: "locale-[request]" */
/* webpackMode: "lazy" */
`./locales/${ locale }/messages.json`
);
return messages.default;
}
// Vite dynamic import
export async function loadLocaleVite ( locale : string ) {
const modules = import . meta . glob ( './locales/*/messages.json' );
const path = `./locales/${ locale }/messages.json` ;
if (modules[path]) {
const messages = await modules[path]();
return messages.default;
}
throw new Error ( `Locale ${ locale } not found` );
}
// App.tsx - Progressive enhancement
import { Suspense, lazy } from 'react' ;
const LocaleProvider = lazy (() =>
import ( /* webpackChunkName: "i18n-provider" */ './LocaleProvider' )
);
function App () {
return (
< Suspense fallback = {< LoadingSpinner />}>
< LocaleProvider >
< Routes />
</ LocaleProvider >
</ Suspense >
);
}
Example: Preloading strategy for better UX
// useLocalePreload.ts
import { useEffect } from 'react' ;
import { useTranslation } from 'react-i18next' ;
const COMMON_LOCALES = [ 'en' , 'es' , 'fr' , 'de' ];
export function useLocalePreload () {
const { i18n } = useTranslation ();
useEffect (() => {
// Preload on idle
if ( 'requestIdleCallback' in window) {
requestIdleCallback (() => {
COMMON_LOCALES . forEach ( locale => {
if (locale !== i18n.language) {
// Prefetch but don't block
import ( `./locales/${ locale }/common.json` ). catch (() => {});
}
});
});
}
}, [i18n.language]);
}
// Link preload in HTML head
function injectPreloadLinks ( locales : string []) {
locales. forEach ( locale => {
const link = document. createElement ( 'link' );
link.rel = 'prefetch' ;
link.as = 'fetch' ;
link.href = `/locales/${ locale }/common.json` ;
link.crossOrigin = 'anonymous' ;
document.head. appendChild (link);
});
}
// Next.js implementation
import Head from 'next/head' ;
function LocalePreload ({ locales } : { locales : string [] }) {
return (
< Head >
{locales. map ( locale => (
< link
key = {locale}
rel = "prefetch"
as = "fetch"
href = { `/locales/${ locale }/common.json` }
crossOrigin = "anonymous"
/>
))}
</ Head >
);
}
Best Practice: Load common namespace eagerly, lazy-load feature-specific
namespaces. Preload user's preferred alternate languages during idle time. Cache translations in localStorage.
Handle complex pluralization rules across different languages using ICU MessageFormat standard.
Feature
Syntax
Use Case
Example
Simple Plural
{count, plural, one{#} other{#}}
Item counts
1 item / 5 items
Select
{gender, select, male{} female{}}
Gender-based text
He/She variations
SelectOrdinal
{num, selectordinal, one{#st}}
Ordinal numbers
1st, 2nd, 3rd
Nested
Combine plural + select
Complex messages
Gender + count variations
// English pluralization (2 forms)
{
"itemCount" : "{count, plural, one {# item} other {# items}}"
}
// Usage: 0 items, 1 item, 2 items, 100 items
// Polish pluralization (3 forms)
{
"itemCount" : "{count, plural, one {# przedmiot} few {# przedmioty} many {# przedmiotów} other {# przedmiotu}}"
}
// Arabic pluralization (6 forms!)
{
"itemCount" : "{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}}"
}
// Complex example with select + plural
{
"taskStatus" : "{taskCount, plural, =0 {No tasks} one {# task} other {# tasks}} {status, select, pending {pending} completed {completed} failed {failed} other {unknown}}"
}
// Nested gender + plural
{
"friendRequest" : "{gender, select, male {He has} female {She has} other {They have}} {count, plural, one {# friend request} other {# friend requests}}"
}
import { FormattedMessage, useIntl } from 'react-intl' ;
// Translation file (en.json)
{
"cart.items" : "You have {itemCount, plural, =0 {no items} one {# item} other {# items}} in your cart" ,
"user.greeting" : "{name} {gender, select, male {is online. Say hi to him!} female {is online. Say hi to her!} other {is online. Say hi!}}" ,
"finish.position" : "You finished in {position, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place"
}
// Component usage
function ShoppingCart ({ itemCount } : { itemCount : number }) {
return (
< div >
< FormattedMessage
id = "cart.items"
values = {{ itemCount }}
/>
</ div >
);
}
function UserStatus ({ name , gender } : { name : string ; gender : 'male' | 'female' | 'other' }) {
return (
< FormattedMessage
id = "user.greeting"
values = {{ name, gender }}
/>
);
}
function RaceResult ({ position } : { position : number }) {
const intl = useIntl ();
return (
< div >
{intl. formatMessage (
{ id: 'finish.position' },
{ position }
)}
</ div >
);
}
Example: i18next with ICU plugin
// i18n.ts - Enable ICU format
import i18n from 'i18next' ;
import ICU from 'i18next-icu' ;
import { initReactI18next } from 'react-i18next' ;
i18n
. use ( ICU ) // Add ICU plugin
. use (initReactI18next)
. init ({
fallbackLng: 'en' ,
resources: {
en: {
translation: {
"notifications" : "You have {count, plural, =0 {no notifications} one {# notification} other {# notifications}}" ,
"fileSize" : "{size, number} {unit, select, KB {kilobytes} MB {megabytes} GB {gigabytes} other {bytes}}"
}
}
}
});
// Component usage
import { useTranslation } from 'react-i18next' ;
function Notifications ({ count } : { count : number }) {
const { t } = useTranslation ();
return < div >{ t ( 'notifications' , { count })}</ div >;
}
function FileInfo ({ size , unit } : { size : number ; unit : string }) {
const { t } = useTranslation ();
return < div >{ t ( 'fileSize' , { size, unit })}</ div >;
}
Performance Note: ICU MessageFormat parsing has runtime cost. Use compile-time extraction
(LinguiJS) for production apps or cache parsed messages. Simple plural/other is sufficient for many use cases.
14.4 RTL LTR CSS Logical Properties
Support right-to-left (RTL) languages like Arabic and Hebrew with CSS logical properties for automatic layout
mirroring.
Physical Property
Logical Property
LTR Value
RTL Value
margin-left
margin-inline-start
Left margin
Right margin
margin-right
margin-inline-end
Right margin
Left margin
padding-left
padding-inline-start
Left padding
Right padding
border-left
border-inline-start
Left border
Right border
text-align: left
text-align: start
Align left
Align right
float: left
float: inline-start
Float left
Float right
Example: CSS logical properties for RTL support
/* Traditional approach (manual RTL) */
.card {
margin-left : 16 px ;
padding-right : 24 px ;
border-left : 2 px solid blue ;
}
[ dir = "rtl" ] .card {
margin-left : 0 ;
margin-right : 16 px ;
padding-right : 0 ;
padding-left : 24 px ;
border-left : none ;
border-right : 2 px solid blue ;
}
/* Modern approach (automatic RTL) */
.card {
margin-inline-start : 16 px ;
padding-inline-end : 24 px ;
border-inline-start : 2 px solid blue ;
}
/* No RTL override needed! */
/* Complete example */
.sidebar {
/* Inline = horizontal (left/right) */
padding-inline-start : 20 px ;
padding-inline-end : 10 px ;
margin-inline : 8 px ; /* shorthand for start + end */
/* Block = vertical (top/bottom) - no change in RTL */
padding-block-start : 16 px ;
padding-block-end : 16 px ;
margin-block : 12 px ;
/* Positioning */
inset-inline-start : 0 ; /* left in LTR, right in RTL */
/* Text alignment */
text-align : start ; /* left in LTR, right in RTL */
}
.icon {
margin-inline-end : 8 px ; /* Space after icon */
}
.arrow {
/* Transform for RTL */
transform : scaleX ( 1 );
}
[ dir = "rtl" ] .arrow {
transform : scaleX ( -1 ); /* Flip horizontally */
}
Example: React RTL implementation with context
// DirectionProvider.tsx
import { createContext, useContext, useEffect } from 'react' ;
type Direction = 'ltr' | 'rtl' ;
const DirectionContext = createContext < Direction >( 'ltr' );
export function DirectionProvider ({
children ,
direction
} : {
children : React . ReactNode ;
direction : Direction
}) {
useEffect (() => {
document.documentElement. setAttribute ( 'dir' , direction);
document.documentElement. setAttribute ( 'lang' , direction === 'rtl' ? 'ar' : 'en' );
}, [direction]);
return (
< DirectionContext.Provider value = {direction}>
{children}
</ DirectionContext.Provider >
);
}
export const useDirection = () => useContext (DirectionContext);
// App.tsx
import { useTranslation } from 'react-i18next' ;
const RTL_LANGUAGES = [ 'ar' , 'he' , 'fa' , 'ur' ];
function App () {
const { i18n } = useTranslation ();
const direction = RTL_LANGUAGES . includes (i18n.language) ? 'rtl' : 'ltr' ;
return (
< DirectionProvider direction = {direction}>
< MainApp />
</ DirectionProvider >
);
}
// Component with direction-aware styles
import styled from 'styled-components' ;
const Card = styled. div `
padding-inline-start: 20px;
padding-inline-end: 10px;
border-inline-start: 3px solid var(--primary-color);
.icon {
margin-inline-end: 8px;
}
` ;
function MyCard () {
const direction = useDirection ();
return (
< Card >
< span className = "icon" >→</ span >
{direction === 'rtl' ? 'النص العربي' : 'English text' }
</ Card >
);
}
Example: Tailwind CSS with RTL plugin
// tailwind.config.js
module . exports = {
plugins: [ require ( 'tailwindcss-rtl' )],
};
// Component with RTL-aware utilities
function Navbar () {
return (
< nav className = "flex items-center" >
< img
src = "/logo.png"
className = "ms-4 me-2" // ms = margin-inline-start, me = margin-inline-end
alt = "Logo"
/>
< ul className = "flex gap-4" >
< li className = "ps-4" >Home</ li > { /* ps = padding-inline-start */ }
< li className = "ps-4" >About</ li >
</ ul >
< button className = "ms-auto" >Login</ button > { /* Push to end */ }
</ nav >
);
}
// Custom RTL utilities
< div className = "ltr:text-left rtl:text-right" >
Directional text
</ div >
Browser Support: Excellent - All modern browsers support
CSS logical properties. Use dir="rtl" on HTML element. Test with Arabic/Hebrew content thoroughly.
Format dates, times, and numbers according to user's locale and timezone with proper internationalization
support.
Library
Features
Use Case
Bundle Size
date-fns
Modular, tree-shakeable, 80+ locales, immutable
Modern apps, small bundles
~2KB per function
date-fns-tz
Timezone support addon for date-fns
Multi-timezone apps
~10KB + date-fns
Luxon
Modern API, Intl wrapper, timezone native
Complex date logic
~70KB
Day.js
Moment.js alternative, plugins, small
Simple date needs
~7KB
Intl API (Native)
Browser built-in, no dependencies
Basic formatting
0KB
import { format, formatDistance, formatRelative } from 'date-fns' ;
import { enUS, es, ar, ja, de } from 'date-fns/locale' ;
// Locale mapping
const locales = { en: enUS, es, ar, ja, de };
function formatDate ( date : Date , locale : string ) {
return format (date, 'PPpp' , { locale: locales[locale] });
}
// Usage examples
const date = new Date ( 2025 , 0 , 15 , 14 , 30 );
// English: "Jan 15, 2025, 2:30 PM"
format (date, 'PPpp' , { locale: enUS });
// Spanish: "15 ene 2025, 14:30"
format (date, 'PPpp' , { locale: es });
// Arabic: "١٥ يناير ٢٠٢٥، ١٤:٣٠"
format (date, 'PPpp' , { locale: ar });
// Relative time
formatDistance (date, new Date (), {
addSuffix: true ,
locale: es
}); // "hace 3 días"
// Custom formats
format (date, 'EEEE, MMMM do yyyy' , { locale: de });
// "Mittwoch, Januar 15. 2025"
// React hook for localized dates
import { useTranslation } from 'react-i18next' ;
function useLocalizedDate () {
const { i18n } = useTranslation ();
const locale = locales[i18n.language] || enUS;
const formatLocalizedDate = ( date : Date , formatStr : string ) => {
return format (date, formatStr, { locale });
};
const formatRelativeTime = ( date : Date ) => {
return formatDistance (date, new Date (), {
addSuffix: true ,
locale
});
};
return { formatLocalizedDate, formatRelativeTime };
}
Example: Timezone handling with date-fns-tz
import { format } from 'date-fns' ;
import { formatInTimeZone, toZonedTime, fromZonedTime } from 'date-fns-tz' ;
import { enUS } from 'date-fns/locale' ;
// Display date in user's timezone
function formatUserTimezone ( date : Date , userTimezone : string ) {
return formatInTimeZone (
date,
userTimezone,
'yyyy-MM-dd HH:mm:ss zzz' ,
{ locale: enUS }
);
}
// Examples
const utcDate = new Date ( '2025-01-15T14:30:00Z' );
formatUserTimezone (utcDate, 'America/New_York' );
// "2025-01-15 09:30:00 EST"
formatUserTimezone (utcDate, 'Asia/Tokyo' );
// "2025-01-15 23:30:00 JST"
formatUserTimezone (utcDate, 'Europe/London' );
// "2025-01-15 14:30:00 GMT"
// Convert between timezones
const tokyoTime = toZonedTime (utcDate, 'Asia/Tokyo' );
const nyTime = fromZonedTime (tokyoTime, 'America/New_York' );
// React component with timezone display
function EventTime ({ eventDate , timezone } : {
eventDate : Date ;
timezone : string ;
}) {
const userTimezone = Intl. DateTimeFormat (). resolvedOptions ().timeZone;
return (
< div >
< p >Event time: { formatInTimeZone (eventDate, timezone, 'PPpp' )}</ p >
< p >Your time: { formatInTimeZone (eventDate, userTimezone, 'PPpp' )}</ p >
</ div >
);
}
// Date formatting with Intl.DateTimeFormat
const date = new Date ( '2025-01-15T14:30:00' );
// English (US)
new Intl. DateTimeFormat ( 'en-US' , {
dateStyle: 'full' ,
timeStyle: 'short'
}). format (date);
// "Wednesday, January 15, 2025 at 2:30 PM"
// Spanish (Spain)
new Intl. DateTimeFormat ( 'es-ES' , {
dateStyle: 'full' ,
timeStyle: 'short'
}). format (date);
// "miércoles, 15 de enero de 2025, 14:30"
// Number formatting with Intl.NumberFormat
const number = 1234567.89 ;
// Currency
new Intl. NumberFormat ( 'en-US' , {
style: 'currency' ,
currency: 'USD'
}). format (number);
// "$1,234,567.89"
new Intl. NumberFormat ( 'ja-JP' , {
style: 'currency' ,
currency: 'JPY'
}). format (number);
// "¥1,234,568"
// Percentage
new Intl. NumberFormat ( 'en-US' , {
style: 'percent' ,
minimumFractionDigits: 2
}). format ( 0.1234 );
// "12.34%"
// Relative time with Intl.RelativeTimeFormat
const rtf = new Intl. RelativeTimeFormat ( 'en' , { numeric: 'auto' });
rtf. format ( - 1 , 'day' ); // "yesterday"
rtf. format ( 2 , 'week' ); // "in 2 weeks"
const rtfEs = new Intl. RelativeTimeFormat ( 'es' , { numeric: 'auto' });
rtfEs. format ( - 1 , 'day' ); // "ayer"
rtfEs. format ( 2 , 'week' ); // "dentro de 2 semanas"
// React hook for Intl formatting
function useIntlFormatting ( locale : string ) {
const dateFormatter = new Intl. DateTimeFormat (locale, {
dateStyle: 'medium' ,
timeStyle: 'short'
});
const currencyFormatter = new Intl. NumberFormat (locale, {
style: 'currency' ,
currency: 'USD'
});
const relativeTimeFormatter = new Intl. RelativeTimeFormat (locale, {
numeric: 'auto'
});
return {
formatDate : ( date : Date ) => dateFormatter. format (date),
formatCurrency : ( amount : number ) => currencyFormatter. format (amount),
formatRelativeTime : ( value : number , unit : Intl . RelativeTimeFormatUnit ) =>
relativeTimeFormatter. format (value, unit)
};
}
Timezone Pitfall: Always store dates in UTC on backend. Convert to user's timezone only for
display. Use Date.prototype.toISOString() for API communication. Avoid
new Date(string)
parsing across timezones.
14.6 Translation Keys TypeScript Validation
Enforce type safety for translation keys to catch missing translations at compile-time instead of runtime.
Approach
Tool
Benefits
Setup Complexity
Type Generation
i18next + typesafe-i18n
Auto-complete, type errors for invalid keys
Medium
CLI Extraction
LinguiJS, react-intl CLI
Extract keys from code, detect unused
High
Const Assertion
TypeScript as const
Simple, no build step
Low
Schema Validation
Zod, Yup
Runtime + compile validation
Medium
Example: TypeScript with i18next typed translations
// locales/en/translation.json
{
"common" : {
"welcome" : "Welcome" ,
"logout" : "Logout"
},
"auth" : {
"login" : {
"title" : "Sign In" ,
"email" : "Email Address" ,
"password" : "Password"
}
},
"errors" : {
"required" : "This field is required" ,
"invalid_email" : "Invalid email format"
}
}
// i18next.d.ts - Type augmentation
import 'i18next' ;
import type translation from './locales/en/translation.json' ;
declare module 'i18next' {
interface CustomTypeOptions {
defaultNS : 'translation' ;
resources : {
translation : typeof translation;
};
}
}
// Now TypeScript knows all translation keys!
import { useTranslation } from 'react-i18next' ;
function MyComponent () {
const { t } = useTranslation ();
// ✅ Valid - autocomplete works!
t ( 'common.welcome' );
t ( 'auth.login.title' );
// ❌ TypeScript error - key doesn't exist
t ( 'common.invalid' ); // Error: Property 'invalid' does not exist
// ✅ Nested object access with type safety
t ( 'auth.login.email' );
return < div >{ t ( 'common.welcome' )} </ div > ;
}
Example: Generate types from translation files
// scripts/generate-i18n-types.ts
import fs from 'fs' ;
import path from 'path' ;
type TranslationKeys < T , Prefix extends string = '' > = {
[ K in keyof T ] : T [ K ] extends object
? TranslationKeys < T [ K ], `${ Prefix }${ K & string }.` >
: `${ Prefix }${ K & string }` ;
}[ keyof T ];
function generateTypes () {
const enTranslations = JSON . parse (
fs. readFileSync ( './locales/en/translation.json' , 'utf-8' )
);
const types = `
// Auto-generated - do not edit
import type en from './locales/en/translation.json';
export type TranslationKey = TranslationKeys<typeof en>;
export type TranslationKeys<T, Prefix extends string = ''> = {
[K in keyof T]: T[K] extends object
? TranslationKeys<T[K], \`\$ {Prefix} \$ {K & string}. \` >
: \`\$ {Prefix} \$ {K & string} \` ;
}[keyof T];
` ;
fs. writeFileSync ( './src/types/i18n.ts' , types);
}
generateTypes ();
// Usage with custom hook
import type { TranslationKey } from './types/i18n' ;
import { useTranslation as useI18next } from 'react-i18next' ;
export function useTranslation () {
const { t , ... rest } = useI18next ();
const typedT = ( key : TranslationKey , options ?: any ) => {
return t (key, options);
};
return { t: typedT, ... rest };
}
Example: typesafe-i18n library (zero-dependency, full type safety)
// Installation: npm install typesafe-i18n
// locales/en.json
{
"HI" : "Hi {name:string}!" ,
"ITEMS" : "You have {count:number} {count:plural(item|items)}" ,
"PRICE" : "Price: {amount:number|currency(USD)}"
}
// Generated types (automatic)
type Translation = {
HI : ( params : { name : string }) => string ;
ITEMS : ( params : { count : number }) => string ;
PRICE : ( params : { amount : number }) => string ;
}
// Usage
import { useI18nContext } from './i18nContext' ;
function MyComponent () {
const { LL } = useI18nContext (); // LL = Localized Language
// ✅ Type-safe with parameter validation
LL . HI ({ name: 'John' }); // "Hi John!"
// ❌ TypeScript error - missing required parameter
LL . HI ({ }); // Error: Property 'name' is missing
// ❌ TypeScript error - wrong type
LL . HI ({ name: 123 }); // Error: Type 'number' is not assignable to 'string'
// ✅ Pluralization
LL . ITEMS ({ count: 1 }); // "You have 1 item"
LL . ITEMS ({ count: 5 }); // "You have 5 items"
// ✅ Formatted values
LL . PRICE ({ amount: 99.99 }); // "Price: $99.99"
return < div >{LL.HI({ name : 'World' })} </ div > ;
}
Example: Validation in CI/CD pipeline
// scripts/validate-translations.ts
import fs from 'fs';
import path from 'path';
interface ValidationResult {
valid : boolean;
errors : string[];
}
function getTranslationKeys(obj : any, prefix = '') : string[] {
return Object.keys(obj).flatMap(key => {
const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
return getTranslationKeys(value, fullKey);
}
return [fullKey];
} );
}
function validateTranslations() : ValidationResult {
const errors : string[] = [];
const localesDir = path.join(__dirname, '../locales');
const locales = fs.readdirSync(localesDir);
// Load base locale (English)
const baseLocale = 'en';
const baseTranslations = JSON.parse(
fs.readFileSync(path.join(localesDir, baseLocale, 'translation.json'), 'utf-8')
);
const baseKeys = new Set(getTranslationKeys(baseTranslations));
// Check each locale
locales.forEach(locale => {
if (locale === baseLocale) return;
const translations = JSON.parse(
fs.readFileSync(path.join(localesDir, locale, 'translation.json'), 'utf-8')
);
const keys = new Set(getTranslationKeys(translations));
// Check for missing keys
baseKeys.forEach(key => {
if (!keys.has(key)) {
errors.push(`[${locale}] Missing translation key : ${key}`);
}
} );
// Check for extra keys
keys.forEach(key => {
if (!baseKeys.has(key)) {
errors.push(`[${locale}] Extra translation key : ${key}`);
}
} );
} );
return {
valid : errors.length === 0,
errors
} ;
}
// Run validation
const result = validateTranslations();
if (!result.valid) {
console.error('Translation validation failed:');
result.errors.forEach(error => console.error(` - ${error}`));
process.exit(1);
}
console.log('✅ All translations are valid!');
// package.json script
{
"scripts" : {
"validate:i18n" : "ts-node scripts/validate-translations.ts" ,
"precommit" : "npm run validate:i18n && npm run type-check"
}
}
15. Modern Project Structure Implementation
15.1 Monorepo Nx Lerna Workspace Setup
Manage multiple related packages in a single repository with shared dependencies, unified tooling, and
efficient builds.
Tool
Key Features
Best For
Learning Curve
Nx
Smart rebuilds, computation caching, code generators, dependency graph
Large teams, enterprise apps
Medium-High
Turborepo
Fast builds, remote caching, simple config, Vercel integration
Modern apps, Vercel users
Low-Medium
Lerna
Package versioning, publishing, bootstrap, legacy support
Library authors, npm packages
Medium
npm/yarn/pnpm workspaces
Built-in, simple, no extra tools
Small-medium projects
Low
Example: Nx monorepo setup with React and shared libraries
// Initialize Nx workspace
npx create-nx-workspace@latest myorg
// Structure
myorg/
├── apps/
│ ├── web/ # Next.js app
│ ├── mobile/ # React Native app
│ └── admin/ # Admin dashboard
├── libs/
│ ├── shared/
│ │ ├── ui/ # Shared UI components
│ │ ├── utils/ # Utility functions
│ │ └── types/ # TypeScript types
│ ├── features/
│ │ ├── auth/ # Authentication feature
│ │ └── payments/ # Payment feature
│ └── data-access/
│ └── api/ # API client
├── tools/ # Custom scripts
├── nx.json # Nx configuration
├── tsconfig.base.json # Base TypeScript config
└── package.json
// nx.json - Configuration
{
"tasksRunnerOptions" : {
"default" : {
"runner" : "nx/tasks-runners/default",
"options" : {
"cacheableOperations" : [ "build" , "test", "lint"],
"parallel" : 3
}
}
},
"targetDefaults" : {
"build" : {
"dependsOn" : [ "^build" ],
"cache" : true
}
}
}
// tsconfig.base.json - Path mapping
{
"compilerOptions" : {
"paths" : {
"@myorg/shared/ui" : [ "libs/shared/ui/src/index.ts" ],
"@myorg/shared/utils" : [ "libs/shared/utils/src/index.ts" ],
"@myorg/features/auth" : [ "libs/features/auth/src/index.ts" ]
}
}
}
// Generate new library
nx generate @nx/react:library shared/ui
// Generate new app
nx generate @nx/next:application admin
// Run commands
nx build web # Build web app
nx test shared-ui # Test UI library
nx run-many --target=test --all # Test all projects
nx affected:build # Build only affected by changes
nx dep-graph # Visualize dependency graph
Example: Turborepo setup (simpler alternative)
// Initialize Turborepo
npx create-turbo@latest
// Structure
my-turborepo/
├── apps/
│ ├── web/ # Next.js app
│ └── docs/ # Documentation site
├── packages/
│ ├── ui/ # Shared UI components
│ ├── eslint-config/ # Shared ESLint config
│ ├── tsconfig/ # Shared TS configs
│ └── typescript-config/
├── turbo.json # Turbo configuration
└── package.json
// turbo.json - Pipeline configuration
{
"pipeline" : {
"build" : {
"dependsOn" : [ "^build" ],
"outputs" : [ ".next/**" , "dist/**" ]
},
"test" : {
"dependsOn" : [ "build" ],
"cache" : false
},
"lint" : {
"outputs" : []
},
"dev" : {
"cache" : false ,
"persistent" : true
}
}
}
// Root package.json
{
"name" : "my-turborepo" ,
"private" : true ,
"workspaces" : [ "apps/*" , "packages/*" ],
"scripts" : {
"build" : "turbo run build" ,
"dev" : "turbo run dev" ,
"test" : "turbo run test" ,
"lint" : "turbo run lint"
},
"devDependencies" : {
"turbo" : "latest"
}
}
// packages/ui/package.json
{
"name" : "@repo/ui" ,
"version" : "0.0.0" ,
"main" : "./src/index.tsx" ,
"types" : "./src/index.tsx" ,
"exports" : {
"." : "./src/index.tsx" ,
"./button" : "./src/Button.tsx"
}
}
// apps/web - Import from workspace
import { Button } from '@repo/ui';
import { formatDate } from '@repo/utils';
Example: pnpm workspaces (lightweight approach)
// pnpm-workspace.yaml
packages :
- 'apps/*'
- 'packages/*'
// Root package.json
{
"name" : "monorepo" ,
"private" : true ,
"scripts" : {
"build" : "pnpm -r --filter='./packages/*' build" ,
"test" : "pnpm -r test" ,
"dev" : "pnpm --parallel -r dev"
}
}
// packages/shared/package.json
{
"name" : "@monorepo/shared" ,
"version" : "1.0.0" ,
"main" : "dist/index.js" ,
"types" : "dist/index.d.ts" ,
"scripts" : {
"build" : "tsc" ,
"dev" : "tsc --watch"
}
}
// apps/web/package.json
{
"name" : "web" ,
"dependencies" : {
"@monorepo/shared" : "workspace:*"
}
}
// Commands
pnpm install # Install all deps
pnpm --filter web dev # Run web app
pnpm --filter @monorepo/shared build # Build shared package
pnpm -r build # Build all packages recursively
Choosing a Tool: Use Nx for large teams with complex
dependencies.
Use Turborepo for modern apps with simple needs. Use pnpm
workspaces
for minimal overhead. Use Lerna for publishing npm packages.
15.2 Feature-based Folder Structure
Organize code by feature/domain rather than technical layer for better scalability and team collaboration.
Structure Type
Organization
Pros
Cons
Feature-based
Group by business feature
Scalable, clear ownership, easy to delete
Shared code requires careful planning
Layer-based
Group by technical layer (components, hooks, utils)
Simple, familiar, flat structure
Hard to scale, unclear ownership
Hybrid
Features + shared folder for common code
Best of both worlds
More complex initially
Example: Feature-based structure (recommended for large apps)
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.tsx
│ │ │ ├── SignupForm.tsx
│ │ │ └── PasswordReset.tsx
│ │ ├── hooks/
│ │ │ ├── useAuth.ts
│ │ │ └── useLogin.ts
│ │ ├── api/
│ │ │ └── authApi.ts
│ │ ├── types/
│ │ │ └── auth.types.ts
│ │ ├── utils/
│ │ │ └── validation.ts
│ │ └── index.ts # Barrel export
│ │
│ ├── dashboard/
│ │ ├── components/
│ │ │ ├── DashboardLayout.tsx
│ │ │ ├── StatsCard.tsx
│ │ │ └── RecentActivity.tsx
│ │ ├── hooks/
│ │ │ └── useDashboardData.ts
│ │ ├── api/
│ │ │ └── dashboardApi.ts
│ │ └── index.ts
│ │
│ ├── products/
│ │ ├── components/
│ │ │ ├── ProductList.tsx
│ │ │ ├── ProductCard.tsx
│ │ │ └── ProductDetail.tsx
│ │ ├── hooks/
│ │ │ ├── useProducts.ts
│ │ │ └── useProductFilters.ts
│ │ ├── api/
│ │ │ └── productsApi.ts
│ │ ├── store/
│ │ │ └── productsSlice.ts
│ │ └── index.ts
│ │
│ └── cart/
│ ├── components/
│ ├── hooks/
│ ├── api/
│ └── index.ts
│
├── shared/
│ ├── components/ # Reusable UI components
│ │ ├── Button/
│ │ ├── Input/
│ │ ├── Modal/
│ │ └── Layout/
│ ├── hooks/ # Generic hooks
│ │ ├── useDebounce.ts
│ │ ├── useLocalStorage.ts
│ │ └── useMediaQuery.ts
│ ├── utils/ # Generic utilities
│ │ ├── format.ts
│ │ ├── validation.ts
│ │ └── api.ts
│ ├── types/ # Global types
│ │ └── common.types.ts
│ └── constants/
│ └── config.ts
│
├── pages/ # Next.js pages or React Router routes
│ ├── index.tsx
│ ├── dashboard.tsx
│ └── products/
│ ├── [id].tsx
│ └── index.tsx
│
├── styles/ # Global styles
│ ├── globals.css
│ └── variables.css
│
├── lib/ # External library configs
│ ├── axios.ts
│ └── react-query.ts
│
└── App.tsx
// Colocate tests, stories, styles with components
src/features/auth/components/LoginForm/
├── LoginForm.tsx
├── LoginForm.test.tsx
├── LoginForm.stories.tsx
├── LoginForm.module.css
├── useLoginForm.ts # Component-specific hook
└── index.ts
// LoginForm/index.ts - Barrel export
export { LoginForm } from './LoginForm';
export type { LoginFormProps } from './LoginForm';
// Usage in other files
import { LoginForm } from '@/features/auth/components/LoginForm';
// Alternative flat structure for simple components
src/features/auth/components/
├── LoginForm.tsx
├── LoginForm.test.tsx
├── LoginForm.stories.tsx
├── SignupForm.tsx
└── SignupForm.test.tsx
Example: Next.js App Router with features
app/
├── (auth)/ # Route group
│ ├── login/
│ │ └── page.tsx # Uses LoginForm from features/auth
│ └── signup/
│ └── page.tsx
│
├── dashboard/
│ ├── page.tsx # Uses Dashboard from features/dashboard
│ ├── layout.tsx
│ └── settings/
│ └── page.tsx
│
├── products/
│ ├── page.tsx
│ └── [id]/
│ └── page.tsx
│
├── api/
│ ├── auth/
│ │ └── route.ts
│ └── products/
│ └── route.ts
│
├── layout.tsx
└── page.tsx
// Separation of concerns:
// - features/ = business logic, UI, hooks
// - app/ = routing, server components, metadata
// - lib/ = configurations, clients
Anti-pattern: Don't create deep nesting (max 3-4 levels). Don't mix feature code with shared
code. Don't use index.ts for everything (explicit imports are clearer). Avoid circular dependencies between
features.
15.3 Barrel Exports Index Files
Use index.ts files to create clean public APIs for modules and simplify imports, but avoid performance
pitfalls.
Pattern
Use Case
Benefits
Cautions
Feature Barrel
Export feature public API
Clean imports, encapsulation
Can break tree-shaking
Component Barrel
Export component + types
Single import point
Import cost for all
Namespace Barrel
Group related utilities
Organized exports
All-or-nothing imports
Direct Exports
Performance-critical code
Best tree-shaking
Longer import paths
Example: Feature barrel export pattern
// features/auth/index.ts - Public API
export { LoginForm } from './components/LoginForm' ;
export { SignupForm } from './components/SignupForm' ;
export { useAuth } from './hooks/useAuth' ;
export { useLogin } from './hooks/useLogin' ;
export type { User, AuthState, LoginCredentials } from './types/auth.types' ;
// Don't export internal utilities
// import { validateEmail } from './utils/validation'; ❌
// Consumer code
import { LoginForm, useAuth } from '@/features/auth' ;
// features/auth/components/index.ts - Component barrel
export { LoginForm } from './LoginForm/LoginForm' ;
export { SignupForm } from './SignupForm/SignupForm' ;
export { PasswordReset } from './PasswordReset/PasswordReset' ;
// Usage
import { LoginForm, SignupForm } from '@/features/auth/components' ;
Example: Tree-shaking friendly exports
// ❌ Bad - Breaks tree-shaking (imports everything)
// shared/utils/index.ts
export * from './format' ;
export * from './validation' ;
export * from './api' ;
export * from './date' ;
// Usage imports ALL utils even if only using one
import { formatCurrency } from '@/shared/utils' ;
// ✅ Good - Named exports (better tree-shaking)
// shared/utils/index.ts
export { formatCurrency, formatDate } from './format' ;
export { validateEmail, validatePhone } from './validation' ;
export { apiClient, handleApiError } from './api' ;
// ✅ Better - Direct imports (best tree-shaking)
import { formatCurrency } from '@/shared/utils/format' ;
import { validateEmail } from '@/shared/utils/validation' ;
// ✅ Best - Namespace exports for related functions
// shared/utils/format/index.ts
export const format = {
currency : ( value : number ) => `${ value . toFixed ( 2 ) }` ,
date : ( date : Date ) => date. toLocaleDateString (),
percent : ( value : number ) => `${ ( value * 100 ). toFixed ( 2 ) }%`
};
// Usage
import { format } from '@/shared/utils/format' ;
format. currency ( 100 ); // "$100.00"
Example: Component library barrel exports
// shared/components/index.ts - Component library
export { Button } from './Button' ;
export { Input } from './Input' ;
export { Modal } from './Modal' ;
export { Card } from './Card' ;
export type { ButtonProps } from './Button' ;
export type { InputProps } from './Input' ;
// Usage
import { Button, Card, type ButtonProps } from '@/shared/components' ;
// Alternative: Subpath exports in package.json
// package.json
{
"exports" : {
"." : "./src/index.ts" ,
"./button" : "./src/Button.tsx" ,
"./input" : "./src/Input.tsx" ,
"./modal" : "./src/Modal.tsx"
}
}
// Usage with subpaths (better performance)
import { Button } from '@repo/ui/button' ;
import { Input } from '@repo/ui/input' ;
Best Practice: Use barrel exports for feature boundaries and component libraries. Use direct
imports for utilities and helpers. Configure sideEffects: false in package.json for better
tree-shaking.
15.4 Absolute Imports Path Mapping
Configure absolute imports to avoid relative path hell and improve code readability with TypeScript path
mapping.
Configuration
Tool/Framework
Config File
Syntax
TypeScript Paths
All TS projects
tsconfig.json
@/*, @components/*
Webpack Aliases
CRA, custom webpack
webpack.config.js
resolve.alias
Next.js
Next.js projects
tsconfig.json (auto)
@/* by default
Vite
Vite projects
vite.config.ts
resolve.alias
Example: TypeScript path mapping configuration
// tsconfig.json
{
"compilerOptions" : {
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "src/*" ],
"@components/*" : [ "src/shared/components/*" ],
"@features/*" : [ "src/features/*" ],
"@hooks/*" : [ "src/shared/hooks/*" ],
"@utils/*" : [ "src/shared/utils/*" ],
"@types/*" : [ "src/shared/types/*" ],
"@lib/*" : [ "src/lib/*" ],
"@styles/*" : [ "src/styles/*" ]
}
}
}
// Before (relative imports - hard to read)
import Button from '../../../shared/components/Button';
import { formatDate } from '../../../../shared/utils/format';
import { useAuth } from '../../features/auth/hooks/useAuth';
// After (absolute imports - clean and clear)
import Button from '@components/Button';
import { formatDate } from '@utils/format';
import { useAuth } from '@features/auth/hooks/useAuth';
// Alternative: Single @ prefix
{
"compilerOptions" : {
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "src/*" ]
}
}
}
// Usage
import Button from '@/shared/components/Button';
import { formatDate } from '@/shared/utils/format';
import { useAuth } from '@/features/auth/hooks/useAuth';
Example: Vite configuration with aliases
// vite.config.ts
import { defineConfig } from 'vite' ;
import react from '@vitejs/plugin-react' ;
import path from 'path' ;
export default defineConfig ({
plugins: [ react ()],
resolve: {
alias: {
'@' : path. resolve (__dirname, './src' ),
'@components' : path. resolve (__dirname, './src/shared/components' ),
'@features' : path. resolve (__dirname, './src/features' ),
'@hooks' : path. resolve (__dirname, './src/shared/hooks' ),
'@utils' : path. resolve (__dirname, './src/shared/utils' ),
'@types' : path. resolve (__dirname, './src/shared/types' ),
'@styles' : path. resolve (__dirname, './src/styles' )
}
}
});
// tsconfig.json (must match Vite config)
{
"compilerOptions" : {
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "src/*" ],
"@components/*" : [ "src/shared/components/*" ],
"@features/*" : [ "src/features/*" ],
"@hooks/*" : [ "src/shared/hooks/*" ],
"@utils/*" : [ "src/shared/utils/*" ],
"@types/*" : [ "src/shared/types/*" ],
"@styles/*" : [ "src/styles/*" ]
}
}
}
Example: Next.js automatic path mapping
// tsconfig.json - Next.js auto-configures @/* alias
{
"compilerOptions" : {
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "./src/*" ],
"@/components/*" : [ "./src/components/*" ],
"@/lib/*" : [ "./src/lib/*" ]
}
}
}
// Usage in Next.js
// app/page.tsx
import { Button } from '@/components/ui/Button';
import { db } from '@/lib/database';
// Monorepo with multiple apps
// apps/web/tsconfig.json
{
"extends" : "../../tsconfig.base.json" ,
"compilerOptions" : {
"paths" : {
"@web/*" : [ "./src/*" ],
"@repo/ui" : [ "../../packages/ui/src/index.ts" ],
"@repo/utils" : [ "../../packages/utils/src/index.ts" ]
}
}
}
Example: Jest configuration for path mapping
// jest.config.js - Match TypeScript paths
module . exports = {
moduleNameMapper: {
"^@/(.*)$" : "<rootDir>/src/$1" ,
"^@components/(.*)$" : "<rootDir>/src/shared/components/$1" ,
"^@features/(.*)$" : "<rootDir>/src/features/$1" ,
"^@hooks/(.*)$" : "<rootDir>/src/shared/hooks/$1" ,
"^@utils/(.*)$" : "<rootDir>/src/shared/utils/$1"
}
};
// Alternative: Use ts-jest preset (auto-reads tsconfig paths)
module . exports = {
preset: 'ts-jest' ,
moduleNameMapper: {
"^@/(.*) \\ $" : "<rootDir>/src/$1"
}
};
Common Pitfalls: Ensure build tools (Webpack, Vite, Jest) configurations match tsconfig.json
paths.
Use relative imports for files in same feature. Don't mix @ and ~ prefixes. Keep path mappings simple (2-3 max).
15.5 ESLint Prettier Husky Configuration
Enforce code quality and consistency with automated linting, formatting, and pre-commit hooks.
Tool
Purpose
When It Runs
Config File
ESLint
Find code quality issues, enforce rules
On save, pre-commit, CI
.eslintrc.json
Prettier
Format code consistently
On save, pre-commit
.prettierrc
Husky
Git hooks automation
Pre-commit, pre-push
.husky/
lint-staged
Run linters on staged files only
Pre-commit
.lintstagedrc
Example: Complete ESLint + Prettier + Husky setup
// Installation
npm install -D eslint prettier eslint-config-prettier eslint-plugin-prettier
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -D eslint-plugin-react eslint-plugin-react-hooks
npm install -D husky lint-staged
// Initialize Husky
npx husky init
// .eslintrc.json
{
"extends" : [
"eslint:recommended" ,
"plugin:@typescript-eslint/recommended" ,
"plugin:react/recommended" ,
"plugin:react-hooks/recommended" ,
"prettier" // Must be last to override other configs
],
"parser" : "@typescript-eslint/parser" ,
"parserOptions" : {
"ecmaVersion" : 2022 ,
"sourceType" : "module" ,
"ecmaFeatures" : {
"jsx" : true
}
},
"plugins" : [ "@typescript-eslint" , "react" , "react-hooks" , "prettier" ],
"rules" : {
"prettier/prettier" : "error" ,
"@typescript-eslint/no-unused-vars" : [ "error" , { "argsIgnorePattern" : "^_" }],
"@typescript-eslint/explicit-module-boundary-types" : "off" ,
"react/react-in-jsx-scope" : "off" , // Not needed in React 17+
"react/prop-types" : "off" , // Using TypeScript
"react-hooks/rules-of-hooks" : "error" ,
"react-hooks/exhaustive-deps" : "warn"
},
"settings" : {
"react" : {
"version" : "detect"
}
}
}
// .prettierrc
{
"semi" : true ,
"trailingComma" : "es5" ,
"singleQuote" : true ,
"printWidth" : 100 ,
"tabWidth" : 2 ,
"useTabs" : false ,
"arrowParens" : "avoid" ,
"endOfLine" : "lf"
}
// .prettierignore
node_modules
.next
dist
build
coverage
*.min.js
Example: Husky + lint-staged configuration
// package.json
{
"scripts" : {
"lint" : "eslint . --ext .ts,.tsx,.js,.jsx" ,
"lint:fix" : "eslint . --ext .ts,.tsx,.js,.jsx --fix" ,
"format" : "prettier --write \" src/**/*.{ts,tsx,js,jsx,json,css,md} \" " ,
"format:check" : "prettier --check \" src/**/*.{ts,tsx,js,jsx,json,css,md} \" " ,
"prepare" : "husky"
},
"lint-staged" : {
"*.{ts,tsx,js,jsx}" : [
"eslint --fix" ,
"prettier --write"
],
"*.{json,css,md}" : [
"prettier --write"
]
}
}
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- " $ 0 ")/_/husky.sh"
npx lint-staged
// .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- " $ 0 ")/_/husky.sh"
npm run type-check
npm run test
// .lintstagedrc.js (alternative config file)
module.exports = {
'*.{ts,tsx }': [
'eslint --fix' ,
'prettier --write' ,
() => 'tsc --noEmit' // Type check all files
],
'*.{ js,jsx }': [ 'eslint --fix' , 'prettier --write' ],
'*.{ json,md,css }': [ 'prettier --write' ]
};
Example: Next.js ESLint configuration
// .eslintrc.json - Next.js specific
{
"extends" : [
"next/core-web-vitals" ,
"plugin:@typescript-eslint/recommended" ,
"prettier"
],
"rules" : {
"@next/next/no-html-link-for-pages" : "off" ,
"@typescript-eslint/no-explicit-any" : "warn" ,
"@typescript-eslint/no-unused-vars" : [ "error" , {
"argsIgnorePattern" : "^_" ,
"varsIgnorePattern" : "^_"
}]
}
}
// VS Code settings.json - Auto-format on save
{
"editor.formatOnSave" : true ,
"editor.defaultFormatter" : "esbenp.prettier-vscode" ,
"editor.codeActionsOnSave" : {
"source.fixAll.eslint" : true
},
"eslint.validate" : [
"javascript" ,
"javascriptreact" ,
"typescript" ,
"typescriptreact"
]
}
Best Practice: Run prettier through ESLint with eslint-plugin-prettier. Use
lint-staged to only check changed files (faster). Configure editor to format on save. Add
type-check
to pre-push hook.
15.6 TypeScript Strict Mode Configuration
Enable TypeScript strict mode for maximum type safety and catch errors at compile-time instead of runtime.
Strict Option
What It Does
Common Issues It Catches
strict
Enables all strict checks (recommended)
All type-related issues
strictNullChecks
null/undefined must be explicit
Null pointer exceptions
strictFunctionTypes
Strict function parameter checking
Function type mismatches
noImplicitAny
No implicit any types
Missing type annotations
noUnusedLocals
Error on unused variables
Dead code, typos
noImplicitReturns
All code paths must return
Missing return statements
Example: Strict TypeScript configuration
// tsconfig.json - Recommended strict settings
{
"compilerOptions" : {
// Strict Type Checking
"strict" : true , // Enable all strict options
"noImplicitAny" : true , // No implicit 'any'
"strictNullChecks" : true , // Strict null/undefined checks
"strictFunctionTypes" : true , // Strict function types
"strictBindCallApply" : true , // Strict bind/call/apply
"strictPropertyInitialization" : true , // Class properties must be initialized
"noImplicitThis" : true , // No implicit 'this'
"alwaysStrict" : true , // Parse in strict mode
// Additional Checks
"noUnusedLocals" : true , // Error on unused variables
"noUnusedParameters" : true , // Error on unused parameters
"noImplicitReturns" : true , // All paths must return
"noFallthroughCasesInSwitch" : true , // No fallthrough in switch
"noUncheckedIndexedAccess" : true , // Index signatures return T | undefined
"allowUnreachableCode" : false , // Error on unreachable code
// Module Resolution
"moduleResolution" : "bundler" , // Modern resolution
"resolveJsonModule" : true , // Import JSON files
"isolatedModules" : true , // Each file as separate module
"esModuleInterop" : true , // Better CommonJS interop
"skipLibCheck" : true , // Skip type checking .d.ts files
// Emit
"target" : "ES2022" ,
"lib" : [ "ES2022" , "DOM" , "DOM.Iterable" ],
"jsx" : "react-jsx" , // React 17+ JSX transform
"module" : "ESNext" ,
"sourceMap" : true ,
// Path Mapping
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "src/*" ]
}
},
"include" : [ "src" ],
"exclude" : [ "node_modules" , "dist" , "build" ]
}
Example: Strict null checks and proper typing
// ❌ Without strictNullChecks
function getUser ( id : string ) {
const user = database. findUser (id); // Could be undefined
return user.name; // Runtime error if user is undefined
}
// ✅ With strictNullChecks
function getUser ( id : string ) : string | undefined {
const user = database. findUser (id); // User | undefined
// Must handle null/undefined explicitly
if ( ! user) {
return undefined ;
}
return user.name;
}
// ✅ Better - Use optional chaining
function getUserName ( id : string ) : string | undefined {
const user = database. findUser (id);
return user?.name; // Safe navigation
}
// ✅ Best - Use nullish coalescing
function getUserNameOrDefault ( id : string ) : string {
const user = database. findUser (id);
return user?.name ?? 'Anonymous' ;
}
// ❌ noImplicitAny error
function process ( data ) { // Error: Parameter 'data' implicitly has 'any' type
return data.value;
}
// ✅ Fixed with explicit type
function process ( data : { value : string }) : string {
return data.value;
}
// ✅ Generic for reusability
function process < T >( data : { value : T }) : T {
return data.value;
}
Example: Class strictPropertyInitialization
// ❌ Without strictPropertyInitialization
class User {
name : string ; // No error, but undefined at runtime
email : string ;
}
// ✅ Fixed - Initialize in constructor
class User {
name : string ;
email : string ;
constructor ( name : string , email : string ) {
this .name = name;
this .email = email;
}
}
// ✅ Alternative - Definite assignment assertion (use carefully)
class User {
name !: string ; // ! tells TS we'll initialize it
email !: string ;
async init ( id : string ) {
const data = await fetchUser (id);
this .name = data.name;
this .email = data.email;
}
}
// ✅ Best - Use optional or union types
class User {
name ?: string ; // Explicitly optional
email : string | null ; // Explicitly nullable
constructor ( email : string | null ) {
this .email = email;
}
}
// React component with strict mode
interface Props {
user ?: User ; // Explicitly optional
onSave : ( data : User ) => void ;
}
function UserProfile ({ user , onSave } : Props ) {
if ( ! user) {
return < div >No user data </ div > ;
}
// TypeScript knows user is defined here
return < div >{user.name} </ div > ;
}
Example: noUncheckedIndexedAccess
// Without noUncheckedIndexedAccess
const colors = [ 'red' , 'green' , 'blue' ];
const color = colors[ 5 ]; // color: string (wrong!)
console. log (color. toUpperCase ()); // Runtime error
// ✅ With noUncheckedIndexedAccess enabled
const colors = [ 'red' , 'green' , 'blue' ];
const color = colors[ 5 ]; // color: string | undefined (correct!)
if (color) {
console. log (color. toUpperCase ()); // Safe
}
// Dictionary access
interface UserMap {
[ id : string ] : User ;
}
const users : UserMap = { '1' : { name: 'John' } };
// Without noUncheckedIndexedAccess
const user = users[ '999' ]; // user: User (wrong!)
// With noUncheckedIndexedAccess
const user = users[ '999' ]; // user: User | undefined (correct!)
if (user) {
console. log (user.name);
}
Project Structure Best Practices
Aspect
Recommendation
Rationale
Monorepo
Nx (enterprise), Turborepo (modern), pnpm (simple)
Choose based on team size and complexity
Folder Structure
Feature-based with shared folder
Scalable, clear ownership, easy refactoring
Barrel Exports
Use for features, avoid for utilities
Clean API, but watch bundle size
Path Mapping
@ prefix with 2-3 aliases max
Clean imports without path hell
Code Quality
ESLint + Prettier + Husky + lint-staged
Automated consistency, catch errors early
TypeScript
strict: true with all checks enabled
Maximum type safety, fewer runtime errors
16.1 Vite Hot Module Replacement
Lightning-fast HMR with instant updates without full page reload, preserving application state during
development.
Feature
Description
Benefit
Use Case
Fast Refresh
Updates React components without losing state
Instant feedback
Component development
CSS HMR
Update styles without reload
Live style changes
UI/UX development
import.meta.hot API
Fine-grained HMR control
Custom module updates
Advanced scenarios
Dependency Pre-bundling
ESBuild pre-bundles node_modules
Fast cold start
Large dependencies
Example: Vite configuration with HMR
// vite.config.ts
import { defineConfig } from 'vite' ;
import react from '@vitejs/plugin-react' ;
export default defineConfig ({
plugins: [
react ({
// Enable Fast Refresh
fastRefresh: true ,
// Babel options for custom transforms
babel: {
plugins: [ 'babel-plugin-styled-components' ]
}
})
],
server: {
port: 3000 ,
open: true ,
hmr: {
// HMR configuration
overlay: true , // Show errors in browser overlay
port: 3000
},
// Watch options
watch: {
usePolling: false , // Use native file watchers
ignored: [ '**/node_modules/**' , '**/.git/**' ]
}
},
// Optimize dependencies
optimizeDeps: {
include: [ 'react' , 'react-dom' ],
exclude: [ '@your-lib/package' ]
}
});
// Custom HMR handling in module
// utils/config.ts
export const config = {
apiUrl: 'http://localhost:8000' ,
timeout: 5000
};
// Accept HMR updates for this module
if ( import . meta .hot) {
import . meta .hot. accept (( newModule ) => {
console. log ( 'Config updated:' , newModule);
});
}
Example: React Fast Refresh with state preservation
// Counter.tsx - State is preserved during HMR
import { useState } from 'react' ;
export function Counter () {
const [ count , setCount ] = useState ( 0 );
return (
< div >
< h1 >Count: {count}</ h1 >
< button onClick = {() => setCount ( c => c + 1 )}>
Increment
</ button >
{ /* Change this text - count state is preserved! */ }
< p >Click the button to count</ p >
</ div >
);
}
// HMR Boundary - Force reload on certain changes
// AppProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' ;
const queryClient = new QueryClient ();
export function AppProvider ({ children } : { children : React . ReactNode }) {
return (
< QueryClientProvider client = {queryClient}>
{children}
</ QueryClientProvider >
);
}
// Force full reload if this module changes
if ( import . meta .hot) {
import . meta .hot. dispose (() => {
// Cleanup before reload
queryClient. clear ();
});
}
Example: Vite environment variables with HMR
// .env.development
VITE_API_URL = http : //localhost:8000
VITE_APP_NAME = My App Dev
// Access in code (HMR updates automatically)
export const config = {
apiUrl: import . meta .env. VITE_API_URL ,
appName: import . meta .env. VITE_APP_NAME ,
isDev: import . meta .env. DEV ,
isProd: import . meta .env. PROD
};
// TypeScript types for env variables
/// < reference types = "vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL : string ;
readonly VITE_APP_NAME : string ;
}
interface ImportMeta {
readonly env : ImportMetaEnv ;
}
// Conditional logic with HMR
if ( import . meta .env. DEV ) {
console. log ( 'Development mode - HMR enabled' );
}
Performance: Vite HMR is 10-100x faster than Webpack due to native ES modules and esbuild.
Cold start in ~500ms, updates in <50ms. Use vite-plugin-inspect to debug HMR issues.
16.2 Webpack Dev Server Proxy
Configure development proxy to bypass CORS issues and route API requests to backend server during local
development.
Proxy Type
Configuration
Use Case
Benefits
Simple Proxy
String target URL
Single backend API
Quick setup, bypass CORS
Path Rewrite
Modify request paths
Different API versioning
Flexible routing
Multiple Proxies
Object with multiple targets
Microservices
Route to different services
WebSocket Proxy
ws: true option
Real-time connections
WebSocket support
Example: Webpack Dev Server proxy configuration
// webpack.config.js
module . exports = {
devServer: {
port: 3000 ,
// Simple proxy - forward /api to backend
proxy: {
'/api' : 'http://localhost:8000'
}
}
};
// Request: http://localhost:3000/api/users
// Proxied to: http://localhost:8000/api/users
// Advanced proxy with options
module . exports = {
devServer: {
proxy: {
'/api' : {
target: 'http://localhost:8000' ,
pathRewrite: { '^/api' : '' }, // Remove /api prefix
changeOrigin: true , // Change origin header
secure: false , // Accept self-signed certificates
logLevel: 'debug' // Log proxy requests
},
// Multiple backend services
'/auth' : {
target: 'http://localhost:8001' ,
changeOrigin: true
},
'/graphql' : {
target: 'http://localhost:8002' ,
changeOrigin: true
},
// WebSocket proxy
'/ws' : {
target: 'ws://localhost:8003' ,
ws: true , // Enable WebSocket proxying
changeOrigin: true
}
}
}
};
// Request: http://localhost:3000/api/users
// Proxied to: http://localhost:8000/users (api removed)
Example: Vite proxy configuration
// vite.config.ts
import { defineConfig } from 'vite' ;
export default defineConfig ({
server: {
proxy: {
// Simple proxy
'/api' : {
target: 'http://localhost:8000' ,
changeOrigin: true ,
rewrite : ( path ) => path. replace ( / ^ \/ api/ , '' )
},
// Proxy with custom headers
'/auth' : {
target: 'http://localhost:8001' ,
changeOrigin: true ,
configure : ( proxy , options ) => {
proxy. on ( 'proxyReq' , ( proxyReq , req , res ) => {
// Add custom headers
proxyReq. setHeader ( 'X-Custom-Header' , 'value' );
console. log ( 'Proxying:' , req.method, req.url);
});
proxy. on ( 'proxyRes' , ( proxyRes , req , res ) => {
console. log ( 'Response:' , proxyRes.statusCode, req.url);
});
}
},
// Conditional proxy based on request
'^/api/v[0-9]+' : {
target: 'http://localhost:8000' ,
changeOrigin: true ,
rewrite : ( path ) => {
// /api/v1/users -> /v1/users
return path. replace ( / ^ \/ api/ , '' );
}
}
}
}
});
// Next.js proxy (next.config.js)
module . exports = {
async rewrites () {
return [
{
source: '/api/:path*' ,
destination: 'http://localhost:8000/:path*'
}
];
}
};
Example: Create React App proxy setup
// package.json - Simple proxy
{
"proxy" : "http://localhost:8000"
}
// All requests to unknown routes are proxied to backend
// src/setupProxy.js - Advanced proxy
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
// API proxy
app.use(
'/api',
createProxyMiddleware({
target : 'http: //localhost:8000',
changeOrigin: true ,
pathRewrite : {
'^/api' : '' // Remove /api prefix
},
onProxyReq : (proxyReq , req, res) => {
console.log('Proxying : ' , req.method, req.url);
}
})
);
// Auth service proxy
app.use(
'/auth',
createProxyMiddleware({
target : 'http: //localhost:8001',
changeOrigin: true
})
);
// WebSocket proxy
app.use(
'/ws',
createProxyMiddleware({
target : 'http: //localhost:8003',
ws: true ,
changeOrigin : true
})
);
};
Security Note: Proxy configuration only works in development. In production, configure proper
CORS headers on backend or use same-origin deployment. Never expose sensitive backend URLs in client code.
16.3 GitHub Actions CI CD Pipeline
Automate testing, building, and deployment with GitHub Actions for continuous integration and delivery.
Workflow Stage
Actions
Purpose
Triggers
CI (Continuous Integration)
Lint, test, type-check, build
Validate code quality
Push, PR
CD (Continuous Deployment)
Build, deploy to staging/prod
Automated deployment
Merge to main
Scheduled Jobs
Security audits, dependency updates
Maintenance tasks
Cron schedule
Release
Version bump, changelog, publish
Package release
Tag creation
Example: Complete CI/CD workflow for React app
# .github/workflows/ci.yml
name : CI
on :
push :
branches : [ main , develop ]
pull_request :
branches : [ main , develop ]
jobs :
lint-and-test :
runs-on : ubuntu-latest
strategy :
matrix :
node-version : [ 18.x , 20.x ]
steps :
- name : Checkout code
uses : actions/checkout@v4
- name : Setup Node.js ${{ matrix.node-version }}
uses : actions/setup-node@v4
with :
node-version : ${{ matrix.node-version }}
cache : 'npm'
- name : Install dependencies
run : npm ci
- name : Lint
run : npm run lint
- name : Type check
run : npm run type-check
- name : Run tests
run : npm run test:ci
- name : Upload coverage
uses : codecov/codecov-action@v3
with :
files : ./coverage/coverage-final.json
fail_ci_if_error : true
build :
runs-on : ubuntu-latest
needs : lint-and-test
steps :
- uses : actions/checkout@v4
- uses : actions/setup-node@v4
with :
node-version : '20.x'
cache : 'npm'
- name : Install dependencies
run : npm ci
- name : Build
run : npm run build
env :
NODE_ENV : production
- name : Upload build artifacts
uses : actions/upload-artifact@v3
with :
name : build-output
path : dist/
retention-days : 7
Example: Deployment workflow to Vercel/Netlify
# .github/workflows/deploy.yml
name : Deploy
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-node@v4
with :
node-version : '20.x'
- name : Install dependencies
run : npm ci
- name : Build
run : npm run build
env :
VITE_API_URL : ${{ secrets.PROD_API_URL }}
VITE_APP_NAME : 'Production App'
# Deploy to Vercel
- name : Deploy to Vercel
uses : amondnet/vercel-action@v25
with :
vercel-token : ${{ secrets.VERCEL_TOKEN }}
vercel-org-id : ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id : ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args : '--prod'
# Or deploy to Netlify
- name : Deploy to Netlify
uses : nwtgck/actions-netlify@v2
with :
publish-dir : './dist'
production-deploy : true
env :
NETLIFY_AUTH_TOKEN : ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID : ${{ secrets.NETLIFY_SITE_ID }}
- name : Notify Slack
uses : 8398a7/action-slack@v3
with :
status : ${{ job.status }}
text : 'Deployment to production completed'
webhook_url : ${{ secrets.SLACK_WEBHOOK }}
if : always()
Example: Monorepo CI with Turborepo
# .github/workflows/ci.yml
name : CI
on : [ push , pull_request ]
jobs :
build :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
with :
fetch-depth : 2 # For turbo to detect changes
- uses : actions/setup-node@v4
with :
node-version : '20.x'
- name : Install pnpm
uses : pnpm/action-setup@v2
with :
version : 8
- name : Get pnpm store directory
id : pnpm-cache
run : echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name : Setup pnpm cache
uses : actions/cache@v3
with :
path : ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key : ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys : |
${{ runner.os }}-pnpm-store-
- name : Install dependencies
run : pnpm install --frozen-lockfile
- name : Build
run : pnpm turbo run build
- name : Test
run : pnpm turbo run test
- name : Lint
run : pnpm turbo run lint
Example: Automated dependency updates with Dependabot
# .github/dependabot.yml
version : 2
updates :
# Enable npm dependency updates
- package-ecosystem : "npm"
directory : "/"
schedule :
interval : "weekly"
day : "monday"
open-pull-requests-limit : 10
reviewers :
- "team-frontend"
labels :
- "dependencies"
- "automated"
# Group minor/patch updates
groups :
production-dependencies :
patterns :
- "*"
update-types :
- "minor"
- "patch"
# GitHub Actions updates
- package-ecosystem : "github-actions"
directory : "/"
schedule :
interval : "weekly"
# Auto-merge Dependabot PRs (workflow)
# .github/workflows/dependabot-auto-merge.yml
name : Dependabot auto-merge
on : pull_request
permissions :
pull-requests : write
contents : write
jobs :
dependabot :
runs-on : ubuntu-latest
if : github.actor == 'dependabot[bot]'
steps :
- name : Dependabot metadata
id : metadata
uses : dependabot/fetch-metadata@v1
- name : Enable auto-merge for Dependabot PRs
if : steps.metadata.outputs.update-type == 'version-update:semver-patch'
run : gh pr merge --auto --squash "$PR_URL"
env :
PR_URL : ${{github.event.pull_request.html_url}}
GITHUB_TOKEN : ${{secrets.GITHUB_TOKEN}}
Best Practice: Cache node_modules and build outputs for faster CI. Run jobs in parallel when
possible. Use matrix strategy for multi-version testing. Set up branch protection rules requiring CI pass.
16.4 Pre-commit Hooks lint-staged
Run linters and formatters only on staged files before commit to catch issues early and maintain code quality.
Tool
Purpose
When Runs
Performance
Husky
Git hooks management
Various git events
Instant hook registration
lint-staged
Run commands on staged files only
Pre-commit
Fast (only changed files)
commitlint
Validate commit messages
Commit-msg hook
Instant validation
pre-push
Run tests before push
Pre-push
Can be slow (full tests)
Example: Complete pre-commit hooks setup
// Installation
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
// Initialize Husky
npx husky init
// package.json
{
"scripts" : {
"prepare" : "husky",
"lint" : "eslint .",
"format" : "prettier --write .",
"type-check" : "tsc --noEmit",
"test" : "vitest"
},
"lint-staged" : {
"*.{ts,tsx,js,jsx}" : [
"eslint --fix" ,
"prettier --write"
],
"*.{json,md,css,scss}" : [
"prettier --write"
],
"*.{ts,tsx}" : [
() = > "tsc --noEmit" // Type check all files, not just staged
]
}
}
// .husky/pre-commit
#!/usr/bin/env sh
. "$( dirname -- " $0 ")/_/husky.sh"
npx lint-staged
// .husky/commit-msg
#!/usr/bin/env sh
. "$( dirname -- " $0 ")/_/husky.sh"
npx --no -- commitlint --edit $1
// .husky/pre-push
#!/usr/bin/env sh
. "$( dirname -- " $0 ")/_/husky.sh"
npm run type-check
npm run test -- --run
// commitlint.config.js
module.exports = {
extends: [ '@commitlint/config-conventional' ],
rules: {
'type-enum' : [
2,
'always' ,
[
'feat' , // New feature
'fix' , // Bug fix
'docs' , // Documentation
'style' , // Formatting
'refactor' , // Code restructuring
'test' , // Tests
'chore' , // Maintenance
'perf' , // Performance
'ci' , // CI/CD
'build' , // Build system
'revert' // Revert commit
]
],
'subject-case' : [2, 'always', 'sentence-case']
}
};
// Valid commit messages:
// ✅ feat: add user authentication
// ✅ fix: resolve navigation bug
// ✅ docs: update README installation steps
// ❌ added new feature (missing type )
// ❌ Fix: bug (wrong case )
Example: Advanced lint-staged configuration
// .lintstagedrc.js - More control
module.exports = {
// TypeScript/JavaScript files
'*.{ts,tsx }': [
'eslint --fix --max-warnings= 0 ' ,
'prettier --write' ,
() => 'tsc --noEmit --pretty' // Run for all files
],
// JavaScript files
'*.{ js,jsx }': [
'eslint --fix --max-warnings= 0 ' ,
'prettier --write'
],
// Styles
'*.{ css,scss }': [
'stylelint --fix' ,
'prettier --write'
],
// JSON/Markdown
'*.{ json,md }': [ 'prettier --write' ],
// Test files - run related tests
'** /*.test.{ts,tsx}': [
(filenames) => {
const tests = filenames.map(f => `--testPathPattern=${f}`).join(' ');
return `vitest run ${tests}`;
}
],
// Package.json - validate and sort
'package.json': [
'sort-package-json',
'prettier --write'
]
};
// Skip hooks when needed
// git commit --no-verify -m "emergency fix"
// git push --no-verify
Example: Monorepo lint-staged with Turborepo
// Root .lintstagedrc.js
module.exports = {
'*' : 'prettier --write --ignore-unknown' ,
'*.{ts,tsx,js,jsx }': (filenames) => {
// Only lint files in affected packages
const packages = new Set(
filenames.map(f => f.split('/')[1]) // Extract package name
);
return Array.from(packages).map(
pkg => `turbo run lint --filter=./${pkg }`
);
}
};
// Per-package hooks
// apps/web/.lintstagedrc.js
module.exports = {
'*.{ts,tsx }': [
'eslint --fix' ,
'prettier --write' ,
() => 'tsc --noEmit -p apps/web'
]
};
// Root package.json
{
"scripts" : {
"prepare" : "husky"
},
"lint-staged" : {
"apps/**/*.{ts,tsx}" : [
"turbo run lint --filter=./apps/*"
],
"packages/**/*.{ts,tsx}" : [
"turbo run lint --filter=./packages/*"
]
}
}
Performance Tip: Avoid running full test suite in pre-commit (too slow). Use pre-push for
tests.
For type-checking, use () => 'tsc --noEmit' to check all files, not individual staged files.
16.5 Storybook Component Documentation
Develop, document, and test UI components in isolation with comprehensive documentation and interactive
examples.
Feature
Purpose
Benefit
Add-on
Component Isolation
Develop without full app
Faster iteration
Built-in
Interactive Docs
Auto-generate documentation
Living style guide
@storybook/addon-docs
Controls
Modify props in UI
Test all states
@storybook/addon-controls
Actions
Log event handlers
Debug interactions
@storybook/addon-actions
Accessibility
A11y testing
Catch issues early
@storybook/addon-a11y
Example: Storybook setup and configuration
// Installation
npx storybook@latest init
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite' ;
const config : StorybookConfig = {
stories: [ '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)' ],
addons: [
'@storybook/addon-links' ,
'@storybook/addon-essentials' ,
'@storybook/addon-interactions' ,
'@storybook/addon-a11y' ,
'@storybook/addon-coverage'
],
framework: {
name: '@storybook/react-vite' ,
options: {}
},
docs: {
autodocs: 'tag' // Auto-generate docs page
}
};
export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react' ;
import '../src/styles/globals.css' ;
const preview : Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background | color) $ / i ,
date: /Date $ / i
}
},
backgrounds: {
default: 'light' ,
values: [
{ name: 'light' , value: '#ffffff' },
{ name: 'dark' , value: '#1a1a1a' }
]
}
}
};
export default preview;
Example: Component stories with all variants
// 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: 'radio' ,
options: [ 'sm' , 'md' , 'lg' ]
},
onClick: { action: 'clicked' }
}
};
export default meta;
type Story = StoryObj < typeof Button>;
// Default story
export const Primary : Story = {
args: {
variant: 'primary' ,
children: 'Button'
}
};
// Variants
export const Secondary : Story = {
args: {
variant: 'secondary' ,
children: 'Button'
}
};
export const Danger : Story = {
args: {
variant: 'danger' ,
children: 'Delete'
}
};
// Sizes
export const Small : Story = {
args: {
size: 'sm' ,
children: 'Small Button'
}
};
export const Large : Story = {
args: {
size: 'lg' ,
children: 'Large Button'
}
};
// States
export const Disabled : Story = {
args: {
disabled: true ,
children: 'Disabled'
}
};
export const Loading : Story = {
args: {
loading: true ,
children: 'Loading...'
}
};
// With icons
export const WithIcon : Story = {
args: {
children : (
<>
< span >🚀 </ span > Launch
</>
)
}
};
Example: Interactive stories with play function
// LoginForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react' ;
import { within, userEvent, expect } from '@storybook/test' ;
import { LoginForm } from './LoginForm' ;
const meta : Meta < typeof LoginForm> = {
title: 'Features/LoginForm' ,
component: LoginForm,
parameters: {
layout: 'centered'
}
};
export default meta;
type Story = StoryObj < typeof LoginForm>;
// Basic story
export const Default : Story = {};
// Interactive test
export const FilledForm : Story = {
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
// Find and fill inputs
const emailInput = canvas. getByLabelText ( 'Email' );
const passwordInput = canvas. getByLabelText ( 'Password' );
const submitButton = canvas. getByRole ( 'button' , { name: /sign in/ i });
// Simulate user interaction
await userEvent. type (emailInput, 'user@example.com' );
await userEvent. type (passwordInput, 'password123' );
// Verify inputs
await expect (emailInput). toHaveValue ( 'user@example.com' );
await expect (passwordInput). toHaveValue ( 'password123' );
// Submit form
await userEvent. click (submitButton);
}
};
// Error state
export const WithErrors : Story = {
args: {
errors: {
email: 'Invalid email format' ,
password: 'Password is required'
}
}
};
Example: MDX documentation with embedded stories
<!-- Button.stories.mdx -->
import { Canvas, Meta, Story, ArgTypes } from '@storybook/blocks';
import { Button } from './Button';
import * as ButtonStories from './Button.stories';
<Meta of={ButtonStories} />
# Button Component
A versatile button component with multiple variants and sizes.
## Usage
```tsx
import { Button } from '@/components/Button' ;
function App () {
return (
< Button variant = "primary" onClick = {() => alert ( 'Clicked!' )}>
Click Me
</ Button >
);
}
```
## Variants
<Canvas of={ButtonStories.Primary} />
<Canvas of={ButtonStories.Secondary} />
<Canvas of={ButtonStories.Danger} />
## Props
<ArgTypes of={Button} />
## Accessibility
- Keyboard navigable with Tab
- Supports aria-label and aria-describedby
- Disabled state properly communicated to screen readers
## Best Practices
- Use semantic button text (avoid "Click here")
- Provide loading state feedback
- Use appropriate variant for action importance
Storybook Benefits: Component catalog, visual testing with Chromatic, interaction testing,
accessibility auditing, responsive design testing, and living documentation for design systems.
Analyze bundle size, identify large dependencies, and optimize JavaScript bundles for better performance.
Tool
Platform
Features
Best For
webpack-bundle-analyzer
Webpack
Interactive treemap, module sizes
Webpack projects
rollup-plugin-visualizer
Vite/Rollup
Multiple chart types, treemap
Vite projects
source-map-explorer
Any bundler
Source map analysis
Generic analysis
bundlephobia
Web service
npm package size lookup
Dependency evaluation
Example: Webpack Bundle Analyzer setup
// Installation
npm install - D webpack - bundle - analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require ( 'webpack-bundle-analyzer' ).BundleAnalyzerPlugin;
module . exports = {
plugins: [
new BundleAnalyzerPlugin ({
analyzerMode: process.env. ANALYZE ? 'server' : 'disabled' ,
openAnalyzer: true ,
generateStatsFile: true ,
statsFilename: 'bundle-stats.json'
})
]
};
// package.json
{
"scripts" : {
"build" : "webpack --mode production" ,
"build:analyze" : "ANALYZE=true webpack --mode production"
}
}
// Run analysis
npm run build :analyze
// Create React App
npm install - D webpack - bundle - analyzer
npm run build
npx webpack - bundle - analyzer build / static / js /*.js
Example: Vite bundle analysis with visualizer
// Installation
npm install - D rollup - plugin - visualizer
// vite.config.ts
import { defineConfig } from 'vite' ;
import { visualizer } from 'rollup-plugin-visualizer' ;
export default defineConfig ({
plugins: [
visualizer ({
filename: './dist/stats.html' ,
open: true ,
gzipSize: true ,
brotliSize: true ,
template: 'treemap' // or 'sunburst', 'network'
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-react' : [ 'react' , 'react-dom' ],
'vendor-ui' : [ '@mui/material' , '@emotion/react' ],
'vendor-utils' : [ 'lodash-es' , 'date-fns' ]
}
}
}
}
});
// package.json
{
"scripts" : {
"build" : "vite build" ,
"build:analyze" : "vite build --mode production"
}
}
// Output: dist/stats.html with interactive visualization
Example: Next.js bundle analysis
// Installation
npm install - D @next / bundle - analyzer
// next.config.js
const withBundleAnalyzer = require ( '@next/bundle-analyzer' )({
enabled: process.env. ANALYZE === 'true'
});
module . exports = withBundleAnalyzer ({
// Next.js config
reactStrictMode: true ,
swcMinify: true ,
// Optimize bundle
webpack : ( config , { isServer }) => {
if ( ! isServer) {
// Client-side optimizations
config.optimization.splitChunks = {
chunks: 'all' ,
cacheGroups: {
default: false ,
vendors: false ,
// Vendor chunk
vendor: {
name: 'vendor' ,
chunks: 'all' ,
test: /node_modules/
},
// Common chunks
common: {
name: 'common' ,
minChunks: 2 ,
chunks: 'all' ,
priority: 10 ,
reuseExistingChunk: true ,
enforce: true
}
}
};
}
return config;
}
});
// package.json
{
"scripts" : {
"build" : "next build" ,
"analyze" : "ANALYZE=true next build"
}
}
// Run: npm run analyze
// Opens two browser tabs: client and server bundles
Example: Bundle size budgets and monitoring
// webpack.config.js - Size budgets
module.exports = {
performance : {
maxAssetSize : 250000 , // 250kb
maxEntrypointSize : 400000 , // 400kb
hints : 'error' , // or 'warning'
assetFilter : (assetFilename) => {
return assetFilename.endsWith('.js');
}
}
};
// vite.config.ts - Size warnings
export default defineConfig({
build : {
chunkSizeWarningLimit : 500 , // 500kb warning threshold
rollupOptions : {
output : {
manualChunks(id) {
// Split large dependencies
if (id.includes('node_modules')) {
if (id.includes('react')) return 'vendor-react';
if (id.includes('@mui')) return 'vendor-ui';
if (id.includes('lodash')) return 'vendor-utils';
return 'vendor';
}
}
}
}
}
});
// package.json - Bundle size tracking
{
"scripts" : {
"size" : "size-limit" ,
"size:why" : "size-limit --why"
},
"size-limit" : [
{
"path" : "dist/index.js" ,
"limit" : "200 KB"
},
{
"path" : "dist/vendor.js" ,
"limit" : "300 KB"
}
]
}
// .github/workflows/size.yml - CI size check
name: Size Check
on: pull_request
jobs:
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v 4
- uses: andresz 1 /size-limit-action@v 1
with:
github_token: ${ { secrets.GITHUB_TOKEN }}
17. Advanced Frontend Architecture Patterns
17.1 Micro-Frontend Module Federation
Split large applications into independently deployable micro-frontends that can be developed, tested, and
deployed by separate teams.
Approach
Technology
Benefits
Challenges
Module Federation
Webpack 5
Runtime integration, shared dependencies
Complex configuration
Iframe Approach
Native iframe
Complete isolation, simple
Poor performance, communication overhead
Web Components
Custom Elements
Framework agnostic, standards-based
Styling isolation issues
Single-SPA
Single-SPA framework
Framework agnostic orchestration
Learning curve, complexity
Example: Webpack Module Federation setup
// Host app (shell) - webpack.config.js
const ModuleFederationPlugin = require ( 'webpack/lib/container/ModuleFederationPlugin' );
module . exports = {
plugins: [
new ModuleFederationPlugin ({
name: 'host' ,
remotes: {
// Load remote apps
app1: 'app1@http://localhost:3001/remoteEntry.js' ,
app2: 'app2@http://localhost:3002/remoteEntry.js'
},
shared: {
// Share dependencies
react: { singleton: true , requiredVersion: '^18.0.0' },
'react-dom' : { singleton: true , requiredVersion: '^18.0.0' }
}
})
]
};
// Remote app 1 - webpack.config.js
module . exports = {
plugins: [
new ModuleFederationPlugin ({
name: 'app1' ,
filename: 'remoteEntry.js' ,
exposes: {
// Expose components
'./Button' : './src/components/Button' ,
'./Dashboard' : './src/pages/Dashboard'
},
shared: {
react: { singleton: true },
'react-dom' : { singleton: true }
}
})
]
};
// Host app - Load remote component
import React, { lazy, Suspense } from 'react' ;
const RemoteDashboard = lazy (() => import ( 'app1/Dashboard' ));
const RemoteButton = lazy (() => import ( 'app1/Button' ));
function App () {
return (
< div >
< h1 >Host Application</ h1 >
< Suspense fallback = "Loading Dashboard..." >
< RemoteDashboard />
</ Suspense >
< Suspense fallback = "Loading Button..." >
< RemoteButton />
</ Suspense >
</ div >
);
}
Example: Vite Module Federation with plugin
// Installation
npm install - D @originjs / vite - plugin - federation
// Host app - vite.config.ts
import { defineConfig } from 'vite' ;
import federation from '@originjs/vite-plugin-federation' ;
export default defineConfig ({
plugins: [
federation ({
name: 'host-app' ,
remotes: {
remoteApp: 'http://localhost:3001/assets/remoteEntry.js'
},
shared: [ 'react' , 'react-dom' ]
})
],
build: {
target: 'esnext' ,
minify: false
}
});
// Remote app - vite.config.ts
export default defineConfig ({
plugins: [
federation ({
name: 'remote-app' ,
filename: 'remoteEntry.js' ,
exposes: {
'./Button' : './src/components/Button' ,
'./Header' : './src/components/Header'
},
shared: [ 'react' , 'react-dom' ]
})
],
build: {
target: 'esnext'
}
});
// Host usage
import { lazy } from 'react' ;
const RemoteButton = lazy (() => import ( 'remoteApp/Button' ));
function App () {
return < RemoteButton />;
}
Example: Communication between micro-frontends
// Shared event bus for communication
// eventBus.ts
class EventBus {
private events : Map < string , Function []> = new Map ();
subscribe ( event : string , callback : Function ) {
if ( ! this .events. has (event)) {
this .events. set (event, []);
}
this .events. get (event) ! . push (callback);
// Return unsubscribe function
return () => {
const callbacks = this .events. get (event);
if (callbacks) {
const index = callbacks. indexOf (callback);
if (index > - 1 ) callbacks. splice (index, 1 );
}
};
}
publish ( event : string , data ?: any ) {
const callbacks = this .events. get (event);
if (callbacks) {
callbacks. forEach ( cb => cb (data));
}
}
}
export const eventBus = new EventBus ();
// App 1 - Publish event
import { eventBus } from '@shared/eventBus' ;
function CartComponent () {
const addToCart = ( item : Product ) => {
eventBus. publish ( 'cart:add' , { item, quantity: 1 });
};
return < button onClick = {() => addToCart (product)}>Add to Cart</ button >;
}
// App 2 - Subscribe to event
import { eventBus } from '@shared/eventBus' ;
import { useEffect, useState } from 'react' ;
function CartBadge () {
const [ count , setCount ] = useState ( 0 );
useEffect (() => {
const unsubscribe = eventBus. subscribe ( 'cart:add' , ( data ) => {
setCount ( prev => prev + data.quantity);
});
return unsubscribe;
}, []);
return < div >Cart: {count}</ div >;
}
// Alternative: Use CustomEvents for cross-app communication
window. dispatchEvent ( new CustomEvent ( 'cart:update' , {
detail: { items: 5 }
}));
window. addEventListener ( 'cart:update' , ( e ) => {
console. log ( 'Cart updated:' , e.detail);
});
Challenges: Version conflicts in shared dependencies, deployment coordination, testing
complexity,
increased bundle size. Use only for large-scale apps with multiple teams. Consider monorepo first.
17.2 Web Components Custom Elements
Create reusable, framework-agnostic components using Web Components standards (Custom Elements, Shadow DOM,
HTML Templates).
API
Purpose
Use Case
Browser Support
Custom Elements
Define custom HTML tags
Reusable components
Excellent
Shadow DOM
Encapsulated styles and DOM
Style isolation
Excellent
HTML Templates
Reusable DOM fragments
Component templates
Excellent
Slots
Content projection
Flexible layouts
Excellent
Example: Creating Custom Element with Shadow DOM
// my-button.ts - Custom button component
class MyButton extends HTMLElement {
private _disabled : boolean = false ;
constructor () {
super ();
// Create Shadow DOM for style encapsulation
this . attachShadow ({ mode: 'open' });
}
// Observed attributes - trigger attributeChangedCallback
static get observedAttributes () {
return [ 'disabled' , 'variant' ];
}
// Lifecycle: Element added to DOM
connectedCallback () {
this . render ();
this . addEventListener ( 'click' , this .handleClick);
}
// Lifecycle: Element removed from DOM
disconnectedCallback () {
this . removeEventListener ( 'click' , this .handleClick);
}
// Lifecycle: Attribute changed
attributeChangedCallback ( name : string , oldValue : string , newValue : string ) {
if (name === 'disabled' ) {
this ._disabled = newValue !== null ;
}
this . render ();
}
handleClick = ( e : Event ) => {
if ( this ._disabled) {
e. preventDefault ();
e. stopPropagation ();
return ;
}
// Dispatch custom event
this . dispatchEvent ( new CustomEvent ( 'button-click' , {
bubbles: true ,
composed: true ,
detail: { timestamp: Date. now () }
}));
};
render () {
const variant = this . getAttribute ( 'variant' ) || 'primary' ;
this .shadowRoot ! .innerHTML = `
<style>
:host {
display: inline-block;
}
button {
padding: 12px 24px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
}
button.primary {
background: #007acc;
color: white;
}
button.secondary {
background: #6c757d;
color: white;
}
button:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-1px);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
<button class="${ variant }" ?disabled="${ this . _disabled }">
<slot></slot>
</button>
` ;
}
}
// Register custom element
customElements. define ( 'my-button' , MyButton);
// Usage in HTML
< my-button variant = "primary" >Click Me</ my-button >
< my-button variant = "secondary" disabled >Disabled</ my-button >
// Usage in JavaScript
const button = document. createElement ( 'my-button' );
button. setAttribute ( 'variant' , 'primary' );
button.textContent = 'Dynamic Button' ;
button. addEventListener ( 'button-click' , ( e ) => {
console. log ( 'Clicked at:' , e.detail.timestamp);
});
document.body. appendChild (button);
Example: Web Component with Lit library (simplifies creation)
// Installation
npm install lit
// my-card.ts
import { LitElement, html, css } from 'lit' ;
import { customElement, property } from 'lit/decorators.js' ;
@ customElement ( 'my-card' )
export class MyCard extends LitElement {
@ property ({ type: String }) title = '' ;
@ property ({ type: String }) subtitle = '' ;
@ property ({ type: Boolean }) loading = false ;
// Scoped styles
static styles = css `
:host {
display: block;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
background: white;
}
.title {
font-size: 20px;
font-weight: bold;
margin-bottom: 8px;
}
.subtitle {
color: #666;
font-size: 14px;
}
.loading {
opacity: 0.6;
}
` ;
render () {
return html `
<div class="${ this . loading ? 'loading' : ''}">
<div class="title">${ this . title }</div>
<div class="subtitle">${ this . subtitle }</div>
<slot></slot>
</div>
` ;
}
}
// Usage
< my-card title = "Card Title" subtitle = "Card subtitle" >
< p >Card content goes here</ p >
</ my-card >
// React integration
function App () {
return (
< my-card
title = "From React"
subtitle = "Web Component in React"
>
< p >Content</ p >
</ my-card >
);
}
Example: Slots for content projection
// dialog-box.ts
class DialogBox extends HTMLElement {
constructor () {
super ();
this . attachShadow ({ mode: 'open' });
}
connectedCallback () {
this .shadowRoot ! .innerHTML = `
<style>
.dialog {
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
background: white;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header {
font-size: 20px;
font-weight: bold;
margin-bottom: 12px;
}
.content {
margin-bottom: 16px;
}
.footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>
<div class="dialog">
<div class="header">
<slot name="header">Default Header</slot>
</div>
<div class="content">
<slot>Default content</slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
` ;
}
}
customElements. define ( 'dialog-box' , DialogBox);
// Usage with named slots
< dialog-box >
< span slot = "header" >Confirm Action</ span >
< p >Are you sure you want to delete this item?</ p >
< div slot = "footer" >
< button >Cancel</ button >
< button >Confirm</ button >
</ div >
</ dialog-box >
When to Use: Design systems shared across multiple frameworks, embeddable widgets, library
components.
Lit, Stencil, or vanilla Web Components all work. Full framework agnosticism with standards-based approach.
17.3 Island Architecture Astro Qwik
Ship zero JavaScript by default, hydrate interactive components only when needed for optimal performance.
Framework
Approach
Hydration Strategy
Best For
Astro
Islands Architecture
Partial, on-demand
Content sites, blogs, docs
Qwik
Resumability
No hydration, resume on interaction
Web apps, e-commerce
Next.js Islands
Server Components
Selective client components
Full-stack apps
Marko
Automatic partial hydration
Component-level
eBay's solution
Example: Astro Islands Architecture
// Installation
npm create astro@latest
// src/pages/index.astro
---
import Layout from '../layouts/Layout.astro';
import Counter from '../components/Counter.jsx'; // React component
import Carousel from '../components/Carousel.vue'; // Vue component
---
< Layout title = "Astro Islands" >
< h1 >Welcome to Astro</ h1 >
<!-- Static content - no JS shipped -->
< p >This content is completely static.</ p >
<!-- Island 1: Load immediately -->
< Counter client:load />
<!-- Island 2: Load when visible -->
< Carousel client:visible />
<!-- Island 3: Load on idle -->
< Comments client:idle />
<!-- Island 4: Load on media query -->
< MobileMenu client:media = "(max-width: 768px)" />
<!-- No client directive = zero JS -->
< StaticComponent />
</ Layout >
// Hydration directives:
// client:load - Load immediately
// client:idle - Load when browser is idle
// client:visible - Load when scrolled into view
// client:media - Load based on media query
// client:only - Only run on client (skip SSR)
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';
export default defineConfig({
integrations: [react(), vue(), svelte()],
output: 'static' // or 'server' for SSR
});
Example: Qwik resumability (no hydration)
// Installation
npm create qwik@latest
// src/routes/index.tsx
import { component$, useSignal } from '@builder.io/qwik' ;
export default component$ (() => {
const count = useSignal ( 0 );
return (
< div >
< h1 >Qwik Counter </ h1 >
< p > Count : { count . value }</ p >
{ /* Event handlers are lazy-loaded on interaction */ }
< button onClick$ = {() => count.value++} >
Increment
</ button >
</ div >
);
});
// Key concepts:
// 1. $ suffix - Qwik optimizer boundary
// 2. useSignal - Reactive state
// 3. No hydration - app "resumes" from server state
// 4. Lazy load event handlers only when needed
// src/components/product-list.tsx
import { component$, Resource, useResource$ } from '@builder.io/qwik' ;
export default component$ (() => {
// Data fetching with suspense
const products = useResource$ ( async () => {
const res = await fetch ( '/api/products' );
return res. json ();
});
return (
< Resource
value = {products}
onPending = {() => <div>Loading ...</ div > }
onRejected = {(error) => <div>Error: {error.message} </ div > }
onResolved = {(data) => (
<ul>
{data. map (( product ) => (
< li key = {product.id} > {product.name} </ li >
))}
</ul>
)}
/>
);
});
// Performance benefits:
// - Instant Time to Interactive (TTI)
// - Progressive interactivity
// - Optimal Core Web Vitals
Example: Astro with React islands for interactivity
// src/components/InteractiveCart.jsx (React)
import { useState } from 'react';
export default function InteractiveCart({ initialItems = [] }) {
const [items, setItems] = useState(initialItems);
const addItem = (item) => {
setItems([...items, item]);
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
< div className = "cart" >
< h2 >Cart ({items.length})</ h2 >
< ul >
{items.map(item => (
< li key = {item.id} >
{item.name} - ${item.price}
< button onClick = {() = > removeItem(item.id)}>Remove</ button >
</ li >
))}
</ ul >
</ div >
);
}
// src/pages/shop.astro
---
import Layout from '../layouts/Layout.astro';
import InteractiveCart from '../components/InteractiveCart.jsx';
import ProductCard from '../components/ProductCard.astro';
const products = await fetch('https://api.example.com/products').then(r => r.json());
---
< Layout title = "Shop" >
<!-- Static product grid (no JS) -->
< div class = "products" >
{products.map(product => (
< ProductCard product = {product} />
))}
</ div >
<!-- Interactive cart (JS island, loads when visible) -->
< InteractiveCart client:visible initialItems = {[]} />
</ Layout >
// Result: Only cart component ships JavaScript
// Products are pure HTML/CSS = instant rendering
Performance Impact: Islands reduce JavaScript by 90%+. Perfect for content-heavy sites.
Use Astro for static sites with islands of interactivity. Use Qwik for full web apps with optimal performance.
17.4 Server Components Next.js 13
React Server Components render on server, reducing client bundle size and enabling direct backend access
without APIs.
Component Type
Rendering
Capabilities
Use Case
Server Component
Server only
Direct DB access, no client JS
Static content, data fetching
Client Component
Client + Server
Hooks, events, browser APIs
Interactive UI
Shared Component
Both (default)
Limited to serializable props
Reusable components
Example: Next.js App Router with Server Components
// app/page.tsx - Server Component (default)
import { db } from '@/lib/database' ;
import ProductList from './ProductList' ;
// This runs ONLY on the server
export default async function HomePage () {
// Direct database access (no API route needed)
const products = await db.product. findMany ({
include: { category: true }
});
// Can use Node.js APIs
const fs = require ( 'fs' );
const data = fs. readFileSync ( './data.json' , 'utf8' );
return (
< div >
< h1 >Products </ h1 >
< ProductList products = {products} />
</ div >
);
}
// No JavaScript shipped for this component!
// app/ProductList.tsx - Client Component
'use client' ; // Directive to mark as client component
import { useState } from 'react' ;
export default function ProductList ({ products }) {
const [ filter , setFilter ] = useState ( '' );
const filtered = products. filter ( p =>
p.name. toLowerCase (). includes (filter. toLowerCase ())
);
return (
< div >
< input
type = "text"
value = {filter}
onChange = {(e) => setFilter (e.target.value)}
placeholder = "Filter products..."
/>
< ul >
{ filtered . map ( product => (
< li key = {product.id} > {product.name} </ li >
))}
</ ul >
</ div >
);
}
// This ships JavaScript for interactivity
Example: Data fetching patterns with Server Components
// app/dashboard/page.tsx
import { Suspense } from 'react' ;
import UserStats from './UserStats' ;
import RecentOrders from './RecentOrders' ;
import Analytics from './Analytics' ;
export default function DashboardPage () {
return (
< div >
< h1 >Dashboard </ h1 >
{ /* Stream components independently */ }
< Suspense fallback = {<div>Loading stats ...</ div > } >
< UserStats />
</ Suspense >
< Suspense fallback = {<div>Loading orders ...</ div > } >
< RecentOrders />
</ Suspense >
< Suspense fallback = {<div>Loading analytics ...</ div > } >
< Analytics />
</ Suspense >
</ div >
);
}
// app/dashboard/UserStats.tsx - Server Component
async function UserStats () {
// Each component fetches its own data
const stats = await fetch ( 'https://api.example.com/stats' , {
// Next.js extended fetch with caching
next: { revalidate: 60 } // Revalidate every 60 seconds
}). then ( r => r. json ());
return (
< div className = "stats" >
< div > Users : { stats . users }</ div >
< div > Active : { stats . active }</ div >
</ div >
);
}
// app/dashboard/RecentOrders.tsx
async function RecentOrders () {
const orders = await db.order. findMany ({
take: 10 ,
orderBy: { createdAt: 'desc' }
});
return (
< ul >
{ orders . map ( order => (
< li key = {order.id} >
Order #{order.id} - ${order.total}
</ li >
))}
</ ul >
);
}
// Parallel data fetching with streaming
// Each Suspense boundary resolves independently
Example: Mixing Server and Client Components
// app/product/[id]/page.tsx - Server Component
import { db } from '@/lib/database' ;
import AddToCartButton from './AddToCartButton' ;
import RelatedProducts from './RelatedProducts' ;
export default async function ProductPage ({ params }) {
const product = await db.product. findUnique ({
where: { id: params.id },
include: { reviews: true }
});
return (
< div >
{ /* Server-rendered content */ }
< h1 >{product.name} </ h1 >
< p >{product.description} </ p >
< p > Price : $ {product.price} </ p >
{ /* Client component for interactivity */ }
< AddToCartButton productId = {product.id} />
{ /* Server component for related products */ }
< RelatedProducts categoryId = {product.categoryId} />
{ /* Server-rendered reviews */ }
< div >
< h2 >Reviews </ h2 >
{ product . reviews . map ( review => (
< div key = {review.id} >
< strong >{review.author} </ strong >
< p >{review.content} </ p >
</ div >
))}
</ div >
</ div >
);
}
// app/product/[id]/AddToCartButton.tsx - Client Component
'use client' ;
import { useState } from 'react' ;
export default function AddToCartButton ({ productId }) {
const [ loading , setLoading ] = useState ( false );
const handleAdd = async () => {
setLoading ( true );
await fetch ( '/api/cart' , {
method: 'POST' ,
body: JSON . stringify ({ productId })
});
setLoading ( false );
};
return (
< button onClick = {handleAdd} disabled = {loading} >
{ loading ? 'Adding...' : 'Add to Cart' }
</ button >
);
}
// Rules:
// 1. Server components can import client components
// 2. Client components CANNOT import server components directly
// 3. Pass server components as children to client components
Limitations: Cannot use hooks, event handlers, or browser APIs in Server Components. Props must
be
serializable (no functions). Requires Next.js 13+ App Router. Learning curve for new mental model.
17.5 Edge Computing Vercel Functions
Deploy serverless functions to edge locations globally for ultra-low latency API responses and dynamic content.
Platform
Runtime
Cold Start
Use Case
Vercel Edge Functions
V8 isolates
<50ms
API routes, middleware
Cloudflare Workers
V8 isolates
<5ms
Edge compute, KV storage
AWS Lambda@Edge
Node.js
~100ms
CloudFront customization
Netlify Edge Functions
Deno
<50ms
JAMstack edge logic
Example: Vercel Edge Function (Next.js)
// app/api/edge/route.ts
import { NextRequest, NextResponse } from 'next/server' ;
export const runtime = 'edge' ; // Enable Edge Runtime
export async function GET ( request : NextRequest ) {
// Access geo location
const country = request.geo?.country || 'Unknown' ;
const city = request.geo?.city || 'Unknown' ;
// Fast external API call (co-located at edge)
const data = await fetch ( 'https://api.example.com/data' , {
headers: { 'Authorization' : process.env. API_KEY }
}). then ( r => r. json ());
return NextResponse. json ({
message: `Hello from ${ city }, ${ country }!` ,
data,
edge: true
});
}
// Edge middleware - runs before every request
// middleware.ts
import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
export function middleware ( request : NextRequest ) {
// Geo-based redirect
const country = request.geo?.country;
if (country === 'CN' && ! request.nextUrl.pathname. startsWith ( '/cn' )) {
return NextResponse. redirect ( new URL ( '/cn' , request.url));
}
// A/B testing
const bucket = Math. random () < 0.5 ? 'a' : 'b' ;
const response = NextResponse. next ();
response.cookies. set ( 'ab-test' , bucket);
// Add custom headers
response.headers. set ( 'x-edge-location' , request.geo?.city || 'Unknown' );
return response;
}
export const config = {
matcher: [ '/api/:path*' , '/products/:path*' ]
};
Example: Cloudflare Workers with KV storage
// worker.js
addEventListener ( 'fetch' , event => {
event. respondWith ( handleRequest (event.request));
});
async function handleRequest ( request ) {
const url = new URL (request.url);
// Check cache first
const cache = caches.default;
let response = await cache. match (request);
if (response) {
return response;
}
// Access KV storage (ultra-fast key-value store)
const config = await MY_KV_NAMESPACE . get ( 'config' , { type: 'json' });
// Generate response
response = new Response ( JSON . stringify ({
message: 'Hello from Cloudflare Edge' ,
config,
location: request.cf?.city
}), {
headers: {
'content-type' : 'application/json' ,
'cache-control' : 'public, max-age=60'
}
});
// Cache response
event. waitUntil (cache. put (request, response. clone ()));
return response;
}
// wrangler.toml
name = "my-worker"
main = "worker.js"
compatibility_date = "2024-01-01"
[[kv_namespaces]]
binding = "MY_KV_NAMESPACE"
id = "your-namespace-id"
// Deploy: npx wrangler deploy
Example: Edge function for personalization
// app/api/personalize/route.ts
import { NextRequest, NextResponse } from 'next/server' ;
export const runtime = 'edge' ;
export async function GET ( request : NextRequest ) {
// Get user preferences from cookie
const preferences = request.cookies. get ( 'preferences' )?.value;
const parsed = preferences ? JSON . parse (preferences) : {};
// Geo-based content
const country = request.geo?.country || 'US' ;
const currency = getCurrency (country);
// A/B test variant
const variant = request.cookies. get ( 'ab-variant' )?.value || 'control' ;
// Time-based personalization
const hour = new Date (). getHours ();
const greeting = hour < 12 ? 'Good morning' :
hour < 18 ? 'Good afternoon' : 'Good evening' ;
return NextResponse. json ({
greeting,
currency,
variant,
preferences: parsed,
location: {
country,
city: request.geo?.city
}
});
}
function getCurrency ( country : string ) : string {
const currencyMap : Record < string , string > = {
US: 'USD' ,
GB: 'GBP' ,
JP: 'JPY' ,
DE: 'EUR'
};
return currencyMap[country] || 'USD' ;
}
// Usage in page
async function ProductPrice ({ productId }) {
const personalization = await fetch ( '/api/personalize' ). then ( r => r. json ());
const price = await getPrice (productId, personalization.currency);
return < div >{price} </ div > ;
}
Edge Benefits: 50-200ms faster than traditional serverless, global distribution, lower costs at
scale.
Limitations: No Node.js APIs, limited CPU time (50ms), smaller bundle size. Perfect for API routes, middleware,
auth.
17.6 WebAssembly WASM Integration
Run high-performance compiled code (C, C++, Rust) in the browser at near-native speed for compute-intensive
tasks.
Language
Toolchain
Use Case
Performance
Rust
wasm-pack, wasm-bindgen
CPU-intensive algorithms
Near-native
C/C++
Emscripten
Porting existing libraries
Near-native
AssemblyScript
asc (TypeScript-like)
TypeScript developers
Very fast
Go
TinyGo
Small WASM binaries
Fast
Example: Rust WebAssembly module with wasm-pack
// Installation
cargo install wasm - pack
// Create new Rust project
cargo new -- lib image - processor
cd image - processor
// Cargo.toml
[package]
name = "image-processor"
version = "0.1.0"
edition = "2021"
[lib]
crate - type = [ "cdylib" ]
[dependencies]
wasm - bindgen = "0.2"
// src/lib.rs
use wasm_bindgen :: prelude :: * ;
#[wasm_bindgen]
pub fn fibonacci (n: u32) -> u32 {
match n {
0 => 0 ,
1 => 1 ,
_ => fibonacci (n - 1 ) + fibonacci (n - 2 )
}
}
#[wasm_bindgen]
pub struct ImageProcessor {
width : u32,
height : u32,
}
#[wasm_bindgen]
impl ImageProcessor {
#[ wasm_bindgen (constructor)]
pub fn new ( width : u32 , height : u32 ) -> ImageProcessor {
ImageProcessor { width, height }
}
pub fn grayscale ( & self, pixels: & mut [u8]) {
for chunk in pixels. chunks_mut ( 4 ) {
let avg = (chunk[ 0 ] as u32 + chunk[ 1 ] as u32 + chunk[ 2 ] as u32 ) / 3 ;
chunk[ 0 ] = avg as u8 ;
chunk[ 1 ] = avg as u8 ;
chunk[ 2 ] = avg as u8 ;
}
}
pub fn blur ( & self, pixels: & mut [u8], radius: u32) {
// High-performance blur algorithm
// ... implementation
}
}
// Build WASM
// wasm-pack build --target web
// JavaScript usage
// src/App.tsx
import init, { fibonacci, ImageProcessor } from './wasm/image_processor' ;
async function App () {
// Initialize WASM module
await init ();
// Use Rust functions
const result = fibonacci ( 10 );
console. log ( 'Fibonacci(10):' , result);
// Process image
const processor = new ImageProcessor ( 800 , 600 );
const imageData = ctx. getImageData ( 0 , 0 , 800 , 600 );
processor. grayscale (imageData.data);
ctx. putImageData (imageData, 0 , 0 );
return < div >WASM Loaded</ div >;
}
Example: AssemblyScript for TypeScript developers
// Installation
npm install - D assemblyscript
// assembly/index.ts (AssemblyScript - TypeScript-like)
export function add ( a : i32 , b : i32 ) : i32 {
return a + b;
}
export function sortArray ( arr : Int32Array ) : void {
// Efficient sorting in WASM
for ( let i = 0 ; i < arr. length ; i ++ ) {
for ( let j = i + 1 ; j < arr. length ; j ++ ) {
if (arr[i] > arr[j]) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
export function processMatrix (
matrix : Float64Array ,
rows : i32 ,
cols : i32
) : Float64Array {
const result = new Float64Array (rows * cols);
for ( let i = 0 ; i < rows; i ++ ) {
for ( let j = 0 ; j < cols; j ++ ) {
const idx = i * cols + j;
result[idx] = matrix[idx] * 2.0 ;
}
}
return result;
}
// Build: npm run asbuild
// JavaScript usage
import { add, sortArray } from './build/release.js' ;
const result = add ( 5 , 10 );
console. log (result); // 15
const arr = new Int32Array ([ 5 , 2 , 8 , 1 , 9 ]);
sortArray (arr);
console. log (arr); // [1, 2, 5, 8, 9]
Example: Real-world WASM use case - Image compression
// imageCompressor.ts - React component using WASM
import { useEffect, useState } from 'react' ;
import init, { compress_image } from './wasm/image_compressor' ;
export function ImageCompressor () {
const [ wasmReady , setWasmReady ] = useState ( false );
useEffect (() => {
init (). then (() => setWasmReady ( true ));
}, []);
const handleCompress = async ( file : File ) => {
if ( ! wasmReady) return ;
const arrayBuffer = await file. arrayBuffer ();
const uint8Array = new Uint8Array (arrayBuffer);
// Call Rust WASM function
const compressed = compress_image (uint8Array, 80 ); // 80% quality
// Create download link
const blob = new Blob ([compressed], { type: 'image/jpeg' });
const url = URL . createObjectURL (blob);
const a = document. createElement ( 'a' );
a.href = url;
a.download = 'compressed.jpg' ;
a. click ();
};
return (
< div >
{wasmReady ? (
< input
type = "file"
accept = "image/*"
onChange = {( e ) => handleCompress (e.target.files[ 0 ])}
/>
) : (
< p >Loading WASM module...</ p >
)}
</ div >
);
}
// Performance comparison:
// JavaScript image compression: ~2000ms
// WASM image compression: ~200ms
// 10x faster!
Advanced Architecture Decision Matrix
Pattern
When to Use
Benefits
Complexity
Micro-Frontends
Large orgs, multiple teams, gradual migration
Independent deployment, tech diversity
High
Web Components
Design systems, framework-agnostic libraries
Standards-based, reusable everywhere
Medium
Islands Architecture
Content sites with selective interactivity
Minimal JS, excellent performance
Low-Medium
Server Components
Data-heavy apps, reducing client bundle
Zero client JS, direct backend access
Medium
Edge Computing
Global apps, low-latency requirements
Ultra-fast responses, geo-distribution
Low-Medium
WebAssembly
CPU-intensive tasks, porting existing code
Near-native performance
High
18. Progressive Web App Implementation
18.1 Service Worker Workbox Caching
Implement service workers with Google Workbox for offline functionality, caching strategies, and background
resource management.
Strategy
Use Case
Behavior
Fallback
Network First
API calls, dynamic content
Try network, fallback to cache
Stale data on offline
Cache First
Static assets (CSS, JS, images)
Serve from cache, update in background
Fast but potentially stale
Stale While Revalidate
Frequently updated content
Serve cache, fetch fresh copy
Balance speed & freshness
Network Only
Real-time data, auth requests
Always fetch from network
Fail if offline
Cache Only
Pre-cached app shell
Only serve from cache
Fast, no network needed
Example: Workbox service worker setup
// Installation
npm install workbox - webpack - plugin -- save - dev
// webpack.config.js
const WorkboxPlugin = require ( 'workbox-webpack-plugin' );
module . exports = {
plugins: [
new WorkboxPlugin. GenerateSW ({
clientsClaim: true ,
skipWaiting: true ,
runtimeCaching: [
{
urlPattern: / ^ https: \/\/ api \. example \. com/ ,
handler: 'NetworkFirst' ,
options: {
cacheName: 'api-cache' ,
expiration: {
maxEntries: 50 ,
maxAgeSeconds: 5 * 60 // 5 minutes
},
networkTimeoutSeconds: 10
}
},
{
urlPattern: / \. (?:png | jpg | jpeg | svg | gif) $ / ,
handler: 'CacheFirst' ,
options: {
cacheName: 'image-cache' ,
expiration: {
maxEntries: 60 ,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days
}
}
},
{
urlPattern: / \. (?:js | css) $ / ,
handler: 'StaleWhileRevalidate' ,
options: {
cacheName: 'static-resources'
}
}
]
})
]
};
// Register service worker in app
// index.tsx
if ( 'serviceWorker' in navigator) {
window. addEventListener ( 'load' , () => {
navigator.serviceWorker
. register ( '/service-worker.js' )
. then ( registration => {
console. log ( 'SW registered:' , registration);
// Check for updates
registration. addEventListener ( 'updatefound' , () => {
const newWorker = registration.installing;
newWorker?. addEventListener ( 'statechange' , () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available
showUpdateNotification ();
}
});
});
})
. catch ( error => {
console. error ( 'SW registration failed:' , error);
});
});
}
Example: Custom Workbox service worker
// service-worker.js
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching' ;
import { registerRoute } from 'workbox-routing' ;
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies' ;
import { ExpirationPlugin } from 'workbox-expiration' ;
import { CacheableResponsePlugin } from 'workbox-cacheable-response' ;
// Clean up old caches
cleanupOutdatedCaches ();
// Precache assets from build
precacheAndRoute (self.__WB_MANIFEST);
// Cache API responses with NetworkFirst
registerRoute (
({ url }) => url.pathname. startsWith ( '/api/' ),
new NetworkFirst ({
cacheName: 'api-cache' ,
plugins: [
new CacheableResponsePlugin ({
statuses: [ 0 , 200 ]
}),
new ExpirationPlugin ({
maxEntries: 50 ,
maxAgeSeconds: 5 * 60 ,
purgeOnQuotaError: true
})
]
})
);
// Cache images with CacheFirst
registerRoute (
({ request }) => request.destination === 'image' ,
new CacheFirst ({
cacheName: 'image-cache' ,
plugins: [
new ExpirationPlugin ({
maxEntries: 100 ,
maxAgeSeconds: 30 * 24 * 60 * 60
})
]
})
);
// Cache Google Fonts with StaleWhileRevalidate
registerRoute (
({ url }) => url.origin === 'https://fonts.googleapis.com' ,
new StaleWhileRevalidate ({
cacheName: 'google-fonts-stylesheets'
})
);
// Offline fallback page
const FALLBACK_HTML_URL = '/offline.html' ;
const CACHE_NAME = 'offline-fallbacks' ;
self. addEventListener ( 'install' , ( event ) => {
event. waitUntil (
caches. open ( CACHE_NAME ). then (( cache ) => cache. add ( FALLBACK_HTML_URL ))
);
});
self. addEventListener ( 'fetch' , ( event ) => {
if (event.request.mode === 'navigate' ) {
event. respondWith (
fetch (event.request). catch (() => {
return caches. match ( FALLBACK_HTML_URL );
})
);
}
});
Example: React hook for service worker updates
// useServiceWorker.ts
import { useEffect, useState } from 'react' ;
export function useServiceWorker () {
const [ updateAvailable , setUpdateAvailable ] = useState ( false );
const [ registration , setRegistration ] = useState < ServiceWorkerRegistration | null >( null );
useEffect (() => {
if ( 'serviceWorker' in navigator) {
navigator.serviceWorker. register ( '/service-worker.js' ). then (( reg ) => {
setRegistration (reg);
// Check for updates every hour
setInterval (() => {
reg. update ();
}, 60 * 60 * 1000 );
reg. addEventListener ( 'updatefound' , () => {
const newWorker = reg.installing;
newWorker?. addEventListener ( 'statechange' , () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
setUpdateAvailable ( true );
}
});
});
});
}
}, []);
const updateServiceWorker = () => {
if (registration?.waiting) {
registration.waiting. postMessage ({ type: 'SKIP_WAITING' });
window.location. reload ();
}
};
return { updateAvailable, updateServiceWorker };
}
// Usage in component
function App () {
const { updateAvailable , updateServiceWorker } = useServiceWorker ();
return (
< div >
{updateAvailable && (
< div className = "update-banner" >
< p >A new version is available!</ p >
< button onClick = {updateServiceWorker}>Update Now</ button >
</ div >
)}
{ /* App content */ }
</ div >
);
}
Best Practices: Use NetworkFirst for API calls, CacheFirst for static assets,
StaleWhileRevalidate for
frequently updated content. Always provide offline fallback pages. Test with DevTools offline mode.
18.2 Web App Manifest Installation
Configure Web App Manifest for installable PWA with app-like experience, custom icons, splash screens, and
display modes.
Property
Purpose
Required
Example
name
Full app name
Yes
"My Progressive Web App"
short_name
Home screen name
Yes
"My PWA"
icons
App icons (various sizes)
Yes
192x192, 512x512 PNG
start_url
Launch URL
Yes
"/?source=pwa"
display
Display mode
Yes
standalone, fullscreen
theme_color
Browser UI color
No
"#007acc"
background_color
Splash screen color
No
"#ffffff"
orientation
Screen orientation
No
portrait, landscape
Example: Complete manifest.json configuration
// public/manifest.json
{
"name" : "My Progressive Web Application" ,
"short_name" : "My PWA" ,
"description" : "A modern progressive web app with offline support" ,
"start_url" : "/?source=pwa" ,
"display" : "standalone" ,
"theme_color" : "#007acc" ,
"background_color" : "#ffffff" ,
"orientation" : "portrait-primary" ,
"icons" : [
{
"src" : "/icons/icon-72x72.png" ,
"sizes" : "72x72" ,
"type" : "image/png" ,
"purpose" : "any"
},
{
"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-152x152.png" ,
"sizes" : "152x152" ,
"type" : "image/png"
},
{
"src" : "/icons/icon-192x192.png" ,
"sizes" : "192x192" ,
"type" : "image/png" ,
"purpose" : "any maskable"
},
{
"src" : "/icons/icon-384x384.png" ,
"sizes" : "384x384" ,
"type" : "image/png"
},
{
"src" : "/icons/icon-512x512.png" ,
"sizes" : "512x512" ,
"type" : "image/png" ,
"purpose" : "any maskable"
}
],
"screenshots" : [
{
"src" : "/screenshots/desktop.png" ,
"sizes" : "1280x720" ,
"type" : "image/png" ,
"form_factor" : "wide"
},
{
"src" : "/screenshots/mobile.png" ,
"sizes" : "750x1334" ,
"type" : "image/png" ,
"form_factor" : "narrow"
}
],
"categories" : [ "productivity" , "utilities" ],
"shortcuts" : [
{
"name" : "New Document" ,
"short_name" : "New" ,
"description" : "Create a new document" ,
"url" : "/new?source=pwa" ,
"icons" : [{ "src" : "/icons/new.png" , "sizes" : "96x96" }]
},
{
"name" : "Dashboard" ,
"short_name" : "Dashboard" ,
"description" : "View your dashboard" ,
"url" : "/dashboard?source=pwa" ,
"icons" : [{ "src" : "/icons/dashboard.png" , "sizes" : "96x96" }]
}
],
"share_target" : {
"action" : "/share" ,
"method" : "POST" ,
"enctype" : "multipart/form-data" ,
"params" : {
"title" : "title" ,
"text" : "text" ,
"url" : "url" ,
"files" : [
{
"name" : "media" ,
"accept" : [ "image/*" , "video/*" ]
}
]
}
},
"protocol_handlers" : [
{
"protocol" : "web+myapp" ,
"url" : "/handle?url=%s"
}
]
}
// Add to HTML head
<link rel= "manifest" href= "/manifest.json" >
<meta name= "theme-color" content= "#007acc" >
<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= "My PWA" >
<link rel= "apple-touch-icon" href= "/icons/icon-192x192.png" >
Example: Install prompt handling with React
// useInstallPrompt.ts
import { useEffect, useState } from 'react' ;
interface BeforeInstallPromptEvent extends Event {
prompt : () => Promise < void >;
userChoice : Promise <{ outcome : 'accepted' | 'dismissed' }>;
}
export function useInstallPrompt () {
const [ installPrompt , setInstallPrompt ] = useState < BeforeInstallPromptEvent | null >( null );
const [ isInstalled , setIsInstalled ] = useState ( false );
useEffect (() => {
const handleBeforeInstall = ( e : Event ) => {
e. preventDefault ();
setInstallPrompt (e as BeforeInstallPromptEvent );
};
const handleAppInstalled = () => {
setIsInstalled ( true );
setInstallPrompt ( null );
};
window. addEventListener ( 'beforeinstallprompt' , handleBeforeInstall);
window. addEventListener ( 'appinstalled' , handleAppInstalled);
// Check if already installed
if (window. matchMedia ( '(display-mode: standalone)' ).matches) {
setIsInstalled ( true );
}
return () => {
window. removeEventListener ( 'beforeinstallprompt' , handleBeforeInstall);
window. removeEventListener ( 'appinstalled' , handleAppInstalled);
};
}, []);
const promptInstall = async () => {
if ( ! installPrompt) return false ;
await installPrompt. prompt ();
const { outcome } = await installPrompt.userChoice;
if (outcome === 'accepted' ) {
setInstallPrompt ( null );
return true ;
}
return false ;
};
return { canInstall: !! installPrompt, isInstalled, promptInstall };
}
// Usage in component
function InstallButton () {
const { canInstall , isInstalled , promptInstall } = useInstallPrompt ();
if (isInstalled) {
return < p >✓ App installed</ p >;
}
if ( ! canInstall) {
return null ;
}
return (
< button onClick = {promptInstall}>
📱 Install App
</ button >
);
}
Example: Vite PWA plugin setup
// Installation
npm install - D vite - plugin - pwa
// vite.config.ts
import { defineConfig } from 'vite' ;
import { VitePWA } from 'vite-plugin-pwa' ;
export default defineConfig ({
plugins: [
VitePWA ({
registerType: 'autoUpdate' ,
includeAssets: [ 'favicon.ico' , 'robots.txt' , 'apple-touch-icon.png' ],
manifest: {
name: 'My PWA App' ,
short_name: 'PWA' ,
description: 'Progressive Web Application' ,
theme_color: '#007acc' ,
icons: [
{
src: '/icon-192.png' ,
sizes: '192x192' ,
type: 'image/png'
},
{
src: '/icon-512.png' ,
sizes: '512x512' ,
type: 'image/png'
}
]
},
workbox: {
runtimeCaching: [
{
urlPattern: / ^ https: \/\/ api \. / ,
handler: 'NetworkFirst' ,
options: {
cacheName: 'api-cache' ,
expiration: {
maxEntries: 50 ,
maxAgeSeconds: 300
}
}
}
]
}
})
]
});
Installation Requirements: HTTPS required (except localhost). Service worker must be
registered.
Manifest must include name, icons (192x192, 512x512), and start_url. User engagement needed before install
prompt.
18.3 Push Notifications Web Push
Implement push notifications with Web Push API and service workers for re-engagement, updates, and real-time
alerts.
Component
Purpose
Technology
Required
VAPID Keys
Server authentication
Public/private key pair
Yes
Push Subscription
User permission & endpoint
PushManager API
Yes
Service Worker
Receive notifications
Push event listener
Yes
Push Server
Send notifications
web-push library
Yes
Notification
Display message
Notification API
Yes
Example: Frontend push notification setup
// usePushNotifications.ts
import { useState, useEffect } from 'react' ;
const VAPID_PUBLIC_KEY = 'YOUR_PUBLIC_VAPID_KEY' ;
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 )));
}
export function usePushNotifications () {
const [ subscription , setSubscription ] = useState < PushSubscription | null >( null );
const [ permission , setPermission ] = useState < NotificationPermission >( 'default' );
useEffect (() => {
if ( 'Notification' in window) {
setPermission (Notification.permission);
}
}, []);
const subscribe = async () => {
if ( ! ( 'serviceWorker' in navigator) || ! ( 'PushManager' in window)) {
throw new Error ( 'Push notifications not supported' );
}
// Request permission
const permission = await Notification. requestPermission ();
setPermission (permission);
if (permission !== 'granted' ) {
throw new Error ( 'Permission denied' );
}
// Get service worker registration
const registration = await navigator.serviceWorker.ready;
// Subscribe to push notifications
const sub = await registration.pushManager. subscribe ({
userVisibleOnly: true ,
applicationServerKey: urlBase64ToUint8Array ( VAPID_PUBLIC_KEY )
});
setSubscription (sub);
// Send subscription to backend
await fetch ( '/api/push/subscribe' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (sub)
});
return sub;
};
const unsubscribe = async () => {
if ( ! subscription) return ;
await subscription. unsubscribe ();
// Notify backend
await fetch ( '/api/push/unsubscribe' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ endpoint: subscription.endpoint })
});
setSubscription ( null );
};
return { subscription, permission, subscribe, unsubscribe };
}
// Usage in component
function NotificationSettings () {
const { permission , subscribe , unsubscribe , subscription } = usePushNotifications ();
const handleEnable = async () => {
try {
await subscribe ();
alert ( 'Notifications enabled!' );
} catch (error) {
console. error ( 'Failed to subscribe:' , error);
}
};
if (permission === 'denied' ) {
return < p >Notifications blocked. Enable in browser settings.</ p >;
}
return (
< div >
{subscription ? (
< button onClick = {unsubscribe}>Disable Notifications</ button >
) : (
< button onClick = {handleEnable}>Enable Notifications</ button >
)}
</ div >
);
}
Example: Service worker push event handler
// service-worker.js
self. addEventListener ( 'push' , ( event ) => {
if ( ! event.data) return ;
const data = event.data. json ();
const { title , body , icon , badge , image , tag , actions , url } = data;
const options = {
body,
icon: icon || '/icons/icon-192x192.png' ,
badge: badge || '/icons/badge-72x72.png' ,
image,
tag: tag || 'default-tag' ,
requireInteraction: false ,
vibrate: [ 200 , 100 , 200 ],
data: { url: url || '/' },
actions: actions || [
{ action: 'open' , title: 'Open' , icon: '/icons/open.png' },
{ action: 'close' , title: 'Close' , icon: '/icons/close.png' }
]
};
event. waitUntil (
self.registration. showNotification (title, options)
);
});
// Handle notification click
self. addEventListener ( 'notificationclick' , ( event ) => {
event.notification. close ();
if (event.action === 'close' ) {
return ;
}
const url = event.notification.data?.url || '/' ;
event. waitUntil (
clients. matchAll ({ type: 'window' , includeUncontrolled: true })
. then (( clientList ) => {
// Check if window is already open
for ( const client of clientList) {
if (client.url === url && 'focus' in client) {
return client. focus ();
}
}
// Open new window
if (clients.openWindow) {
return clients. openWindow (url);
}
})
);
});
// Handle notification close
self. addEventListener ( 'notificationclose' , ( event ) => {
console. log ( 'Notification closed:' , event.notification.tag);
// Track analytics
fetch ( '/api/analytics/notification-close' , {
method: 'POST' ,
body: JSON . stringify ({ tag: event.notification.tag })
});
});
Example: Backend push notification server (Node.js)
// Installation
npm install web - push
// Generate VAPID keys (run once)
const webpush = require ( 'web-push' );
const vapidKeys = webpush. generateVAPIDKeys ();
console. log ( 'Public Key:' , vapidKeys.publicKey);
console. log ( 'Private Key:' , vapidKeys.privateKey);
// server.js
const express = require ( 'express' );
const webpush = require ( 'web-push' );
const app = express ();
app. use (express. json ());
// Configure VAPID keys
webpush. setVapidDetails (
'mailto:your-email@example.com' ,
process.env. VAPID_PUBLIC_KEY ,
process.env. VAPID_PRIVATE_KEY
);
// Store subscriptions (use database in production)
const subscriptions = new Map ();
// Subscribe endpoint
app. post ( '/api/push/subscribe' , ( req , res ) => {
const subscription = req.body;
subscriptions. set (subscription.endpoint, subscription);
res. status ( 201 ). json ({ success: true });
});
// Unsubscribe endpoint
app. post ( '/api/push/unsubscribe' , ( req , res ) => {
const { endpoint } = req.body;
subscriptions. delete (endpoint);
res. json ({ success: true });
});
// Send notification
app. post ( '/api/push/send' , async ( req , res ) => {
const { title , body , url , userId } = req.body;
const payload = JSON . stringify ({
title,
body,
icon: '/icons/icon-192x192.png' ,
badge: '/icons/badge-72x72.png' ,
url,
actions: [
{ action: 'open' , title: 'View' },
{ action: 'close' , title: 'Dismiss' }
]
});
// Send to all subscriptions (or filter by userId)
const promises = Array. from (subscriptions. values ()). map ( subscription =>
webpush. sendNotification (subscription, payload)
. catch ( error => {
// Handle expired subscriptions
if (error.statusCode === 410 ) {
subscriptions. delete (subscription.endpoint);
}
console. error ( 'Push error:' , error);
})
);
await Promise . all (promises);
res. json ({ success: true , sent: promises. length });
});
app. listen ( 3000 , () => console. log ( 'Server running on port 3000' ));
Best Practices: Always ask permission contextually, not immediately on page load. Provide clear
value
proposition. Allow easy unsubscribe. Use notification tags to avoid duplicates. Handle expired subscriptions
gracefully.
18.4 Background Sync Offline Queue
Implement Background Sync API to defer actions until network connectivity is restored, ensuring reliable data
submission.
Feature
Purpose
Use Case
Browser Support
Background Sync
Retry failed requests
Form submissions, messages
Chrome, Edge
Periodic Sync
Regular background updates
News, content refresh
Limited support
SyncManager
Register sync events
Sync orchestration
Chrome, Edge
// useBackgroundSync.ts
import { useState } from 'react' ;
export function useBackgroundSync () {
const [ pending , setPending ] = useState < number >( 0 );
const queueSync = async ( tag : string , data : any ) => {
if ( ! ( 'serviceWorker' in navigator) || ! ( 'SyncManager' in window)) {
// Fallback: immediate submission
return fetch ( '/api/submit' , {
method: 'POST' ,
body: JSON . stringify (data),
headers: { 'Content-Type' : 'application/json' }
});
}
// Store data in IndexedDB
const db = await openDatabase ();
await db. add ( 'sync-queue' , { tag, data, timestamp: Date. now () });
// Register background sync
const registration = await navigator.serviceWorker.ready;
await registration.sync. register (tag);
setPending ( prev => prev + 1 );
};
return { queueSync, pending };
}
// service-worker.js
self. addEventListener ( 'sync' , ( event ) => {
if (event.tag. startsWith ( 'form-submit-' )) {
event. waitUntil ( syncFormData (event.tag));
} else if (event.tag === 'message-queue' ) {
event. waitUntil ( syncMessages ());
}
});
async function syncFormData ( tag ) {
const db = await openDatabase ();
const items = await db. getAllFromIndex ( 'sync-queue' , 'by-tag' , tag);
for ( const item of items) {
try {
const response = await fetch ( '/api/submit' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (item.data)
});
if (response.ok) {
await db. delete ( 'sync-queue' , item.id);
// Notify clients
const clients = await self.clients. matchAll ();
clients. forEach ( client => {
client. postMessage ({
type: 'SYNC_SUCCESS' ,
tag: item.tag
});
});
} else {
throw new Error ( 'Server error' );
}
} catch (error) {
console. error ( 'Sync failed:' , error);
// Will retry automatically
}
}
}
async function syncMessages () {
const db = await openDatabase ();
const messages = await db. getAll ( 'message-queue' );
for ( const message of messages) {
try {
await fetch ( '/api/messages' , {
method: 'POST' ,
body: JSON . stringify (message),
headers: { 'Content-Type' : 'application/json' }
});
await db. delete ( 'message-queue' , message.id);
} catch (error) {
console. error ( 'Message sync failed:' , error);
}
}
}
// OfflineForm.tsx
import { useState } from 'react' ;
import { useBackgroundSync } from './useBackgroundSync' ;
export function OfflineForm () {
const [ formData , setFormData ] = useState ({ name: '' , email: '' , message: '' });
const [ status , setStatus ] = useState < 'idle' | 'submitting' | 'queued' >( 'idle' );
const { queueSync } = useBackgroundSync ();
const handleSubmit = async ( e : React . FormEvent ) => {
e. preventDefault ();
setStatus ( 'submitting' );
try {
if (navigator.onLine) {
// Online: submit immediately
const response = await fetch ( '/api/contact' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (formData)
});
if (response.ok) {
alert ( 'Form submitted successfully!' );
setFormData ({ name: '' , email: '' , message: '' });
}
} else {
// Offline: queue for background sync
await queueSync ( 'form-submit-contact' , formData);
setStatus ( 'queued' );
alert ( 'You are offline. Form will be submitted when connection is restored.' );
}
} catch (error) {
// Network error: queue for sync
await queueSync ( 'form-submit-contact' , formData);
setStatus ( 'queued' );
alert ( 'Submission queued. Will retry automatically.' );
} finally {
setStatus ( 'idle' );
}
};
return (
< form onSubmit = {handleSubmit}>
< input
type = "text"
value = {formData.name}
onChange = { e => setFormData ({ ... formData, name: e.target.value })}
placeholder = "Name"
required
/>
< input
type = "email"
value = {formData.email}
onChange = { e => setFormData ({ ... formData, email: e.target.value })}
placeholder = "Email"
required
/>
< textarea
value = {formData.message}
onChange = { e => setFormData ({ ... formData, message: e.target.value })}
placeholder = "Message"
required
/>
< button type = "submit" disabled = {status === 'submitting' }>
{status === 'submitting' ? 'Submitting...' :
status === 'queued' ? 'Queued for Sync' :
'Submit' }
</ button >
</ form >
);
}
Example: Periodic Background Sync (Chrome only)
// Register periodic sync (requires user engagement)
async function registerPeriodicSync () {
const registration = await navigator.serviceWorker.ready;
if ( 'periodicSync' in registration) {
try {
await registration.periodicSync. register ( 'content-sync' , {
minInterval: 24 * 60 * 60 * 1000 // 24 hours
});
console. log ( 'Periodic sync registered' );
} catch (error) {
console. error ( 'Periodic sync registration failed:' , error);
}
}
}
// service-worker.js
self. addEventListener ( 'periodicsync' , ( event ) => {
if (event.tag === 'content-sync' ) {
event. waitUntil ( syncContent ());
}
});
async function syncContent () {
try {
// Fetch latest content
const response = await fetch ( '/api/content/latest' );
const content = await response. json ();
// Update cache
const cache = await caches. open ( 'content-cache' );
await cache. put ( '/api/content/latest' , new Response ( JSON . stringify (content)));
// Notify clients
const clients = await self.clients. matchAll ();
clients. forEach ( client => {
client. postMessage ({
type: 'CONTENT_UPDATED' ,
count: content. length
});
});
} catch (error) {
console. error ( 'Periodic sync failed:' , error);
}
}
// Unregister periodic sync
async function unregisterPeriodicSync () {
const registration = await navigator.serviceWorker.ready;
if ( 'periodicSync' in registration) {
await registration.periodicSync. unregister ( 'content-sync' );
}
}
Browser Support: Background Sync supported in Chrome, Edge, Opera. Safari and Firefox have
limited/no
support. Always implement fallback for immediate submission. Test offline scenarios thoroughly.
18.5 IndexedDB Storage Management
Use IndexedDB for client-side structured data storage with queries, indexes, and transactions for offline-first
apps.
Library
Features
API Style
Bundle Size
idb (Jake Archibald)
Promise wrapper, simple API
Modern async/await
~1KB
Dexie.js
Rich queries, relationships
Declarative
~20KB
LocalForage
localStorage-like API
Simple key-value
~8KB
PouchDB
CouchDB sync, replication
Document database
~140KB
Example: IndexedDB with idb library (recommended)
// Installation
npm install idb
// db.ts - Database setup
import { openDB, DBSchema, IDBPDatabase } from 'idb' ;
interface MyDB extends DBSchema {
products : {
key : string ;
value : {
id : string ;
name : string ;
price : number ;
category : string ;
createdAt : Date ;
};
indexes : { 'by-category' : string ; 'by-price' : number };
};
cart : {
key : string ;
value : {
productId : string ;
quantity : number ;
addedAt : Date ;
};
};
syncQueue : {
key : number ;
value : {
action : string ;
data : any ;
timestamp : number ;
};
};
}
let dbPromise : Promise < IDBPDatabase < MyDB >> | null = null ;
export function getDB () {
if ( ! dbPromise) {
dbPromise = openDB < MyDB >( 'my-pwa-db' , 1 , {
upgrade ( db ) {
// Create products store
const productStore = db. createObjectStore ( 'products' , { keyPath: 'id' });
productStore. createIndex ( 'by-category' , 'category' );
productStore. createIndex ( 'by-price' , 'price' );
// Create cart store
db. createObjectStore ( 'cart' , { keyPath: 'productId' });
// Create sync queue store
db. createObjectStore ( 'syncQueue' , { keyPath: 'id' , autoIncrement: true });
}
});
}
return dbPromise;
}
// products.ts - CRUD operations
export async function addProduct ( product : MyDB [ 'products' ][ 'value' ]) {
const db = await getDB ();
await db. add ( 'products' , product);
}
export async function getProduct ( id : string ) {
const db = await getDB ();
return db. get ( 'products' , id);
}
export async function getAllProducts () {
const db = await getDB ();
return db. getAll ( 'products' );
}
export async function getProductsByCategory ( category : string ) {
const db = await getDB ();
return db. getAllFromIndex ( 'products' , 'by-category' , category);
}
export async function updateProduct ( product : MyDB [ 'products' ][ 'value' ]) {
const db = await getDB ();
await db. put ( 'products' , product);
}
export async function deleteProduct ( id : string ) {
const db = await getDB ();
await db. delete ( 'products' , id);
}
// Advanced queries with cursor
export async function getProductsInPriceRange ( min : number , max : number ) {
const db = await getDB ();
const range = IDBKeyRange. bound (min, max);
return db. getAllFromIndex ( 'products' , 'by-price' , range);
}
// Bulk operations with transaction
export async function bulkAddProducts ( products : MyDB [ 'products' ][ 'value' ][]) {
const db = await getDB ();
const tx = db. transaction ( 'products' , 'readwrite' );
await Promise . all ([
... products. map ( product => tx.store. add (product)),
tx.done
]);
}
Example: React hook for IndexedDB
// useIndexedDB.ts
import { useState, useEffect } from 'react' ;
import { getDB } from './db' ;
import type { DBSchema } from 'idb' ;
export function useIndexedDB < T >( storeName : string ) {
const [ data , setData ] = useState < T []>([]);
const [ loading , setLoading ] = useState ( true );
const [ error , setError ] = useState < Error | null >( null );
useEffect (() => {
loadData ();
}, [storeName]);
const loadData = async () => {
try {
setLoading ( true );
const db = await getDB ();
const items = await db. getAll (storeName as any );
setData (items as T []);
setError ( null );
} catch (err) {
setError (err as Error );
} finally {
setLoading ( false );
}
};
const add = async ( item : T ) => {
try {
const db = await getDB ();
await db. add (storeName as any , item);
await loadData ();
} catch (err) {
setError (err as Error );
}
};
const update = async ( item : T ) => {
try {
const db = await getDB ();
await db. put (storeName as any , item);
await loadData ();
} catch (err) {
setError (err as Error );
}
};
const remove = async ( key : string | number ) => {
try {
const db = await getDB ();
await db. delete (storeName as any , key);
await loadData ();
} catch (err) {
setError (err as Error );
}
};
const clear = async () => {
try {
const db = await getDB ();
await db. clear (storeName as any );
setData ([]);
} catch (err) {
setError (err as Error );
}
};
return { data, loading, error, add, update, remove, clear, refresh: loadData };
}
// Usage in component
function ProductList () {
const { data : products , loading , add , remove } = useIndexedDB < Product >( 'products' );
if (loading) return < div >Loading...</ div >;
return (
< div >
< h2 >Products ({products. length })</ h2 >
< ul >
{products. map ( product => (
< li key = {product.id}>
{product.name} - ${product.price}
< button onClick = {() => remove (product.id)}>Delete</ button >
</ li >
))}
</ ul >
< button onClick = {() => add ({ id: Date. now (). toString (), name: 'New Product' , price: 99 })}>
Add Product
</ button >
</ div >
);
}
Example: Dexie.js for advanced queries
// Installation
npm install dexie
// db.ts
import Dexie, { Table } from 'dexie' ;
interface Product {
id ?: number ;
name : string ;
price : number ;
category : string ;
tags : string [];
inStock : boolean ;
}
class MyDatabase extends Dexie {
products !: Table < Product >;
constructor () {
super ( 'MyAppDB' );
this . version ( 1 ). stores ({
products: '++id, name, price, category, *tags, inStock'
// ++ = auto-increment
// * = multi-entry index (for arrays)
});
}
}
export const db = new MyDatabase ();
// Complex queries
export async function searchProducts ( query : string ) {
return db.products
. where ( 'name' )
. startsWithIgnoreCase (query)
. or ( 'category' )
. equalsIgnoreCase (query)
. toArray ();
}
export async function getInStockProducts () {
return db.products. where ( 'inStock' ). equals ( 1 ). toArray ();
}
export async function getProductsByTags ( tags : string []) {
return db.products. where ( 'tags' ). anyOf (tags). toArray ();
}
export async function getAffordableProducts ( maxPrice : number ) {
return db.products. where ( 'price' ). below (maxPrice). toArray ();
}
// Bulk operations
export async function bulkUpdate () {
await db.products. where ( 'category' ). equals ( 'Electronics' ). modify ({
inStock: false
});
}
// Transactions
export async function transferStock ( fromProductId : number , toProductId : number ) {
await db. transaction ( 'rw' , db.products, async () => {
const fromProduct = await db.products. get (fromProductId);
const toProduct = await db.products. get (toProductId);
if (fromProduct && toProduct) {
await db.products. update (fromProductId, { inStock: false });
await db.products. update (toProductId, { inStock: true });
}
});
}
// Hooks integration
import { useLiveQuery } from 'dexie-react-hooks' ;
function ProductList () {
const products = useLiveQuery (() => db.products. toArray ());
if ( ! products) return < div >Loading...</ div >;
return (
< ul >
{products. map ( p => (
< li key = {p.id}>{p.name}</ li >
))}
</ ul >
);
}
Storage Limits: IndexedDB typically allows 50% of available disk space. Chrome/Edge: ~60% of
available
storage. Firefox: ~50%. Monitor quota with navigator.storage.estimate(). Implement cleanup strategies for old
data.
Implement App Shell pattern for instant loading with cached static shell and dynamic content for optimal
perceived performance.
Component
Caching Strategy
Priority
Update Frequency
App Shell (HTML/CSS/JS)
Cache First, pre-cache
Critical
On deployment
Static Assets
Cache First
High
On version change
Dynamic Content
Network First
Medium
Real-time
API Responses
Stale While Revalidate
Medium
Background
User Data
IndexedDB
High
On change
Example: App Shell structure with React
// App shell components (always cached)
// AppShell.tsx
import { Suspense, lazy } from 'react' ;
import { Header } from './Header' ; // Pre-cached
import { Sidebar } from './Sidebar' ; // Pre-cached
import { Footer } from './Footer' ; // Pre-cached
// Dynamic content (loaded separately)
const DynamicContent = lazy (() => import ( './DynamicContent' ));
export function AppShell () {
return (
< div className = "app-shell" >
< Header />
< div className = "main-content" >
< Sidebar />
< main >
< Suspense fallback = {< SkeletonLoader />}>
< DynamicContent />
</ Suspense >
</ main >
</ div >
< Footer />
</ div >
);
}
// index.html - Minimal critical HTML
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title >My PWA</ title >
< link rel = "manifest" href = "/manifest.json" >
<!-- Inline critical CSS for instant render -->
< style >
.app-shell { display: flex; flex - direction: column; min - height: 100vh; }
.skeleton { background: linear - gradient (90deg, #f0f0f0 25 % , #e0e0e0 50 % , #f0f0f0 75 % ); }
/* ... minimal styles ... */
</ style >
</ head >
< body >
< div id = "root" >
<!-- Initial shell renders immediately from cache -->
< div class = "app-shell" >
< header class = "skeleton" ></ header >
< main class = "skeleton" ></ main >
< footer class = "skeleton" ></ footer >
</ div >
</ div >
< script type = "module" src = "/src/main.tsx" ></ script >
</ body >
</ html >
Example: Service worker for App Shell caching
// service-worker.js with Workbox
import { precacheAndRoute } from 'workbox-precaching' ;
import { registerRoute, NavigationRoute } from 'workbox-routing' ;
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies' ;
// Pre-cache app shell
const APP_SHELL_CACHE = 'app-shell-v1' ;
const appShellFiles = [
'/' ,
'/index.html' ,
'/styles/main.css' ,
'/scripts/app.js' ,
'/offline.html'
];
self. addEventListener ( 'install' , ( event ) => {
event. waitUntil (
caches. open ( APP_SHELL_CACHE ). then (( cache ) => {
return cache. addAll (appShellFiles);
})
);
self. skipWaiting ();
});
// Serve app shell for all navigation requests
registerRoute (
new NavigationRoute (
new NetworkFirst ({
cacheName: APP_SHELL_CACHE ,
plugins: [
{
cacheWillUpdate : async ({ response }) => {
// Only cache successful responses
return response.status === 200 ? response : null ;
}
}
]
})
)
);
// Cache static assets
registerRoute (
({ request }) => request.destination === 'style' ||
request.destination === 'script' ||
request.destination === 'font' ,
new CacheFirst ({
cacheName: 'static-assets-v1' ,
plugins: [
new ExpirationPlugin ({
maxEntries: 60 ,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days
})
]
})
);
// Dynamic content with network priority
registerRoute (
({ url }) => url.pathname. startsWith ( '/api/' ),
new NetworkFirst ({
cacheName: 'api-cache-v1' ,
plugins: [
new ExpirationPlugin ({
maxEntries: 50 ,
maxAgeSeconds: 5 * 60 // 5 minutes
})
]
})
);
// Images with cache priority
registerRoute (
({ request }) => request.destination === 'image' ,
new CacheFirst ({
cacheName: 'image-cache-v1' ,
plugins: [
new ExpirationPlugin ({
maxEntries: 100 ,
maxAgeSeconds: 7 * 24 * 60 * 60 // 7 days
})
]
})
);
// SkeletonLoader.tsx - Immediate UI feedback
export function SkeletonLoader () {
return (
< div className = "skeleton-container" >
< div className = "skeleton skeleton-text" style = {{ width: '60%' }} />
< div className = "skeleton skeleton-text" style = {{ width: '80%' }} />
< div className = "skeleton skeleton-text" style = {{ width: '70%' }} />
< div className = "skeleton skeleton-card" />
</ div >
);
}
// Performance monitoring
export function measureAppShellPerformance () {
if ( 'performance' in window) {
window. addEventListener ( 'load' , () => {
const perfData = performance. getEntriesByType ( 'navigation' )[ 0 ] as PerformanceNavigationTiming ;
const metrics = {
// Time to First Byte
ttfb: perfData.responseStart - perfData.requestStart,
// DOM Content Loaded
domContentLoaded: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
// Full page load
loadComplete: perfData.loadEventEnd - perfData.loadEventStart,
// App shell render (custom)
appShellRender: performance. now ()
};
// Send to analytics
console. log ( 'App Shell Performance:' , metrics);
// Report to backend
navigator. sendBeacon ( '/api/analytics/performance' , JSON . stringify (metrics));
});
}
}
// useAppShellCache.ts - React hook
import { useEffect, useState } from 'react' ;
export function useAppShellCache () {
const [ cacheStatus , setCacheStatus ] = useState < 'checking' | 'cached' | 'error' >( 'checking' );
useEffect (() => {
checkAppShellCache ();
}, []);
const checkAppShellCache = async () => {
try {
const cache = await caches. open ( 'app-shell-v1' );
const cachedRequests = await cache. keys ();
if (cachedRequests. length > 0 ) {
setCacheStatus ( 'cached' );
} else {
setCacheStatus ( 'error' );
}
} catch (error) {
setCacheStatus ( 'error' );
}
};
const clearCache = async () => {
await caches. delete ( 'app-shell-v1' );
window.location. reload ();
};
return { cacheStatus, clearCache };
}
PWA Implementation Checklist
Component
Implementation
Testing
Impact
Service Worker
Workbox with caching strategies
DevTools offline mode
Offline functionality
Web Manifest
Complete manifest.json, install prompt
Lighthouse PWA audit
Installability
Push Notifications
VAPID keys, push subscription, handlers
Push notification test
Re-engagement
Background Sync
SyncManager, offline queue
Offline form submit test
Reliability
IndexedDB
idb/Dexie for structured storage
Storage quota checks
Data persistence
App Shell
Pre-cached shell, dynamic content
Performance metrics
Fast loading
Implement Workbox caching strategies for offline support
Create comprehensive manifest.json with all required fields
Handle install prompt with user-friendly UI
Generate VAPID keys for push notifications
Request notification permission contextually
Implement push notification handlers in service worker
Set up Background Sync for offline form submissions
Configure IndexedDB with proper schema and indexes
Implement App Shell architecture with pre-caching
Test PWA with Lighthouse and offline scenarios
Monitor storage quota and implement cleanup
Add performance metrics tracking
19. Modern Development Best Practices
19.1 Clean Code Principles SOLID
Apply SOLID principles and clean code practices to write maintainable, testable, and scalable frontend
applications.
Principle
Definition
Frontend Application
Violation Example
Single Responsibility
One reason to change
Component does one thing well
Component handles UI + API + state
Open/Closed
Open for extension, closed for modification
Use composition, HOCs, hooks
Modify existing code for new features
Liskov Substitution
Subtypes must be substitutable
Consistent component interfaces
Child breaks parent's contract
Interface Segregation
Small, specific interfaces
Focused props interfaces
Large props with many optionals
Dependency Inversion
Depend on abstractions
Inject dependencies via props/context
Hard-coded dependencies
Example: Single Responsibility Principle
// ❌ Bad: Component doing too much
function UserDashboard () {
const [ user , setUser ] = useState ( null );
const [ posts , setPosts ] = useState ([]);
const [ comments , setComments ] = useState ([]);
useEffect (() => {
// Fetching data
fetch ( '/api/user' ). then ( r => r. json ()). then (setUser);
fetch ( '/api/posts' ). then ( r => r. json ()). then (setPosts);
fetch ( '/api/comments' ). then ( r => r. json ()). then (setComments);
}, []);
// Data transformation logic
const processedPosts = posts. map ( post => ({
... post,
commentCount: comments. filter ( c => c.postId === post.id). length
}));
// Rendering complex UI
return (
< div >
< header >{user?.name}</ header >
< div >{ /* Complex post list */ }</ div >
< div >{ /* Complex comment section */ }</ div >
</ div >
);
}
// ✅ Good: Separated responsibilities
// Custom hook for data fetching
function useUserData () {
const [ user , setUser ] = useState ( null );
useEffect (() => {
fetch ( '/api/user' ). then ( r => r. json ()). then (setUser);
}, []);
return user;
}
function usePosts () {
const [ posts , setPosts ] = useState ([]);
useEffect (() => {
fetch ( '/api/posts' ). then ( r => r. json ()). then (setPosts);
}, []);
return posts;
}
// Separate component for user header
function UserHeader ({ user }) {
return < header >{user?.name}</ header >;
}
// Separate component for posts
function PostList ({ posts }) {
return < div >{posts. map ( post => < PostCard key = {post.id} post = {post} />)}</ div >;
}
// Main component just orchestrates
function UserDashboard () {
const user = useUserData ();
const posts = usePosts ();
return (
< div >
< UserHeader user = {user} />
< PostList posts = {posts} />
</ div >
);
}
Example: Open/Closed Principle with composition
// ❌ Bad: Modifying component for each variation
function Button ({ type , icon , loading , disabled , onClick }) {
if (loading) {
return < button disabled >< Spinner /> Loading...</ button >;
}
if (type === 'primary' ) {
return < button className = "btn-primary" onClick = {onClick}>{icon} Click</ button >;
}
if (type === 'secondary' ) {
return < button className = "btn-secondary" onClick = {onClick}>Click</ button >;
}
// Adding new types requires modifying this component
}
// ✅ Good: Open for extension via composition
function Button ({ children , disabled , onClick , className }) {
return (
< button
className = { `btn ${ className }` }
disabled = {disabled}
onClick = {onClick}
>
{children}
</ button >
);
}
// Extend via composition without modifying Button
function PrimaryButton ({ children , ... props }) {
return < Button className = "btn-primary" { ... props}>{children}</ Button >;
}
function SecondaryButton ({ children , ... props }) {
return < Button className = "btn-secondary" { ... props}>{children}</ Button >;
}
function LoadingButton ({ loading , children , ... props }) {
return (
< Button disabled = {loading} { ... props}>
{loading ? <>< Spinner /> Loading...</> : children}
</ Button >
);
}
function IconButton ({ icon , children , ... props }) {
return (
< Button { ... props}>
{icon} {children}
</ Button >
);
}
// Easy to add new variants without touching base Button
function DangerButton ({ children , ... props }) {
return < Button className = "btn-danger" { ... props}>{children}</ Button >;
}
Example: Dependency Inversion with dependency injection
// ❌ Bad: Hard-coded dependencies
function UserList () {
const [ users , setUsers ] = useState ([]);
useEffect (() => {
// Directly coupled to specific API implementation
axios. get ( 'https://api.example.com/users' )
. then ( response => setUsers (response.data));
}, []);
return < ul >{users.map( u => < li key ={ u . id }>{u.name} </ li > )} </ ul > ;
}
// ✅ Good: Depend on abstraction (interface/contract)
// Abstract API service
interface UserService {
getUsers () : Promise < User []>;
}
// Concrete implementation
class ApiUserService implements UserService {
async getUsers () {
const response = await axios. get ( 'https://api.example.com/users' );
return response.data;
}
}
// Mock implementation for testing
class MockUserService implements UserService {
async getUsers () {
return [
{ id: 1 , name: 'John' },
{ id: 2 , name: 'Jane' }
];
}
}
// Component depends on abstraction, not concrete implementation
function UserList ({ userService } : { userService : UserService }) {
const [ users , setUsers ] = useState < User []>([]);
useEffect (() => {
userService. getUsers (). then (setUsers);
}, [userService]);
return < ul >{users.map( u => < li key ={ u . id }>{u.name} </ li > )} </ ul > ;
}
// Usage with dependency injection
function App () {
const userService = new ApiUserService (); // or MockUserService for testing
return < UserList userService ={ userService } />;
}
// Even better: Use context for DI
const ServiceContext = createContext < UserService >( new ApiUserService ());
function UserList () {
const userService = useContext (ServiceContext);
const [ users , setUsers ] = useState < User []>([]);
useEffect (() => {
userService. getUsers (). then (setUsers);
}, [userService]);
return < ul >{users.map( u => < li key ={ u . id }>{u.name} </ li > )} </ ul > ;
}
Clean Code Guidelines: Functions should be small (5-20 lines), do one thing. Names should be
descriptive.
Avoid magic numbers. Comments explain why, not what. DRY (Don't Repeat Yourself). KISS (Keep It Simple, Stupid).
19.2 Design Patterns Factory Observer
Apply proven design patterns to solve common frontend architecture challenges with reusable, maintainable
solutions.
Pattern
Purpose
Frontend Use Case
Implementation
Factory Pattern
Object creation abstraction
Component factories, form builders
Function returns components
Observer Pattern
Event-driven updates
State management, pub/sub
Subscribe/notify pattern
Singleton Pattern
Single instance
API client, config, store
Module exports instance
Strategy Pattern
Interchangeable algorithms
Payment methods, validators
Interface + implementations
Decorator Pattern
Add behavior dynamically
HOCs, wrapper components
Enhance component/function
Facade Pattern
Simplified interface
API wrappers, complex libs
Wrapper with simple API
Example: Factory Pattern for dynamic component creation
// Factory function to create form fields based on config
type FieldType = 'text' | 'email' | 'number' | 'select' | 'checkbox' ;
interface FieldConfig {
type : FieldType ;
name : string ;
label : string ;
options ?: { value : string ; label : string }[];
validation ?: any ;
}
function createFormField ( config : FieldConfig ) {
switch (config.type) {
case 'text' :
case 'email' :
case 'number' :
return < TextInput key ={ config . name } { ... config } />;
case 'select' :
return < SelectInput key ={ config . name } { ... config } />;
case 'checkbox' :
return < CheckboxInput key ={ config . name } { ... config } />;
default :
throw new Error ( `Unknown field type: ${ config . type }` );
}
}
// Form builder using factory
function DynamicForm ({ fields } : { fields : FieldConfig [] }) {
return (
< form >
{ fields . map ( field => createFormField (field))}
< button type = "submit" > Submit </ button >
</ form >
);
}
// Usage
const formConfig : FieldConfig [] = [
{ type: 'text' , name: 'name' , label: 'Name' },
{ type: 'email' , name: 'email' , label: 'Email' },
{ type: 'select' , name: 'country' , label: 'Country' , options: [
{ value: 'us' , label: 'United States' },
{ value: 'uk' , label: 'United Kingdom' }
]}
];
< DynamicForm fields = {formConfig} />
// Chart factory example
function createChart ( type : 'line' | 'bar' | 'pie' , data : any ) {
const chartComponents = {
line: LineChart,
bar: BarChart,
pie: PieChart
};
const ChartComponent = chartComponents[type];
return < ChartComponent data ={ data } />;
}
Example: Observer Pattern for event system
// Event bus implementation (Observer pattern)
class EventBus {
private events : Map < string , Set < Function >> = new Map ();
subscribe ( event : string , callback : Function ) : () => void {
if ( ! this .events. has (event)) {
this .events. set (event, new Set ());
}
this .events. get (event) ! . add (callback);
// Return unsubscribe function
return () => {
this .events. get (event)?. delete (callback);
};
}
publish ( event : string , data ?: any ) : void {
const callbacks = this .events. get (event);
if (callbacks) {
callbacks. forEach ( callback => callback (data));
}
}
clear ( event ?: string ) : void {
if (event) {
this .events. delete (event);
} else {
this .events. clear ();
}
}
}
export const eventBus = new EventBus ();
// Usage: Shopping cart example
// CartButton.tsx
function CartButton () {
const addToCart = ( product : Product ) => {
eventBus. publish ( 'cart:add' , product);
};
return < button onClick ={() => addToCart ( product )}>Add to Cart </ button > ;
}
// CartBadge.tsx - Observes cart events
function CartBadge () {
const [ count , setCount ] = useState ( 0 );
useEffect (() => {
const unsubscribe = eventBus. subscribe ( 'cart:add' , () => {
setCount ( prev => prev + 1 );
});
return unsubscribe; // Cleanup on unmount
}, []);
return < div className = "badge" >{count} </ div > ;
}
// State management with observer pattern
class Store {
private state : any ;
private listeners = new Set < Function >();
getState () {
return this .state;
}
setState ( newState : any ) {
this .state = { ... this .state, ... newState };
this . notify ();
}
subscribe ( listener : Function ) {
this .listeners. add (listener);
return () => this .listeners. delete (listener);
}
private notify () {
this .listeners. forEach ( listener => listener ( this .state));
}
}
const store = new Store ();
// React hook for store
function useStore () {
const [ state , setState ] = useState (store. getState ());
useEffect (() => {
return store. subscribe (( newState : any ) => {
setState (newState);
});
}, []);
return [state, ( newState : any ) => store. setState (newState)];
}
Example: Strategy Pattern for payment processing
// Strategy interface
interface PaymentStrategy {
processPayment ( amount : number ) : Promise < PaymentResult >;
validate () : boolean ;
}
// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
constructor ( private cardNumber : string , private cvv : string ) {}
validate () : boolean {
return this .cardNumber. length === 16 && this .cvv. length === 3 ;
}
async processPayment ( amount : number ) : Promise < PaymentResult > {
// Credit card processing logic
const response = await fetch ( '/api/payment/credit-card' , {
method: 'POST' ,
body: JSON . stringify ({ cardNumber: this .cardNumber, amount })
});
return response. json ();
}
}
class PayPalPayment implements PaymentStrategy {
constructor ( private email : string ) {}
validate () : boolean {
return / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ / . test ( this .email);
}
async processPayment ( amount : number ) : Promise < PaymentResult > {
// PayPal processing logic
const response = await fetch ( '/api/payment/paypal' , {
method: 'POST' ,
body: JSON . stringify ({ email: this .email, amount })
});
return response. json ();
}
}
class CryptoPayment implements PaymentStrategy {
constructor ( private walletAddress : string ) {}
validate () : boolean {
return this .walletAddress. length === 42 ;
}
async processPayment ( amount : number ) : Promise < PaymentResult > {
// Cryptocurrency processing logic
const response = await fetch ( '/api/payment/crypto' , {
method: 'POST' ,
body: JSON . stringify ({ wallet: this .walletAddress, amount })
});
return response. json ();
}
}
// Context that uses strategy
class PaymentProcessor {
constructor ( private strategy : PaymentStrategy ) {}
setStrategy ( strategy : PaymentStrategy ) {
this .strategy = strategy;
}
async execute ( amount : number ) : Promise < PaymentResult > {
if ( ! this .strategy. validate ()) {
throw new Error ( 'Invalid payment details' );
}
return this .strategy. processPayment (amount);
}
}
// React component using strategies
function CheckoutForm () {
const [ paymentMethod , setPaymentMethod ] = useState < 'card' | 'paypal' | 'crypto' >( 'card' );
const [ processor ] = useState ( new PaymentProcessor ( new CreditCardPayment ( '' , '' )));
const handlePayment = async ( amount : number ) => {
// Select strategy based on user choice
let strategy : PaymentStrategy ;
switch (paymentMethod) {
case 'card' :
strategy = new CreditCardPayment (cardNumber, cvv);
break ;
case 'paypal' :
strategy = new PayPalPayment (email);
break ;
case 'crypto' :
strategy = new CryptoPayment (walletAddress);
break ;
}
processor. setStrategy (strategy);
const result = await processor. execute (amount);
if (result.success) {
alert ( 'Payment successful!' );
}
};
return (
< form >
< select onChange = {(e) => setPaymentMethod (e.target.value as any )} >
< option value = "card" > Credit Card </ option >
< option value = "paypal" > PayPal </ option >
< option value = "crypto" > Cryptocurrency </ option >
</ select >
{ /* Render appropriate form fields based on strategy */ }
< button onClick = {() => handlePayment (99.99)} > Pay Now </ button >
</ form >
);
}
Pattern Overuse: Don't force patterns where they're not needed. Patterns add complexity and
abstraction.
Use them to solve specific problems, not to demonstrate knowledge. Start simple, refactor to patterns when
needed.
Define and enforce performance budgets with automated testing to prevent performance regressions in production.
Metric
Good Target
Tool
Impact
First Contentful Paint (FCP)
<1.8s
Lighthouse, WebPageTest
Perceived speed
Largest Contentful Paint (LCP)
<2.5s
Core Web Vitals
Loading performance
Time to Interactive (TTI)
<3.8s
Lighthouse
Interactivity
Total Blocking Time (TBT)
<200ms
Lighthouse
Responsiveness
Cumulative Layout Shift (CLS)
<0.1
Core Web Vitals
Visual stability
JavaScript Bundle Size
<200KB (gzipped)
Webpack Bundle Analyzer
Load time
// Installation
npm install - D @lhci / cli
// lighthouserc.json
{
"ci" : {
"collect" : {
"startServerCommand" : "npm run build && npm run preview" ,
"url" : [
"http://localhost:4173/" ,
"http://localhost:4173/products" ,
"http://localhost:4173/checkout"
],
"numberOfRuns" : 3
},
"assert" : {
"preset" : "lighthouse:recommended" ,
"assertions" : {
// Performance budgets
"first-contentful-paint" : [ "error" , { "maxNumericValue" : 1800 }],
"largest-contentful-paint" : [ "error" , { "maxNumericValue" : 2500 }],
"interactive" : [ "error" , { "maxNumericValue" : 3800 }],
"speed-index" : [ "error" , { "maxNumericValue" : 3400 }],
"total-blocking-time" : [ "error" , { "maxNumericValue" : 200 }],
"cumulative-layout-shift" : [ "error" , { "maxNumericValue" : 0.1 }],
// Resource budgets
"resource-summary:script:size" : [ "error" , { "maxNumericValue" : 204800 }], // 200KB
"resource-summary:stylesheet:size" : [ "error" , { "maxNumericValue" : 51200 }], // 50KB
"resource-summary:image:size" : [ "error" , { "maxNumericValue" : 512000 }], // 500KB
"resource-summary:total:size" : [ "error" , { "maxNumericValue" : 1048576 }], // 1MB
// Best practices
"uses-responsive-images" : "error" ,
"offscreen-images" : "error" ,
"modern-image-formats" : "warn" ,
"uses-text-compression" : "error" ,
"unused-javascript" : "warn" ,
// Accessibility
"color-contrast" : "error" ,
"heading-order" : "error" ,
"label" : "error"
}
},
"upload" : {
"target" : "temporary-public-storage"
}
}
}
// package.json scripts
{
"scripts" : {
"lhci:collect" : "lhci collect" ,
"lhci:assert" : "lhci assert" ,
"lhci:upload" : "lhci upload" ,
"lhci:full" : "lhci collect && lhci assert && lhci upload"
}
}
Example: GitHub Actions CI workflow with Lighthouse
// .github/workflows/lighthouse-ci.yml
name : Lighthouse CI
on :
pull_request :
branches : [ main ]
jobs :
lighthouse :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Setup Node
uses : actions/setup-node@v3
with :
node-version : '18'
cache : 'npm'
- name : Install dependencies
run : npm ci
- name : Build
run : npm run build
- name : Run Lighthouse CI
run : |
npm install -g @lhci/cli
lhci autorun
env :
LHCI_GITHUB_APP_TOKEN : ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name : Upload results
uses : actions/upload-artifact@v3
with :
name : lighthouse-results
path : .lighthouseci/
- name : Comment PR
uses : treosh/lighthouse-ci-action@v9
with :
urls : |
http://localhost:4173
http://localhost:4173/products
uploadArtifacts : true
temporaryPublicStorage : true
// webpack.config.js
module . exports = {
performance: {
maxAssetSize: 244000 , // 244KB
maxEntrypointSize: 244000 ,
hints: 'error' , // or 'warning'
assetFilter : function ( assetFilename ) {
// Only check JS and CSS files
return / \. (js | css) $ / . test (assetFilename);
}
},
optimization: {
splitChunks: {
chunks: 'all' ,
cacheGroups: {
vendor: {
test: / [ \\ /] node_modules [ \\ /] / ,
name: 'vendors' ,
priority: 10
},
common: {
minChunks: 2 ,
priority: 5 ,
reuseExistingChunk: true
}
}
}
}
};
// Next.js config with budgets
// next.config.js
module . exports = {
experimental: {
performanceBudgets: [
{
path: '/' ,
maxSize: {
total: 200 * 1024 , // 200KB
javascript: 150 * 1024 ,
css: 50 * 1024
}
},
{
path: '/products' ,
maxSize: {
total: 250 * 1024
}
}
]
}
};
// bundlesize package
// package.json
{
"bundlesize" : [
{
"path" : "./dist/main.*.js" ,
"maxSize" : "150 kB"
},
{
"path" : "./dist/vendor.*.js" ,
"maxSize" : "50 kB"
},
{
"path" : "./dist/styles.*.css" ,
"maxSize" : "30 kB"
}
]
}
// CI script
npm install - g bundlesize
bundlesize
Monitoring Strategy: Set budgets 20% lower than current performance to encourage optimization.
Run Lighthouse on every PR. Track Core Web Vitals in production with Real User Monitoring (RUM). Review budgets
quarterly.
19.4 Code Splitting Route Based
Implement route-based and component-based code splitting to reduce initial bundle size and improve load
performance.
Technique
When to Use
Benefits
Trade-offs
Route-based Splitting
Different pages/routes
Smaller initial bundle, faster FCP
Navigation delay
Component-based Splitting
Large components (modals, charts)
Load on demand
Complexity
Vendor Splitting
Third-party libraries
Better caching
More requests
Dynamic Import
Conditional features
Load only when needed
Network waterfall
Example: React route-based code splitting
// App.tsx - Route-based lazy loading
import { lazy, Suspense } from 'react' ;
import { BrowserRouter, Routes, Route } from 'react-router-dom' ;
// Eagerly loaded (always needed)
import { Header } from './components/Header' ;
import { Footer } from './components/Footer' ;
// Lazy loaded routes (loaded on demand)
const Home = lazy (() => import ( './pages/Home' ));
const Products = lazy (() => import ( './pages/Products' ));
const ProductDetail = lazy (() => import ( './pages/ProductDetail' ));
const Checkout = lazy (() => import ( './pages/Checkout' ));
const Dashboard = lazy (() => import ( './pages/Dashboard' ));
const Admin = lazy (() => import ( './pages/Admin' ));
// Loading fallback
function PageLoader () {
return (
< div className = "page-loader" >
< div className = "spinner" />
< p >Loading...</ p >
</ div >
);
}
export function App () {
return (
< BrowserRouter >
< Header />
< main >
< Suspense fallback = {< PageLoader />}>
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/products" element = {< Products />} />
< Route path = "/products/:id" element = {< ProductDetail />} />
< Route path = "/checkout" element = {< Checkout />} />
< Route path = "/dashboard" element = {< Dashboard />} />
< Route path = "/admin/*" element = {< Admin />} />
</ Routes >
</ Suspense >
</ main >
< Footer />
</ BrowserRouter >
);
}
// Result: Each route is a separate chunk
// home.chunk.js, products.chunk.js, checkout.chunk.js, etc.
Example: Component-based code splitting with prefetching
// Lazy load heavy components
import { lazy, Suspense } from 'react' ;
const HeavyChart = lazy (() => import ( './components/HeavyChart' ));
const VideoPlayer = lazy (() => import ( './components/VideoPlayer' ));
const RichTextEditor = lazy (() => import ( './components/RichTextEditor' ));
// Prefetch on hover for better UX
function ProductPage () {
const [ showChart , setShowChart ] = useState ( false );
const handleMouseEnter = () => {
// Prefetch chart component
import ( './components/HeavyChart' );
};
return (
< div >
< h1 >Product Analytics</ h1 >
< button
onClick = {() => setShowChart ( true )}
onMouseEnter = {handleMouseEnter}
>
Show Chart
</ button >
{showChart && (
< Suspense fallback = {< div >Loading chart...</ div >}>
< HeavyChart data = {data} />
</ Suspense >
)}
</ div >
);
}
// Conditional feature loading
function AdminPanel () {
const { user } = useAuth ();
const [ AdminTools , setAdminTools ] = useState ( null );
useEffect (() => {
if (user?.role === 'admin' ) {
// Only load admin tools for admin users
import ( './components/AdminTools' ). then ( module => {
setAdminTools (() => module .default);
});
}
}, [user]);
if ( ! user) return < Login />;
if (user.role !== 'admin' ) return < Forbidden />;
if ( ! AdminTools) return < Loading />;
return < AdminTools />;
}
// Named exports lazy loading
const { AdvancedFilters , DataExport } = lazy (() =>
import ( './components/AdvancedFeatures' ). then ( module => ({
default: {
AdvancedFilters: module .AdvancedFilters,
DataExport: module .DataExport
}
}))
);
Example: Next.js dynamic imports with loading states
// Next.js dynamic import
import dynamic from 'next/dynamic' ;
// Basic dynamic import
const DynamicComponent = dynamic (() => import ( './components/Heavy' ), {
loading : () => < p >Loading...</ p >,
ssr: false // Disable SSR for this component
});
// Import with named export
const DynamicChart = dynamic (
() => import ( './components/Charts' ). then ( mod => mod.LineChart),
{ loading : () => < ChartSkeleton /> }
);
// Conditional dynamic import
function Dashboard () {
const DynamicMap = dynamic (() => import ( './components/Map' ), {
loading : () => < MapSkeleton />,
ssr: false
});
return (
< div >
< h1 >Dashboard</ h1 >
< DynamicMap center = {[ 51.505 , - 0.09 ]} zoom = { 13 } />
</ div >
);
}
// Multiple dynamic components
const [ Modal , Tooltip , Drawer ] = [
dynamic (() => import ( './components/Modal' )),
dynamic (() => import ( './components/Tooltip' )),
dynamic (() => import ( './components/Drawer' ))
];
// Webpack magic comments for better chunk names
const ProductDetail = lazy (() =>
import (
/* webpackChunkName: "product-detail" */
/* webpackPrefetch: true */
'./pages/ProductDetail'
)
);
Best Practices: Split routes by default. Lazy load modals, charts, rich editors. Prefetch on
hover/focus.
Use loading skeletons. Keep critical path synchronous. Monitor chunk sizes with bundle analyzer.
19.5 Tree Shaking Dead Code Elimination
Eliminate unused code from bundles through proper module exports, sideEffects configuration, and build
optimizations.
Technique
Purpose
Configuration
Impact
ES6 Modules
Enable static analysis
Use import/export
Required for tree shaking
sideEffects Flag
Mark pure modules
package.json
Aggressive elimination
Named Exports
Import only what's needed
Avoid default exports
Better tree shaking
Production Mode
Enable optimizations
NODE_ENV=production
Removes dev code
Example: Proper module structure for tree shaking
// ❌ Bad: Default export with everything
// utils.js
export default {
add : ( a , b ) => a + b,
subtract : ( a , b ) => a - b,
multiply : ( a , b ) => a * b,
divide : ( a , b ) => a / b,
// ... 50 more functions
};
// Usage imports everything (no tree shaking)
import utils from './utils' ;
utils. add ( 1 , 2 );
// ✅ Good: Named exports
// utils.js
export const add = ( a , b ) => a + b;
export const subtract = ( a , b ) => a - b;
export const multiply = ( a , b ) => a * b;
export const divide = ( a , b ) => a / b;
// Usage imports only what's needed (tree shaken)
import { add } from './utils' ;
add ( 1 , 2 );
// ❌ Bad: Barrel exports without re-exports
// index.js
export * from './utils' ;
export * from './validators' ;
export * from './formatters' ;
// ✅ Good: Direct imports or selective barrel exports
// Import directly from source
import { add } from './utils/math' ;
import { validateEmail } from './validators/email' ;
// Or use selective barrel exports
// index.js
export { add, subtract } from './utils/math' ;
export { validateEmail, validatePhone } from './validators' ;
// ❌ Bad: Side effects prevent tree shaking
// module.js
import './styles.css' ; // Side effect
console. log ( 'Module loaded' ); // Side effect
export const doSomething = () => {};
// ✅ Good: Pure module
// module.js
export const doSomething = () => {};
// Import styles separately when needed
// App.tsx
import './styles.css' ;
import { doSomething } from './module' ;
Example: package.json sideEffects configuration
// package.json - Library configuration
{
"name" : "my-library" ,
"version" : "1.0.0" ,
"main" : "dist/index.js" ,
"module" : "dist/index.esm.js" ,
"sideEffects" : false , // No side effects, safe to tree shake
"exports" : {
"." : {
"import" : "./dist/index.esm.js" ,
"require" : "./dist/index.js"
},
"./utils" : {
"import" : "./dist/utils.esm.js" ,
"require" : "./dist/utils.js"
}
}
}
// Or specify files with side effects
{
"sideEffects" : [
"*.css" ,
"*.scss" ,
"./src/polyfills.js"
]
}
// Webpack configuration
module.exports = {
mode : 'production' , // Enables tree shaking
optimization : {
usedExports : true , // Mark unused exports
minimize : true , // Remove unused code
sideEffects : true // Respect package.json sideEffects
}
};
// Rollup configuration
export default {
input : 'src/index.js' ,
output : {
file : 'dist/bundle.js' ,
format : 'esm'
},
plugins : [
resolve() ,
commonjs() ,
terser() // Minify and tree shake
],
treeshake : {
moduleSideEffects : false ,
propertyReadSideEffects : false
}
};
Example: Optimizing library imports for tree shaking
// ❌ Bad: Import entire library
import _ from 'lodash' ; // Imports all of lodash (~70KB)
import * as MUI from '@mui/material' ; // Imports everything
import moment from 'moment' ; // Imports full moment.js
_. debounce (fn, 300 );
const button = < MUI.Button />;
moment (). format ();
// ✅ Good: Import specific functions/components
import debounce from 'lodash/debounce' ; // Only debounce (~2KB)
import Button from '@mui/material/Button' ; // Only Button component
import dayjs from 'dayjs' ; // Smaller alternative to moment
debounce (fn, 300 );
const button = < Button />;
dayjs (). format ();
// ✅ Better: Use libraries with good tree shaking
// lodash-es (ES modules version)
import { debounce, throttle } from 'lodash-es' ;
// date-fns (tree-shakeable by default)
import { format, addDays } from 'date-fns' ;
// ✅ Best: Babel plugin for automatic optimization
// .babelrc
{
"plugins" : [
[ "import" , {
"libraryName" : "lodash" ,
"libraryDirectory" : "" ,
"camel2DashComponentName" : false
}],
[ "import" , {
"libraryName" : "@mui/material" ,
"libraryDirectory" : "" ,
"camel2DashComponentName" : false
}]
]
}
// Write normal imports, babel transforms them
import { Button, TextField } from '@mui/material' ;
// Becomes:
// import Button from '@mui/material/Button';
// import TextField from '@mui/material/TextField';
// Next.js optimized imports (built-in)
// next.config.js
module . exports = {
modularizeImports: {
'@mui/material' : {
transform: '@mui/material/{{member}}'
},
'lodash' : {
transform: 'lodash/{{member}}'
}
}
};
Common Pitfalls: CommonJS modules can't be tree shaken. Barrel exports (index.js) can prevent
tree shaking.
Side effects (CSS imports, global mutations) prevent elimination. Always analyze bundle with
webpack-bundle-analyzer.
19.6 Bundle Optimization Webpack Rollup
Optimize production bundles with advanced techniques: minification, compression, code splitting, and caching
strategies.
Optimization
Technique
Tool
Benefit
Minification
Remove whitespace, shorten names
Terser, esbuild
30-50% size reduction
Compression
Gzip, Brotli
compression-webpack-plugin
60-80% size reduction
Code Splitting
Separate vendor/async chunks
SplitChunksPlugin
Better caching
Content Hashing
Filename based on content
[contenthash]
Long-term caching
Scope Hoisting
Flatten module scope
ModuleConcatenationPlugin
Smaller bundle, faster
Example: Advanced Webpack optimization configuration
// webpack.config.js - Production optimization
const TerserPlugin = require ( 'terser-webpack-plugin' );
const CompressionPlugin = require ( 'compression-webpack-plugin' );
const BundleAnalyzerPlugin = require ( 'webpack-bundle-analyzer' ).BundleAnalyzerPlugin;
module . exports = {
mode: 'production' ,
output: {
filename: '[name].[contenthash].js' ,
chunkFilename: '[name].[contenthash].chunk.js' ,
path: path. resolve (__dirname, 'dist' ),
clean: true ,
publicPath: '/assets/'
},
optimization: {
minimize: true ,
minimizer: [
new TerserPlugin ({
terserOptions: {
compress: {
drop_console: true , // Remove console.log
drop_debugger: true ,
pure_funcs: [ 'console.info' , 'console.debug' ]
},
mangle: {
safari10: true
},
format: {
comments: false
}
},
extractComments: false ,
parallel: true
})
],
// Split chunks for better caching
splitChunks: {
chunks: 'all' ,
cacheGroups: {
// Vendor libraries
vendor: {
test: / [ \\ /] node_modules [ \\ /] / ,
name: 'vendors' ,
priority: 10 ,
reuseExistingChunk: true
},
// Common code shared across chunks
common: {
minChunks: 2 ,
priority: 5 ,
reuseExistingChunk: true ,
enforce: true
},
// Large libraries in separate chunks
react: {
test: / [ \\ /] node_modules [ \\ /] (react | react-dom) [ \\ /] / ,
name: 'react' ,
priority: 20
},
// UI libraries
mui: {
test: / [ \\ /] node_modules [ \\ /] @mui [ \\ /] / ,
name: 'mui' ,
priority: 15
}
},
maxAsyncRequests: 30 ,
maxInitialRequests: 30 ,
minSize: 20000 ,
maxSize: 244000
},
// Runtime chunk for better caching
runtimeChunk: {
name: 'runtime'
},
// Module IDs based on path
moduleIds: 'deterministic'
},
plugins: [
// Gzip compression
new CompressionPlugin ({
filename: '[path][base].gz' ,
algorithm: 'gzip' ,
test: / \. (js | css | html | svg) $ / ,
threshold: 10240 ,
minRatio: 0.8
}),
// Brotli compression (better than gzip)
new CompressionPlugin ({
filename: '[path][base].br' ,
algorithm: 'brotliCompress' ,
test: / \. (js | css | html | svg) $ / ,
compressionOptions: {
level: 11
},
threshold: 10240 ,
minRatio: 0.8
}),
// Bundle analysis
new BundleAnalyzerPlugin ({
analyzerMode: 'static' ,
openAnalyzer: false ,
reportFilename: 'bundle-report.html'
})
],
performance: {
maxAssetSize: 244000 ,
maxEntrypointSize: 244000 ,
hints: 'warning'
}
};
Example: Vite optimization configuration
// vite.config.ts
import { defineConfig } from 'vite' ;
import react from '@vitejs/plugin-react' ;
import { visualizer } from 'rollup-plugin-visualizer' ;
import compression from 'vite-plugin-compression' ;
export default defineConfig ({
plugins: [
react (),
// Gzip compression
compression ({
algorithm: 'gzip' ,
ext: '.gz'
}),
// Brotli compression
compression ({
algorithm: 'brotliCompress' ,
ext: '.br'
}),
// Bundle analysis
visualizer ({
open: false ,
filename: 'dist/stats.html' ,
gzipSize: true ,
brotliSize: true
})
],
build: {
target: 'es2020' ,
minify: 'terser' ,
terserOptions: {
compress: {
drop_console: true ,
drop_debugger: true
}
},
// Chunk splitting
rollupOptions: {
output: {
manualChunks: {
// Vendor chunks
'react-vendor' : [ 'react' , 'react-dom' , 'react-router-dom' ],
'ui-vendor' : [ '@mui/material' , '@emotion/react' ],
'utils' : [ 'lodash-es' , 'date-fns' ]
},
// Chunk naming with content hash
chunkFileNames: 'assets/[name].[hash].js' ,
entryFileNames: 'assets/[name].[hash].js' ,
assetFileNames: 'assets/[name].[hash].[ext]'
}
},
// Chunk size warnings
chunkSizeWarningLimit: 500 ,
// Source maps for production debugging
sourcemap: 'hidden' ,
// CSS code splitting
cssCodeSplit: true ,
// Asset inlining threshold
assetsInlineLimit: 4096
},
// Optimize dependencies
optimizeDeps: {
include: [ 'react' , 'react-dom' ],
exclude: [ '@vite/client' , '@vite/env' ]
}
});
Example: Advanced bundle analysis and optimization
// Analyze bundle with webpack-bundle-analyzer
npm install -D webpack-bundle-analyzer
// Run analysis
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json
// Identify optimization opportunities:
// 1. Large dependencies (replace with lighter alternatives )
// 2. Duplicate code (adjust splitChunks )
// 3. Unused imports (tree shaking issues )
// 4. Large assets (optimize images, fonts )
// Source map explorer (alternative)
npm install -D source-map-explorer
// package.json
{
"scripts" : {
"analyze" : "source-map-explorer 'dist/*.js'"
}
}
// Optimization strategies based on analysis:
// 1. Replace large libraries
// moment.js (67KB) → dayjs ( 2KB )
// lodash (71KB) → lodash-es + tree shaking
// MaterialUI → lighter alternative or selective imports
// 2. Dynamic imports for large features
const Chart = lazy (() = > import (
/* webpackChunkName: "chart" * /
/* webpackPrefetch: true * /
'./components/Chart'
));
// 3. Optimize images
// Use next/image or webpack image-loader
{
test : / \. ( png | jpg | jpeg )$ /,
type : 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB inline limit
}
},
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { progressive: true , quality: 65 },
optipng: { enabled: true },
pngquant: { quality: [0.65, 0.90], speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 }
}
}
]
}
// 4. Preload critical chunks
< link rel = "preload" href = "/assets/main.js" as = "script" >
< link rel = "prefetch" href = "/assets/chart.chunk.js" as = "script" >
// 5. Enable HTTP/2 server push
// In your server configuration
Link: < /assets/main.j s > ; rel = preload ; as = script
Link: < /assets/styles.cs s > ; rel = preload ; as = style
Bundle Optimization Checklist
Optimization
Target
Tool
Expected Reduction
Minification
JS, CSS, HTML
Terser, cssnano
30-40%
Compression
All text files
Gzip, Brotli
60-80%
Tree Shaking
Unused code
Webpack, Rollup
20-50%
Code Splitting
Routes, vendors
Dynamic imports
Initial: 40-60%
Image Optimization
PNG, JPG, SVG
imagemin, WebP
50-70%
Font Subsetting
Web fonts
glyphhanger
70-90%
Apply SOLID principles in component design
Use composition over inheritance
Implement dependency injection for testability
Apply design patterns appropriately (Factory, Observer, Strategy)
Set up Lighthouse CI in pipeline
Define performance budgets for all metrics
Implement route-based code splitting
Lazy load heavy components and modals
Configure sideEffects in package.json
Use named exports for better tree shaking
Enable Terser minification in production
Configure Gzip and Brotli compression
Analyze bundles with webpack-bundle-analyzer
Optimize vendor chunk splitting
Use content hashing for long-term caching
Monitor bundle size in CI pipeline
20. Deployment Production Implementation
20.1 Vercel Netlify Jamstack Hosting
Deploy frontend applications to modern hosting platforms with automatic builds, CDN distribution, and
serverless
functions.
Platform
Best For
Key Features
Pricing
Vercel
Next.js, React apps
Edge network, ISR, serverless
Free hobby, $20+/mo pro
Netlify
Static sites, Jamstack
Form handling, split testing
Free starter, $19+/mo pro
Cloudflare Pages
Static sites, workers
Unlimited bandwidth, fast CDN
Free unlimited
AWS Amplify
Full-stack apps
Backend integration, auth
Pay per build
GitHub Pages
Static sites, docs
Free hosting, Jekyll support
Free
Example: Vercel deployment configuration
// vercel.json
{
"version" : 2 ,
"builds" : [
{
"src" : "package.json" ,
"use" : "@vercel/next"
}
],
"routes" : [
{
"src" : "/api/(.*)" ,
"dest" : "/api/$1"
},
{
"src" : "/(.*)" ,
"dest" : "/$1"
}
],
"env" : {
"NEXT_PUBLIC_API_URL" : "https://api.example.com" ,
"DATABASE_URL" : "@database_url"
},
"headers" : [
{
"source" : "/(.*)" ,
"headers" : [
{
"key" : "X-Content-Type-Options" ,
"value" : "nosniff"
},
{
"key" : "X-Frame-Options" ,
"value" : "DENY"
},
{
"key" : "X-XSS-Protection" ,
"value" : "1; mode=block"
}
]
},
{
"source" : "/static/(.*)" ,
"headers" : [
{
"key" : "Cache-Control" ,
"value" : "public, max-age=31536000, immutable"
}
]
}
],
"redirects" : [
{
"source" : "/old-page" ,
"destination" : "/new-page" ,
"permanent" : true
}
],
"rewrites" : [
{
"source" : "/blog/:path*" ,
"destination" : "https://blog.example.com/:path*"
}
]
}
// Deploy via CLI
npm install -g vercel
vercel login
vercel --prod
// Deploy via Git integration (automatic)
// Push to main branch → auto-deploy to production
// Push to other branches → preview deployments
// Environment variables (via Vercel Dashboard or CLI)
vercel env add NEXT_PUBLIC_API_URL production
vercel env add DATABASE_URL production
// Preview URLs for each PR
// https://my-app-git-feature-branch-username.vercel.app
Example: Netlify deployment configuration
// netlify.toml
[build]
base = "/"
publish = "dist"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
NPM_FLAGS = "--legacy-peer-deps"
# Redirect rules
[[redirects]]
from = "/old-url"
to = "/new-url"
status = 301
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"
status = 200 # SPA fallback
# Headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# Functions (serverless)
[functions]
directory = "netlify/functions"
node_bundler = "esbuild"
# Dev server
[dev]
command = "npm run dev"
port = 3000
publish = "dist"
# Deploy contexts
[context.production]
environment = { NODE_ENV = "production" }
[context.deploy-preview]
environment = { NODE_ENV = "staging" }
[context.branch-deploy]
environment = { NODE_ENV = "development" }
# Form handling (Netlify feature)
# In HTML:
< form name = "contact" method = "POST" data-netlify = "true" >
< input type="text" name="name" / >
< input type="email" name="email" / >
< button type="submit" > Submit < /butto n >
< /form >
# Deploy via CLI
npm install -g netlify-cli
netlify login
netlify init
netlify deploy --prod
Example: Cloudflare Pages deployment
// wrangler.toml (for Pages with Functions )
name = "my-app"
compatibility_date = "2024-01-01"
[build]
command = "npm run build"
cwd = "."
watch_dir = "src"
[[build.upload]]
format = "service-worker"
main = "./functions/[[path]].js"
[env.production]
[env.production.vars]
API_URL = "https://api.example.com"
# _redirects file (in public folder)
/old-url /new-url 301
/api/* https://api.example.com/:splat 200
/* /index.html 200
# _headers file
/*
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode = block
X-Content-Type-Options: nosniff
/static/*
Cache-Control: public, max-age=31536000, immutable
# Cloudflare Pages Function
// functions/api/hello.ts
export async function onRequest( context ) {
const { request, env, params } = context ;
return new Response ( JSON.stringify( {
message: 'Hello from Cloudflare Pages',
time: new Date () .toISOString ()
}) , {
headers: { 'Content-Type': 'application/json' }
});
}
# Deploy via CLI
npm install -g wrangler
wrangler login
wrangler pages publish dist --project-name=my-app
# Or use Git integration (automatic)
# Connect GitHub repo → automatic deployments
Platform Selection: Vercel for Next.js apps with edge functions. Netlify for static sites with
forms and split testing.
Cloudflare Pages for unlimited bandwidth. All provide automatic HTTPS, CDN, preview deployments, and Git
integration.
20.2 Docker Container Frontend Apps
Containerize frontend applications with Docker for consistent deployments across environments and orchestration
with Kubernetes.
Strategy
Use Case
Benefits
Complexity
Multi-stage Build
Production optimization
Smaller images, faster builds
Medium
Nginx Serving
Static files, reverse proxy
Fast, efficient, caching
Low
Node.js SSR
Next.js, server rendering
Dynamic content, APIs
Medium
Docker Compose
Multi-container setup
Local dev, testing
Medium
Example: Multi-stage Dockerfile for React app
# Dockerfile - Multi-stage build for production
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package * .json ./
# Install dependencies
RUN npm ci --only=production && \
npm cache clean --force
# Copy source code
COPY . .
# Build app
RUN npm run build
# Stage 2: Production
FROM nginx:alpine AS production
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets from builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Add healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
# Expose port
EXPOSE 80
# Start nginx
CMD [ "nginx" , "-g", "daemon off;"]
# Build and run
# docker build -t my-app:latest .
# docker run -p 8080:80 my-app:latest
Example: Nginx configuration for SPA
# nginx.conf
server {
listen 80 ;
server_name localhost ;
root /usr/share/nginx/html ;
index index.html ;
# Gzip compression
gzip on ;
gzip_vary on ;
gzip_min_length 1024 ;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/javascript application/json ;
# 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 ;
add_header Referrer-Policy "strict-origin-when-cross-origin" always ;
# Cache static assets
location ~ * \. ( js | css | png | jpg | jpeg | gif | ico | svg | woff | woff2 | ttf | eot )$ {
expires 1y ;
add_header Cache-Control "public, immutable" ;
access_log off ;
}
# API proxy (optional)
location /api {
proxy_pass http://backend-service:3000 ;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade' ;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# SPA fallback - serve index.html for all routes
location / {
try_files $uri $uri / /index.html ;
}
# Health check endpoint
location /health {
access_log off ;
return 200 "healthy\n" ;
add_header Content-Type text/plain ;
}
# Deny access to hidden files
location ~ / \. {
deny all ;
access_log off ;
log_not_found off ;
}
}
Example: Next.js SSR Dockerfile
# Dockerfile for Next.js
FROM node:18-alpine AS deps
WORKDIR /app
COPY package * .json ./
RUN npm ci
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build Next.js app
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set correct permissions
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD [ "node" , "server.js"]
# next.config.js - Enable standalone output
module.exports = {
output: 'standalone'
}
Example: Docker Compose for full-stack development
# docker-compose.yml
version : '3.8'
services :
frontend :
build :
context : ./frontend
dockerfile : Dockerfile
ports :
- "3000:80"
environment :
- REACT_APP_API_URL=http://localhost:4000
depends_on :
- backend
networks :
- app-network
volumes :
- ./frontend/src:/app/src # Hot reload in dev
backend :
build :
context : ./backend
dockerfile : Dockerfile
ports :
- "4000:4000"
environment :
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- NODE_ENV=development
depends_on :
- db
networks :
- app-network
db :
image : postgres:15-alpine
ports :
- "5432:5432"
environment :
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes :
- postgres-data:/var/lib/postgresql/data
networks :
- app-network
nginx :
image : nginx:alpine
ports :
- "80:80"
volumes :
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on :
- frontend
- backend
networks :
- app-network
networks :
app-network :
driver : bridge
volumes :
postgres-data :
# Commands
# docker-compose up -d
# docker-compose down
# docker-compose logs -f frontend
# docker-compose exec frontend sh
Security Best Practices: Use multi-stage builds to reduce image size. Run as non-root user.
Scan
images for
vulnerabilities with docker scan. Use .dockerignore to exclude unnecessary files. Pin base image versions.
20.3 CDN CloudFront Asset Optimization
Distribute static assets globally with CDN for faster load times, reduced latency, and improved user experience
worldwide.
CDN Provider
Key Features
Best For
Pricing Model
CloudFront (AWS)
Global edge, Lambda@Edge
AWS ecosystem integration
Pay per request
Cloudflare
DDoS protection, workers
Security, free tier
Free + paid plans
Fastly
Instant purge, VCL
Real-time apps
Pay as you go
Akamai
Largest network
Enterprise scale
Enterprise pricing
# main.tf - CloudFront + S3 setup
resource "aws_s3_bucket" "frontend" {
bucket = "my-app-frontend"
}
resource "aws_s3_bucket_public_access_block" "frontend" {
bucket = aws_s3_bucket.frontend.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_cloudfront_origin_access_identity" "frontend" {
comment = "OAI for frontend bucket"
}
resource "aws_s3_bucket_policy" "frontend" {
bucket = aws_s3_bucket.frontend.id
policy = jsonencode ({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontAccess"
Effect = "Allow"
Principal = {
AWS = aws_cloudfront_origin_access_identity.frontend.iam_arn
}
Action = "s3:GetObject"
Resource = "${ aws_s3_bucket . frontend . arn }/*"
}
]
})
}
resource "aws_cloudfront_distribution" "frontend" {
enabled = true
is_ipv6_enabled = true
comment = "Frontend distribution"
default_root_object = "index.html"
price_class = "PriceClass_100"
aliases = [ "www.example.com" , "example.com"]
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "S3-Frontend"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = [ "GET" , "HEAD", "OPTIONS"]
cached_methods = [ "GET" , "HEAD"]
target_origin_id = "S3-Frontend"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
compress = true
}
# Cache behavior for static assets
ordered_cache_behavior {
path_pattern = "/static/*"
allowed_methods = [ "GET" , "HEAD"]
cached_methods = [ "GET" , "HEAD"]
target_origin_id = "S3-Frontend"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 31536000
default_ttl = 31536000
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
# SPA fallback
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/index.html"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = {
Environment = "production"
}
}
// Cache control strategy for different asset types
// 1. HTML files - No cache (always fresh)
Cache - Control : no - cache, no - store, must - revalidate
// Or short cache with revalidation
Cache - Control : max - age = 0 , must - revalidate
// 2. JavaScript/CSS with hash in filename
// main.a1b2c3d4.js, styles.e5f6g7h8.css
Cache - Control : public, max - age = 31536000 , immutable
// 3. Images, fonts with hash
// logo.i9j0k1l2.png
Cache - Control : public, max - age = 31536000 , immutable
// 4. API responses
Cache - Control : max - age = 60 , s - maxage = 300 , stale -while- revalidate = 86400
// 5. Dynamic content
Cache - Control : private, max - age = 0 , must - revalidate
// Next.js automatic cache headers
// next.config.js
module . exports = {
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Cache-Control' ,
value: 'public, max-age=0, must-revalidate'
}
]
},
{
source: '/_next/static/:path*' ,
headers: [
{
key: 'Cache-Control' ,
value: 'public, max-age=31536000, immutable'
}
]
},
{
source: '/images/:path*' ,
headers: [
{
key: 'Cache-Control' ,
value: 'public, max-age=86400, s-maxage=604800'
}
]
}
];
}
};
// Webpack output with content hash
output : {
filename : '[name].[contenthash].js' ,
chunkFilename : '[name].[contenthash].chunk.js'
}
// CloudFront cache invalidation
aws cloudfront create - invalidation \
-- distribution - id E1234ABCD5678 \
-- paths "/*"
// Selective invalidation (cheaper)
aws cloudfront create - invalidation \
-- distribution - id E1234ABCD5678 \
-- paths "/index.html" "/manifest.json"
Example: Cloudflare Workers for edge optimization
// workers/optimize-images.js
export default {
async fetch ( request , env ) {
const url = new URL (request.url);
// Check if WebP is supported
const acceptHeader = request.headers. get ( 'Accept' ) || '' ;
const supportsWebP = acceptHeader. includes ( 'image/webp' );
// Transform image format at edge
if (url.pathname. match ( / \. (jpg | png) $ / )) {
const imageRequest = new Request (request);
if (supportsWebP) {
// Serve WebP version
const webpUrl = url.pathname. replace ( / \. (jpg | png) $ / , '.webp' );
return fetch ( new Request (webpUrl, imageRequest));
}
}
// Add custom headers
const response = await fetch (request);
const newResponse = new Response (response.body, response);
// Cache static assets aggressively
if (url.pathname. startsWith ( '/static/' )) {
newResponse.headers. set ( 'Cache-Control' , 'public, max-age=31536000, immutable' );
}
// Security headers
newResponse.headers. set ( 'X-Content-Type-Options' , 'nosniff' );
newResponse.headers. set ( 'X-Frame-Options' , 'DENY' );
return newResponse;
}
};
// wrangler.toml
name = "image-optimizer"
main = "workers/optimize-images.js"
compatibility_date = "2024-01-01"
[env.production]
route = "example.com/*"
CDN Best Practices: Use content hashing for immutable assets. Set aggressive cache headers for
static files.
Invalidate cache selectively. Enable compression (Gzip/Brotli). Use HTTP/2 push for critical resources. Monitor
CDN hit ratio.
Choose appropriate hosting platform (Vercel/Netlify/Cloudflare)
Configure automatic deployments from Git
Set up preview deployments for PRs
Create multi-stage Dockerfile for production
Configure Nginx with proper caching and security headers
Set up Docker Compose for local development
20.4 Environment Variables Config
Manage environment-specific configuration securely across development, staging, and production environments.
Approach
Use Case
Security
Complexity
.env Files
Local development
Low (gitignored)
Low
Platform Secrets
Vercel/Netlify secrets
High (encrypted)
Low
AWS Secrets Manager
Production secrets
High (encrypted, rotated)
Medium
HashiCorp Vault
Enterprise secrets
Very High
High
Example: Environment variables setup with validation
// .env.example (commit to git)
NEXT_PUBLIC_API_URL = https : //api.example.com
NEXT_PUBLIC_GA_ID = G - XXXXXXXXXX
DATABASE_URL = postgresql : //localhost:5432/mydb
JWT_SECRET = your - secret - key - here
STRIPE_SECRET_KEY = sk_test_xxx
// .env.local (gitignored - actual values)
NEXT_PUBLIC_API_URL = http : //localhost:4000
NEXT_PUBLIC_GA_ID = G - 123456789
DATABASE_URL = postgresql : //user:pass@localhost:5432/mydb_dev
JWT_SECRET = super - secret - dev - key
STRIPE_SECRET_KEY = sk_test_actual_key
// .env.production (production values - use secrets manager)
NEXT_PUBLIC_API_URL = https : //api.production.com
NEXT_PUBLIC_GA_ID = G - PROD123456
DATABASE_URL = @database - url - secret
JWT_SECRET = @jwt - secret
STRIPE_SECRET_KEY = @stripe - secret
// .gitignore
.env * .local
.env.production
// config/env.ts - Type-safe environment variables
import { z } from 'zod' ;
const envSchema = z. object ({
// Public variables (available in browser)
NEXT_PUBLIC_API_URL: z. string (). url (),
NEXT_PUBLIC_GA_ID: z. string (). optional (),
// Server-only variables
DATABASE_URL: z. string (),
JWT_SECRET: z. string (). min ( 32 ),
STRIPE_SECRET_KEY: z. string (). startsWith ( 'sk_' ),
// Environment
NODE_ENV: z. enum ([ 'development' , 'staging' , 'production' ]). default ( 'development' )
});
// Validate and export
const _env = envSchema. safeParse (process.env);
if ( ! _env.success) {
console. error ( '❌ Invalid environment variables:' , _env.error. format ());
throw new Error ( 'Invalid environment variables' );
}
export const env = _env.data;
// Usage
import { env } from '@/config/env' ;
console. log (env. NEXT_PUBLIC_API_URL ); // Type-safe access
console. log (env. JWT_SECRET ); // Only available server-side
Example: Multiple environment configurations
// config/environments.ts
interface AppConfig {
apiUrl : string ;
environment : 'development' | 'staging' | 'production' ;
features : {
analytics : boolean ;
errorTracking : boolean ;
debugMode : boolean ;
};
endpoints : {
api : string ;
auth : string ;
cdn : string ;
};
}
const development : AppConfig = {
apiUrl: 'http://localhost:4000' ,
environment: 'development' ,
features: {
analytics: false ,
errorTracking: false ,
debugMode: true
},
endpoints: {
api: 'http://localhost:4000/api' ,
auth: 'http://localhost:4000/auth' ,
cdn: 'http://localhost:3000'
}
};
const staging : AppConfig = {
apiUrl: 'https://api-staging.example.com' ,
environment: 'staging' ,
features: {
analytics: true ,
errorTracking: true ,
debugMode: true
},
endpoints: {
api: 'https://api-staging.example.com/api' ,
auth: 'https://api-staging.example.com/auth' ,
cdn: 'https://cdn-staging.example.com'
}
};
const production : AppConfig = {
apiUrl: 'https://api.example.com' ,
environment: 'production' ,
features: {
analytics: true ,
errorTracking: true ,
debugMode: false
},
endpoints: {
api: 'https://api.example.com/api' ,
auth: 'https://api.example.com/auth' ,
cdn: 'https://cdn.example.com'
}
};
const configs = { development, staging, production };
export const config = configs[process.env. NODE_ENV || 'development' ];
// Feature flags
export const isProduction = config.environment === 'production' ;
export const isDevelopment = config.environment === 'development' ;
export const isStaging = config.environment === 'staging' ;
// Usage
import { config, isProduction } from '@/config/environments' ;
if (isProduction) {
initAnalytics (config.endpoints.api);
}
Example: AWS Secrets Manager integration
// Installation
npm install @aws - sdk / client - secrets - manager
// lib/secrets.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' ;
const client = new SecretsManagerClient ({
region: process.env. AWS_REGION || 'us-east-1'
});
export async function getSecret ( secretName : string ) : Promise < any > {
try {
const command = new GetSecretValueCommand ({
SecretId: secretName
});
const response = await client. send (command);
if (response.SecretString) {
return JSON . parse (response.SecretString);
}
// Binary secret
const buff = Buffer. from (response.SecretBinary as Uint8Array );
return buff. toString ( 'ascii' );
} catch (error) {
console. error ( 'Error retrieving secret:' , error);
throw error;
}
}
// Cache secrets in memory
const secretsCache = new Map < string , any >();
export async function getCachedSecret ( secretName : string ) : Promise < any > {
if (secretsCache. has (secretName)) {
return secretsCache. get (secretName);
}
const secret = await getSecret (secretName);
secretsCache. set (secretName, secret);
// Refresh after 1 hour
setTimeout (() => {
secretsCache. delete (secretName);
}, 60 * 60 * 1000 );
return secret;
}
// Usage in Next.js API route
export async function GET () {
const secrets = await getCachedSecret ( 'prod/api/keys' );
const response = await fetch (secrets. API_URL , {
headers: {
'Authorization' : `Bearer ${ secrets . API_KEY }`
}
});
return Response. json ( await response. json ());
}
// Terraform to create secret
resource "aws_secretsmanager_secret" "api_keys" {
name = "prod/api/keys"
description = "API keys for production"
}
resource "aws_secretsmanager_secret_version" "api_keys" {
secret_id = aws_secretsmanager_secret.api_keys.id
secret_string = jsonencode ({
API_URL = "https://api.example.com"
API_KEY = "secret-key-value"
STRIPE_KEY = "sk_live_xxx"
})
}
Security Best Practices: Never commit secrets to git. Use .env.example for documentation.
Rotate
secrets
regularly. Use different secrets per environment. Prefix public variables with NEXT_PUBLIC_ or VITE_. Validate
all
env vars.
20.5 Blue-Green Canary Deployment
Implement zero-downtime deployment strategies with instant rollback capabilities and gradual traffic shifting.
Strategy
Approach
Risk
Rollback Time
Blue-Green
Switch all traffic instantly
Medium
Instant
Canary
Gradual traffic shift (5%→50%→100%)
Low
Quick
Rolling Update
Replace instances one by one
Low
Minutes
A/B Testing
Split traffic by user segment
Low
N/A (experimental)
Example: Kubernetes Blue-Green deployment
# blue-deployment.yaml (current production)
apiVersion : apps/v1
kind : Deployment
metadata :
name : frontend-blue
labels :
app : frontend
version : blue
spec :
replicas : 3
selector :
matchLabels :
app : frontend
version : blue
template :
metadata :
labels :
app : frontend
version : blue
spec :
containers :
- name : frontend
image : myapp/frontend:v1.0.0
ports :
- containerPort : 80
resources :
requests :
memory : "128Mi"
cpu : "100m"
limits :
memory : "256Mi"
cpu : "200m"
---
# green-deployment.yaml (new version)
apiVersion : apps/v1
kind : Deployment
metadata :
name : frontend-green
labels :
app : frontend
version : green
spec :
replicas : 3
selector :
matchLabels :
app : frontend
version : green
template :
metadata :
labels :
app : frontend
version : green
spec :
containers :
- name : frontend
image : myapp/frontend:v2.0.0 # New version
ports :
- containerPort : 80
resources :
requests :
memory : "128Mi"
cpu : "100m"
limits :
memory : "256Mi"
cpu : "200m"
---
# service.yaml
apiVersion : v1
kind : Service
metadata :
name : frontend-service
spec :
selector :
app : frontend
version : blue # Initially pointing to blue
ports :
- protocol : TCP
port : 80
targetPort : 80
type : LoadBalancer
# Deployment process:
# 1. Deploy green version
kubectl apply -f green-deployment.yaml
# 2. Test green version internally
kubectl port-forward deployment/frontend-green 8080:80
# 3. Switch traffic to green (instant cutover)
kubectl patch service frontend-service -p '{"spec":{"selector":{"version":"green"}}}'
# 4. Monitor for issues
kubectl logs -f deployment/frontend-green
# 5. If issues, rollback to blue instantly
kubectl patch service frontend-service -p '{"spec":{"selector":{"version":"blue"}}}'
# 6. If successful, delete blue deployment
kubectl delete deployment frontend-blue
Example: Canary deployment with traffic splitting
# Using Istio for canary deployment
# virtual-service.yaml
apiVersion : networking.istio.io/v1beta1
kind : VirtualService
metadata :
name : frontend
spec :
hosts :
- frontend.example.com
http :
- match :
- headers :
canary :
exact : "true"
route :
- destination :
host : frontend
subset : v2
- route :
- destination :
host : frontend
subset : v1
weight : 95
- destination :
host : frontend
subset : v2
weight : 5 # 5% traffic to canary
---
# destination-rule.yaml
apiVersion : networking.istio.io/v1beta1
kind : DestinationRule
metadata :
name : frontend
spec :
host : frontend
subsets :
- name : v1
labels :
version : v1
- name : v2
labels :
version : v2
# Canary deployment script
#!/bin/bash
# Stage 1: Deploy v2 with 5% traffic
kubectl apply -f canary-deployment.yaml
kubectl apply -f virtual-service-5.yaml
# Wait and monitor
sleep 300
# Stage 2: Increase to 25%
kubectl apply -f virtual-service-25.yaml
sleep 300
# Stage 3: Increase to 50%
kubectl apply -f virtual-service-50.yaml
sleep 300
# Stage 4: Increase to 100%
kubectl apply -f virtual-service-100.yaml
# Remove old version
kubectl delete deployment frontend-v1
# Using Flagger for automated canary
# flagger-canary.yaml
apiVersion : flagger.app/v1beta1
kind : Canary
metadata :
name : frontend
spec :
targetRef :
apiVersion : apps/v1
kind : Deployment
name : frontend
service :
port : 80
analysis :
interval : 1m
threshold : 10
maxWeight : 50
stepWeight : 5
metrics :
- name : request-success-rate
thresholdRange :
min : 99
interval : 1m
- name : request-duration
thresholdRange :
max : 500
interval : 1m
webhooks :
- name : load-test
url : http://flagger-loadtester/
metadata :
cmd : "hey -z 1m -q 10 -c 2 http://frontend-canary/"
Example: Vercel deployment with preview URLs
// Vercel automatic preview deployments
// Every PR gets a unique preview URL
// .github/workflows/preview.yml
name : Preview Deployment
on :
pull_request :
branches : [ main ]
jobs :
deploy-preview :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Deploy to Vercel
uses : amondnet/vercel-action@v20
with :
vercel-token : ${{ secrets.VERCEL_TOKEN }}
vercel-org-id : ${{ secrets.ORG_ID }}
vercel-project-id : ${{ secrets.PROJECT_ID }}
scope : ${{ secrets.VERCEL_SCOPE }}
- name : Comment PR
uses : actions/github-script@v6
with :
script : |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 Preview deployed to: https://my-app-pr-${{ github.event.number }}.vercel.app'
})
// Netlify branch deploys
// netlify.toml
[ context.deploy-preview ]
command = "npm run build"
environment = { NODE_ENV = "staging" }
[ context.branch-deploy ]
command = "npm run build"
# Each branch gets: https://branch-name--my-app.netlify.app
# Each PR gets: https://deploy-preview-123--my-app.netlify.app
// Progressive rollout with feature flags
import { useFeatureFlag } from '@/lib/feature-flags';
function NewFeature() {
const isEnabled = useFeatureFlag('new-ui-redesign', {
rollout : 10 // Start with 10% of users
} );
if (!isEnabled) {
return <OldFeature />;
}
return <RedesignedFeature />;
}
// Gradually increase rollout
// Day 1 : 10%
// Day 3 : 25%
// Day 7 : 50%
// Day 14 : 100%
Deployment Strategy Selection: Blue-Green for instant rollback with no downtime. Canary for
gradual rollout
with monitoring. Rolling update for resource-constrained environments. Always test in staging first.
20.6 Monitoring Sentry Performance Tracking
Implement comprehensive monitoring and error tracking to detect issues early and maintain application health in
production.
Tool
Purpose
Key Features
Pricing
Sentry
Error tracking, performance
Source maps, releases, alerts
Free + paid tiers
Datadog
Full observability
APM, logs, metrics, traces
$15+/host/month
New Relic
Application monitoring
Real user monitoring (RUM)
Free + paid
LogRocket
Session replay
Video replay, console logs
$99+/month
Google Analytics 4
User analytics
Events, conversions, funnels
Free
Example: Sentry integration with source maps
// Installation
npm install @sentry / nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs' ;
Sentry. init ({
dsn: process.env. NEXT_PUBLIC_SENTRY_DSN ,
environment: process.env. NODE_ENV ,
// Performance Monitoring
tracesSampleRate: 1.0 ,
// Session Replay
replaysSessionSampleRate: 0.1 ,
replaysOnErrorSampleRate: 1.0 ,
integrations: [
new Sentry. BrowserTracing ({
tracePropagationTargets: [
'localhost' ,
/ ^ https: \/\/ api \. example \. com/
]
}),
new Sentry. Replay ({
maskAllText: true ,
blockAllMedia: true
})
],
// Filter sensitive data
beforeSend ( event , hint ) {
// Remove PII
if (event.request) {
delete event.request.cookies;
delete event.request.headers;
}
// Ignore certain errors
if (event.exception) {
const error = hint.originalException;
if (error && error.message?. includes ( 'ResizeObserver' )) {
return null ; // Don't send this error
}
}
return event;
}
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs' ;
Sentry. init ({
dsn: process.env. SENTRY_DSN ,
tracesSampleRate: 1.0
});
// next.config.js - Source maps
const { withSentryConfig } = require ( '@sentry/nextjs' );
module . exports = withSentryConfig (
{
// Your Next.js config
},
{
silent: true ,
org: 'my-org' ,
project: 'my-project' ,
// Upload source maps
authToken: process.env. SENTRY_AUTH_TOKEN ,
// Release tracking
release: process.env. VERCEL_GIT_COMMIT_SHA ,
// Source map options
widenClientFileUpload: true ,
hideSourceMaps: true ,
disableLogger: true
}
);
// Usage in components
import * as Sentry from '@sentry/nextjs' ;
function MyComponent () {
const handleError = () => {
try {
throw new Error ( 'Something went wrong' );
} catch (error) {
Sentry. captureException (error, {
tags: {
component: 'MyComponent' ,
action: 'button-click'
},
extra: {
userId: user.id,
timestamp: Date. now ()
}
});
}
};
return < button onClick = {handleError}>Trigger Error</ button >;
}
// lib/monitoring.ts
import * as Sentry from '@sentry/nextjs' ;
// Track custom metrics
export function trackMetric ( name : string , value : number , tags ?: Record < string , string >) {
Sentry.metrics. gauge (name, value, {
tags,
unit: 'millisecond'
});
}
// Track API performance
export async function monitoredFetch ( url : string , options ?: RequestInit ) {
const startTime = performance. now ();
const transaction = Sentry. startTransaction ({
name: `API ${ options ?. method || 'GET'} ${ url }` ,
op: 'http.client'
});
try {
const response = await fetch (url, options);
const duration = performance. now () - startTime;
// Track success
trackMetric ( 'api.response_time' , duration, {
endpoint: url,
status: String (response.status),
method: options?.method || 'GET'
});
if ( ! response.ok) {
Sentry. captureMessage ( `API Error: ${ response . status } ${ url }` , 'warning' );
}
transaction. setHttpStatus (response.status);
transaction. finish ();
return response;
} catch (error) {
const duration = performance. now () - startTime;
// Track failure
trackMetric ( 'api.error_count' , 1 , {
endpoint: url,
error: error.message
});
Sentry. captureException (error, {
tags: { endpoint: url },
extra: { duration }
});
transaction. setStatus ( 'internal_error' );
transaction. finish ();
throw error;
}
}
// React Error Boundary with Sentry
import { ErrorBoundary } from '@sentry/nextjs' ;
function App () {
return (
< ErrorBoundary
fallback = {({ error, resetError }) => (
<div>
<h1>Something went wrong</h1>
<p>{error.message}</p>
<button onClick = {resetError} > Try again </ button >
</div>
)}
showDialog
>
< YourApp />
</ ErrorBoundary >
);
}
// Core Web Vitals tracking
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals' ;
function sendToAnalytics ( metric ) {
// Send to Sentry
Sentry.metrics. gauge (metric.name, metric.value, {
tags: {
rating: metric.rating
},
unit: metric.name === 'CLS' ? 'ratio' : 'millisecond'
});
// Send to Google Analytics
gtag ( 'event' , metric.name, {
value: Math. round (metric.value),
metric_id: metric.id,
metric_rating: metric.rating
});
}
getCLS (sendToAnalytics);
getFID (sendToAnalytics);
getFCP (sendToAnalytics);
getLCP (sendToAnalytics);
getTTFB (sendToAnalytics);
Example: Comprehensive monitoring dashboard setup
// Datadog RUM integration
import { datadogRum } from '@datadog/browser-rum' ;
datadogRum. init ({
applicationId: process.env. NEXT_PUBLIC_DD_APP_ID ! ,
clientToken: process.env. NEXT_PUBLIC_DD_CLIENT_TOKEN ! ,
site: 'datadoghq.com' ,
service: 'my-app' ,
env: process.env. NODE_ENV ,
version: process.env. NEXT_PUBLIC_APP_VERSION ,
sessionSampleRate: 100 ,
sessionReplaySampleRate: 20 ,
trackUserInteractions: true ,
trackResources: true ,
trackLongTasks: true ,
defaultPrivacyLevel: 'mask-user-input'
});
// Custom monitoring service
class MonitoringService {
private static instance : MonitoringService ;
private constructor () {
this . initHealthChecks ();
}
static getInstance () {
if ( ! MonitoringService.instance) {
MonitoringService.instance = new MonitoringService ();
}
return MonitoringService.instance;
}
// Track page views
trackPageView ( page : string ) {
gtag ( 'event' , 'page_view' , { page_path: page });
Sentry. addBreadcrumb ({
category: 'navigation' ,
message: `Navigated to ${ page }` ,
level: 'info'
});
}
// Track custom events
trackEvent ( name : string , properties ?: Record < string , any >) {
gtag ( 'event' , name, properties);
Sentry. addBreadcrumb ({
category: 'user-action' ,
message: name,
data: properties,
level: 'info'
});
}
// Track errors
trackError ( error : Error , context ?: Record < string , any >) {
console. error (error);
Sentry. captureException (error, {
extra: context
});
}
// Health checks
private initHealthChecks () {
setInterval (() => {
this . checkApiHealth ();
this . checkPerformance ();
}, 60000 ); // Every minute
}
private async checkApiHealth () {
try {
const response = await fetch ( '/api/health' );
if ( ! response.ok) {
this . trackError ( new Error ( 'API health check failed' ), {
status: response.status
});
}
} catch (error) {
this . trackError (error as Error , { check: 'api-health' });
}
}
private checkPerformance () {
const memory = (performance as any ).memory;
if (memory && memory.usedJSHeapSize > 100 * 1024 * 1024 ) {
Sentry. captureMessage ( 'High memory usage detected' , {
level: 'warning' ,
extra: {
usedHeapSize: memory.usedJSHeapSize,
totalHeapSize: memory.totalJSHeapSize
}
});
}
}
}
export const monitoring = MonitoringService. getInstance ();
// Usage
monitoring. trackEvent ( 'button_click' , { button: 'checkout' });
monitoring. trackError ( new Error ( 'Payment failed' ), { amount: 99.99 });
Production Deployment Checklist
Category
Task
Priority
Status
Hosting
Configure CDN and caching
High
☐
Security
Set up HTTPS and security headers
Critical
☐
Monitoring
Integrate error tracking (Sentry)
Critical
☐
Performance
Track Core Web Vitals
High
☐
CI/CD
Automated deployment pipeline
High
☐
Config
Environment variables properly set
Critical
☐
Deployment
Blue-green or canary strategy
Medium
☐
Backup
Rollback plan documented
High
☐
Choose appropriate hosting platform (Vercel/Netlify/Cloudflare)
Configure automatic deployments from Git
Set up preview deployments for PRs
Create multi-stage Dockerfile for production
Configure Nginx with proper caching and security headers
Set up Docker Compose for local development
Configure CDN with CloudFront or Cloudflare
Implement proper cache control headers
Set up environment variables validation
Use secrets manager for sensitive data
Implement blue-green or canary deployment
Configure automated rollback strategy
Integrate Sentry for error tracking
Set up performance monitoring
Track Core Web Vitals in production
Configure alerts for critical errors
Document deployment and rollback procedures
Test deployment pipeline in staging