Cross-site scripting remains one of the most critical web security vulnerabilities, requiring comprehensive defense strategies that combine multiple layers of protection to safeguard user data and application integrity.
1. Problem Statement & Requirements
Clarifying questions
- What specific types of user input are we protecting against (HTML injection, JavaScript injection, DOM-based XSS)?
- Are there any existing content sanitization libraries in our tech stack that we should leverage?
- What's the expected scale of content processing (e.g., 100s vs 1000s of user-generated content items)?
- Do we need to support rich text editing or just plain text input?
- Are there any specific regulatory or compliance requirements (e.g., GDPR, HIPAA) that impact our XSS strategy?
- What's the expected browser support matrix (e.g., IE11, mobile browsers)?
- Is there a content moderation workflow or admin dashboard that needs to be protected?
Functional requirements
- Input sanitization - All user-generated content must be sanitized to prevent malicious script execution
- Output encoding - Rendered content must be properly encoded for safe HTML output
- Rich text support - Allow safe formatting while preventing script injection (e.g., bold, italics, links)
- Content validation - Validate content against defined safe character sets and formats
- Admin dashboard protection - Ensure admin interfaces are protected from XSS attacks
Non-functional requirements
- Performance impact - Sanitization should not introduce noticeable latency (max 50ms per content item)
- Security coverage - 100% protection against known XSS vectors (DOM, reflected, stored)
- Maintainability - Sanitization logic should be centralized and easily auditable
- Extensibility - Strategy should support future XSS vector additions without major refactoring
Constraints and assumptions
- Assumption: All user input comes from authenticated users with appropriate permissions
- Constraint: Cannot rely on backend-only sanitization for client-side rendered content
- Assumption: Content will be displayed in modern browsers (ES6+ support)
- Constraint: Must support real-time content rendering without full page reloads
- Assumption: Content will be primarily in English with limited international character support
Out of scope
- Server-side XSS protection - This is a client-side XSS prevention design
- Third-party library integration - We're designing the core strategy, not integrating existing libraries
- Content moderation workflows - Content approval and review processes are outside this scope
- Mobile-specific XSS vectors - Focus on web browser XSS prevention only
- Cross-origin resource attacks - Same-origin policy enforcement is assumed to be handled elsewhere
2. Component Architecture
Component tree diagram (Mermaid)
graph TD
A[ContentRenderer] --> B[SanitizedContent]
A --> C[RichTextEditor]
C --> D[ContentInput]
C --> E[FormattingToolbar]
A --> F[AdminDashboard]
F --> G[UserContentList]
G --> H[ContentItem]
H --> I[ContentPreview]
H --> J[ContentActions]
Breakdown of smart (container) vs. dumb (presentational) components
Smart Components:
ContentRenderer- Manages content lifecycle and sanitization stateRichTextEditor- Handles rich text input with formatting toolbar integrationAdminDashboard- Container for content management workflows
Dumb Components:
SanitizedContent- Pure rendering of sanitized HTML contentContentInput- Simple text input field with validationFormattingToolbar- UI controls for content formattingContentPreview- Read-only view of formatted contentContentActions- Action buttons (edit, delete, approve)
TypeScript props contracts
// ContentRenderer.tsx
interface ContentRendererProps {
content: string;
contentType?: 'plain' | 'html' | 'markdown';
sanitizeOptions?: SanitizeOptions;
onContentChange?: (content: string) => void;
}
// RichTextEditor.tsx
interface RichTextEditorProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
maxLength?: number;
allowedFormats?: ('bold' | 'italic' | 'link' | 'list')[];
onFormatChange?: (format: string) => void;
}
// ContentItem.tsx
interface ContentItemProps {
id: string;
content: string;
author: string;
timestamp: Date;
status: 'draft' | 'published' | 'moderated';
actions?: ContentAction[];
onAction?: (action: ContentAction, contentId: string) => void;
}
type SanitizeOptions = {
allowedTags?: string[];
allowedAttributes?: Record<string, string[]>;
allowedSchemes?: string[];
allowComments?: boolean;
escapeHtml?: boolean;
};
type ContentAction = {
id: string;
label: string;
icon?: string;
color?: 'primary' | 'secondary' | 'danger';
};
UI state machine diagram
stateDiagram-v2
[*] --> Idle
Idle --> Editing : StartEdit
Editing --> Saving : SaveContent
Saving --> Saved : Success
Saving --> Error : Failure
Saved --> Idle : Reset
Error --> Editing : RetryEdit
Editing --> Cancelled : CancelEdit
Cancelled --> Idle : Reset
Component responsibilities and props contract
ContentRenderer:
- Responsible for sanitizing and rendering user content
- Manages content lifecycle state transitions
- Props:
content,contentType,sanitizeOptions,onContentChange
RichTextEditor:
- Handles rich text input with formatting capabilities
- Validates content against security policies
- Props:
value,onChange,placeholder,maxLength,allowedFormats
ContentItem:
- Displays individual content items with actions
- Manages content status and lifecycle
- Props:
id,content,author,timestamp,status,actions
Reusability decisions
- Highly reusable: SanitizedContent component for consistent rendering
- Medium reusability: ContentInput with validation logic across multiple forms
- Low reusability: RichTextEditor - specific to content creation workflows
Component lifecycle considerations
- ContentRenderer: Mount → Sanitize → Render → Unmount
- RichTextEditor: Mount → Initialize formatting → Handle user input → Save content
- ContentItem: Mount → Fetch content → Render → Handle actions → Unmount
3. State Management Design
Local state vs. global state decisions
Local state:
- Editor input state in RichTextEditor
- Component-specific UI states (editing, saving, error)
- Temporary content previews
Global state:
- User session and permissions (for admin dashboard)
- Content moderation queue
- Cache of previously sanitized content
State management approach
Selected approach: React Context API with custom hooks + Zustand for complex state
Justification:
- Context API: For session and permissions that need to be available across the admin dashboard
- Zustand: For complex content state management and caching with minimal boilerplate
- React Query: For server-side content fetching and caching (separate from XSS concerns)
State shape and normalization strategy
interface ContentState {
content: string;
sanitizedContent: string;
status: 'idle' | 'editing' | 'saving' | 'saved' | 'error';
errors: string[];
metadata: {
author: string;
timestamp: Date;
version: number;
};
}
interface XSSProtectionState {
sanitizedCache: Map<string, string>;
pendingContent: Map<string, ContentState>;
contentValidationRules: ValidationRule[];
}
Server state vs. client state separation
Client state:
- Editor content and formatting
- UI states (editing, saving)
- Local content previews
Server state:
- Actual content stored in database
- Content moderation status
- User permissions and roles
Cache invalidation strategy
- Content versioning: Increment version number on content changes
- Time-based invalidation: Cache entries expire after 1 hour
- Manual invalidation: Clear cache when content is published or moderated
- Event-driven invalidation: Invalidate when content status changes
Optimistic updates design
// When user edits content, we show optimistic update immediately
const handleContentChange = (newContent: string) => {
// Optimistically update UI
setLocalContent(newContent);
// Then send to server with proper validation
const sanitized = sanitizeContent(newContent);
saveToServer(sanitized).catch(error => {
// Revert to previous content on failure
setLocalContent(previousContent);
});
};
Critical-path hook implementation
// useXSSProtection.ts
import { useState, useCallback, useEffect } from 'react';
import { create } from 'zustand';
interface XSSProtectionStore {
sanitizedCache: Map<string, string>;
pendingContent: Map<string, string>;
addContentToCache: (key: string, content: string) => void;
getSanitizedContent: (key: string) => string | null;
clearCache: () => void;
}
const useXSSProtectionStore = create<XSSProtectionStore>((set) => ({
sanitizedCache: new Map(),
pendingContent: new Map(),
addContentToCache: (key, content) => {
set((state) => {
const newCache = new Map(state.sanitizedCache);
newCache.set(key, content);
return { sanitizedCache: newCache };
});
},
getSanitizedContent: (key) => {
return useXSSProtectionStore.getState().sanitizedCache.get(key) || null;
},
clearCache: () => {
set({ sanitizedCache: new Map(), pendingContent: new Map() });
}
}));
interface UseXSSProtectionReturn {
sanitizeContent: (content: string, options?: SanitizeOptions) => string;
isContentSafe: (content: string) => boolean;
cachedSanitizedContent: (key: string) => string | null;
cacheContent: (key: string, content: string) => void;
clearCache: () => void;
}
export const useXSSProtection = (): UseXSSProtectionReturn => {
const [sanitizedContent, setSanitizedContent] = useState<string>('');
const [isContentSafe, setIsContentSafe] = useState<boolean>(true);
const sanitizeContent = useCallback((content: string, options?: SanitizeOptions): string => {
// Implementation details here
const sanitized = sanitizeHTML(content, options);
setSanitizedContent(sanitized);
return sanitized;
}, []);
const cachedSanitizedContent = useCallback((key: string): string | null => {
return useXSSProtectionStore.getState().sanitizedCache.get(key) || null;
}, []);
const cacheContent = useCallback((key: string, content: string) => {
useXSSProtectionStore.getState().addContentToCache(key, content);
}, []);
const clearCache = useCallback(() => {
useXSSProtectionStore.getState().clearCache();
}, []);
return {
sanitizeContent,
isContentSafe,
cachedSanitizedContent,
cacheContent,
clearCache
};
};
// Sanitization utility function
const sanitizeHTML = (html: string, options?: SanitizeOptions): string => {
// Implementation using DOMPurify or similar
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'a'],
ALLOWED_ATTR: ['href', 'title', 'target'],
...options
});
};
4. Routing & Navigation
Route structure and hierarchy
// Main route structure for content management system
const routes = [
{
path: '/admin',
component: AdminDashboard,
children: [
{
path: 'content',
component: ContentManagement,
children: [
{ path: 'list', component: ContentList },
{ path: 'create', component: ContentCreate },
{ path: 'edit/:id', component: ContentEdit },
{ path: 'preview/:id', component: ContentPreview }
]
},
{
path: 'moderation',
component: ModerationDashboard,
children: [
{ path: 'pending', component: PendingContent },
{ path: 'approved', component: ApprovedContent }
]
}
]
},
{
path: '/public',
component: PublicContent,
children: [
{ path: 'article/:id', component: ArticleView },
{ path: 'blog', component: BlogList }
]
}
];
Code splitting strategy
Route-based code splitting:
/admin/content- Split into separate chunks for content management/public/article/:id- Split for individual article rendering/admin/moderation- Separate chunk for moderation workflows
Justification:
- Admin dashboard is only accessed by authenticated users, so it can be lazy-loaded
- Public content pages should load quickly for all users
- Content creation/editing components can be chunked separately from read-only views
Navigation guards and authentication flows
// Authentication guard for admin routes
const AdminRouteGuard = ({ children }: { children: React.ReactNode }) => {
const { isAuthenticated, user } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
// Check permissions for admin content
if (user.role !== 'admin' && user.role !== 'moderator') {
return <Navigate to="/unauthorized" replace />;
}
return children;
};
// XSS protection guard for content rendering
const ContentSecurityGuard = ({ children }: { children: React.ReactNode }) => {
const [isContentSafe, setIsContentSafe] = useState(true);
useEffect(() => {
// Check content security on mount
const checkContentSecurity = async () => {
try {
// Validate content security policies
const securityCheck = await validateContentSecurity();
setIsContentSafe(securityCheck);
} catch (error) {
setIsContentSafe(false);
}
};
checkContentSecurity();
}, []);
if (!isContentSafe) {
return <div>Content security violation detected</div>;
}
return children;
};
Deep linking support and URL parameter design
// URL structure for content management
const contentRoutes = {
list: '/admin/content/list',
create: '/admin/content/create',
edit: (id: string) => `/admin/content/edit/${id}`,
preview: (id: string) => `/admin/content/preview/${id}`,
view: (id: string) => `/public/article/${id}`
};
// Query parameters for content filtering
const contentFilters = {
status: 'status',
author: 'author',
dateRange: 'dateRange',
search: 'search'
};
Browser history management and scroll restoration
// Custom scroll restoration hook
const useScrollRestoration = () => {
useEffect(() => {
const handleScroll = () => {
sessionStorage.setItem('scrollPosition', window.scrollY.toString());
};
window.addEventListener('scroll', handleScroll);
// Restore scroll position on navigation
const savedPosition = sessionStorage.getItem('scrollPosition');
if (savedPosition) {
window.scrollTo(0, parseInt(savedPosition));
}
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
};
5. API Integration Layer
API client design pattern
// apiClient.ts - Custom fetch wrapper with XSS protection
class SecureAPIClient {
private baseUrl: string;
private token: string | null = null;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
setToken(token: string) {
this.token = token;
}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
}
});
return this.processResponse<T>(response);
}
async post<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify(data)
});
return this.processResponse<T>(response);
}
private async processResponse<T>(response: Response): Promise<T> {
const raw = await response.json();
// XSS protection - sanitize response content
if (raw.content) {
raw.content = this.sanitizeContent(raw.content);
}
return raw as T;
}
private sanitizeContent(content: string): string {
// Apply DOMPurify or similar sanitization
return DOMPurify.sanitize(content);
}
}
Request/response interceptors
// requestInterceptor.ts - Auth token injection and XSS protection
const requestInterceptor = async (config: any) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Sanitize request content if needed
if (config.data && typeof config.data === 'object') {
const sanitized = sanitizeRequestContent(config.data);
config.data = sanitized;
}
return config;
};
// responseInterceptor.ts - Response normalization and error handling
const responseInterceptor = async (response: any) => {
// Normalize responses to standard format
const normalizedResponse = await normalizeResponse(response);
// Apply XSS protection to response content
if (normalizedResponse.data && normalizedResponse.data.content) {
normalizedResponse.data.content = sanitizeContent(
normalizedResponse.data.content
);
}
return normalizedResponse;
};
const sanitizeRequestContent = (data: any): any => {
// Sanitize content fields in request data
if (typeof data === 'string') {
return DOMPurify.sanitize(data);
}
if (Array.isArray(data)) {
return data.map(item => sanitizeRequestContent(item));
}
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const [key, value] of Object.entries(data)) {
sanitized[key] = sanitizeRequestContent(value);
}
return sanitized;
}
return data;
};
Error handling and retry strategy
// errorHandling.ts - Comprehensive error handling with XSS awareness
class XssAwareErrorHandler {
static handleAPIError(error: any): void {
// Log error with context
console.error('API Error:', {
message: error.message,
status: error.status,
timestamp: new Date().toISOString(),
// Don't log raw content that might contain XSS
context: 'XSS-aware error handling'
});
// Show user-friendly error message
if (error.status === 403) {
showUserError('Access denied - content security violation');
} else if (error.status === 429) {
showUserError('Rate limit exceeded');
} else {
showUserError('An error occurred while processing your request');
}
}
static async retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let lastError: any;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
}
Loading, error, and empty states design
// contentState.ts - State management for content operations
interface ContentState {
loading: boolean;
error: string | null;
data: any | null;
empty: boolean;
}
const useContentState = (): ContentState => {
const [state, setState] = useState<ContentState>({
loading: false,
error: null,
data: null,
empty: false
});
const setLoading = (loading: boolean) => {
setState(prev => ({ ...prev, loading }));
};
const setError = (error: string | null) => {
setState(prev => ({ ...prev, error }));
};
const setData = (data: any) => {
setState(prev => ({
...prev,
data,
empty: !data || (Array.isArray(data) && data.length === 0)
}));
};
return { ...state, setLoading, setError, setData };
};
Data fetching strategy
CSR (Client-Side Rendering) with React Query:
// contentQueries.ts - React Query integration for content fetching
import { useQuery, useMutation } from '@tanstack/react-query';
const fetchContent = async (id: string): Promise<Content> => {
const response = await apiClient.get<Content>(`/content/${id}`);
// Ensure content is sanitized on fetch
return {
...response,
content: DOMPurify.sanitize(response.content)
};
};
const useContentQuery = (id: string) => {
return useQuery({
queryKey: ['content', id],
queryFn: () => fetchContent(id),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 2,
retryDelay: 1000
});
};
const useContentMutation = () => {
return useMutation({
mutationFn: async (content: Content) => {
// Sanitize content before sending to server
const sanitizedContent = DOMPurify.sanitize(content.content);
return apiClient.post<Content>('/content', {
...content,
content: sanitizedContent
});
},
onSuccess: (data) => {
// Invalidate cache on successful save
queryClient.invalidateQueries(['content']);
}
});
};
API response transformation layer
// contentTransformer.ts - DTO to domain model mapping with XSS protection
interface ContentDTO {
id: string;
content: string;
author: string;
status: 'draft' | 'published' | 'moderated';
createdAt: string;
updatedAt: string;
}
interface Content {
id: string;
content: string;
author: string;
status: 'draft' | 'published' | 'moderated';
createdAt: Date;
updatedAt: Date;
}
const transformContentDTO = (dto: ContentDTO): Content => {
return {
id: dto.id,
content: DOMPurify.sanitize(dto.content),
author: dto.author,
status: dto.status,
createdAt: new Date(dto.createdAt),
updatedAt: new Date(dto.updatedAt)
};
};
const transformContentListDTO = (dtos: ContentDTO[]): Content[] => {
return dtos.map(transformContentDTO);
};
Sequence diagram for primary data flows
sequenceDiagram
participant User
participant Editor
participant API
participant Store
User->>Editor: Edit content
Editor->>Store: Update local state
Editor->>API: Save content (sanitized)
API->>Store: Return sanitized response
Store->>Editor: Update UI with sanitized content
Editor->>User: Show success message
User->>Editor: View content
Editor->>API: Fetch content
API->>Store: Return content (sanitized)
Store->>Editor: Render sanitized content
Editor->>User: Display content safely
6. Performance Design
Core Web Vitals targets
- LCP (Largest Contentful Paint): < 2.5s for content rendering
- INP (Interaction Nearness): < 200ms for user interactions
- CLS (Cumulative Layout Shift): < 0.1 for content display stability
Bundle size budget per route
- Admin Dashboard: < 500KB (including all dependencies)
- Content Editor: < 300KB (editor + sanitization library)
- Public Content View: < 200KB (minimal dependencies)
Critical rendering path optimization
// contentOptimization.ts - Performance optimizations for content display
const optimizeContentRendering = () => {
// Use React.memo for content components
const MemoizedContent = React.memo(({ content }: { content: string }) => {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
});
// Use virtualization for large lists
const VirtualizedContentList = ({ items }: { items: Content[] }) => {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={100}
>
{({ index, style }) => (
<div style={style}>
<MemoizedContent content={items[index].content} />
</div>
)}
</FixedSizeList>
);
};
return { MemoizedContent, VirtualizedContentList };
};
Image optimization strategy
// imageOptimization.ts - XSS-safe image handling
const OptimizedImage = ({
src,
alt,
width,
height
}: {
src: string;
alt: string;
width?: number;
height?: number;
}) => {
// Validate image source to prevent XSS
const isValidImageSrc = (url: string): boolean => {
try {
const parsed = new URL(url);
return parsed.protocol === 'https:' || parsed.protocol === 'http:';
} catch {
return false;
}
};
// Sanitize image URL to prevent XSS
const sanitizedSrc = isValidImageSrc(src)
? DOMPurify.sanitize(src)
: '';
return (
<img
src={sanitizedSrc}
alt={alt}
width={width}
height={height}
loading="lazy"
/>
);
};
Font loading strategy
// fontLoading.ts - Secure font loading with XSS protection
const loadSecureFonts = () => {
// Use CSS font loading with proper validation
const fontLoader = new FontFaceObserver('Inter', {
weight: '400',
style: 'normal'
});
// Validate font URLs to prevent XSS
const validateFontUrl = (url: string): boolean => {
try {
const parsed = new URL(url);
return parsed.protocol === 'https:' &&
(parsed.hostname === 'fonts.googleapis.com' ||
parsed.hostname === 'use.typekit.net');
} catch {
return false;
}
};
return fontLoader.load().catch(error => {
console.warn('Font loading failed:', error);
// Fallback to system fonts
});
};
Memoization strategy
// memoization.ts - Strategic use of React.memo and useCallback
const ContentRenderer = React.memo(({ content }: { content: string }) => {
// Memoize the sanitized content to prevent re-renders
const sanitizedContent = useMemo(() => {
return DOMPurify.sanitize(content);
}, [content]);
// Memoize the rendering function
const renderContent = useCallback(() => {
return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
}, [sanitizedContent]);
return renderContent();
});
// For event handlers, use useCallback to prevent unnecessary re-renders
const useContentHandlers = () => {
const handleContentChange = useCallback((newContent: string) => {
// Handle content change with XSS protection
const sanitized = DOMPurify.sanitize(newContent);
// Update state...
}, []);
return { handleContentChange };
};
Virtualization for large lists
// virtualization.ts - Implementation of content list virtualization
import { FixedSizeList as List } from 'react-window';
interface ContentItem {
id: string;
content: string;
author: string;
}
const VirtualizedContentList = ({ items }: { items: ContentItem[] }) => {
const Row = ({ index, style }: { index: number; style: any }) => {
const item = items[index];
// Ensure content is sanitized before rendering
const sanitizedContent = DOMPurify.sanitize(item.content);
return (
<div style={style}>
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
</div>
);
};
return (
<List
height={600}
itemCount={items.length}
itemSize={100}
width="100%"
>
{Row}
</List>
);
};
Prefetching and preloading strategy
// prefetching.ts - Content prefetching with XSS safety
const prefetchContent = async (contentIds: string[]) => {
// Prefetch content with XSS protection
const prefetchPromises = contentIds.map(async (id) => {
try {
const response = await fetch(`/api/content/${id}`);
const data = await response.json();
// Sanitize content during prefetch
return {
...data,
content: DOMPurify.sanitize(data.content)
};
} catch (error) {
console.warn(`Failed to prefetch content ${id}:`, error);
return null;
}
});
return Promise.all(prefetchPromises);
};
// Preload critical content for admin dashboard
const preloadAdminContent = async () => {
const contentIds = await fetchRecentContentIds();
return prefetchContent(contentIds.slice(0, 10));
};
7. Error Handling & Resilience
Error boundary placement strategy
graph TD
A[App] --> B[ErrorBoundary]
B --> C[ContentRenderer]
C --> D[SanitizedContent]
C --> E[RichTextEditor]
B --> F[AdminDashboard]
F --> G[ContentList]
G --> H[ContentItem]
Global error handler design
// globalErrorHandler.ts - Comprehensive error handling system
class GlobalXssErrorHandler {
static handleError(error: Error, errorInfo: any) {
// Log error with XSS awareness
console.error('Global XSS Error:', {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
componentStack: errorInfo.componentStack || 'N/A'
});
// Report to monitoring service (without raw content)
reportToMonitoringService({
error: error.message,
componentStack: errorInfo.componentStack || 'N/A',
context: 'XSS protection error'
});
// Show user-friendly message
showUserError(
'An unexpected error occurred. Your content has been safely protected.'
);
}
static handleContentSecurityViolation(content: string, violationType: string) {
console.warn('Content security violation detected:', {
contentLength: content.length,
violationType,
timestamp: new Date().toISOString()
});
// Log to security monitoring
reportSecurityViolation({
type: violationType,
contentLength: content.length,
timestamp: new Date().toISOString()
});
// Return safe fallback content
return '<p>Content security violation - this content has been sanitized</p>';
}
}
// Error boundary component
class ContentErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: any) {
GlobalXssErrorHandler.handleError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>Content Error</h2>
<p>We're sorry, but there was an issue displaying this content.</p>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
Offline / network failure handling
// offlineHandling.ts - Offline content protection and fallbacks
class OfflineContentHandler {
static async handleOfflineContent(content: string): Promise<string> {
// Check if we have cached content
const cached = await this.getCachedContent(content);
if (cached) {
return cached;
}
// Return safe fallback content
return this.getSafeFallbackContent(content);
}
static async getCachedContent(content: string): Promise<string | null> {
try {
const cache = await caches.open('content-cache');
const response = await cache.match(content);
if (response) {
return response.text();
}
return null;
} catch {
return null;
}
}
static getSafeFallbackContent(content: string): string {
// Return minimal safe content
return '<p>Content is currently unavailable. Please check your connection.</p>';
}
static async handleNetworkFailure(error: any): Promise<string> {
console.warn('Network error in content handling:', error);
// Return cached or safe fallback
return this.getSafeFallbackContent('network-error-fallback');
}
}
Graceful degradation patterns
// gracefulDegradation.ts - Content rendering with fallbacks
const ContentRendererWithFallback = ({
content,
fallbackContent
}: {
content: string;
fallbackContent?: string
}) => {
const [renderedContent, setRenderedContent] = useState<string>('');
useEffect(() => {
try {
// Attempt to render content safely
const sanitized = DOMPurify.sanitize(content);
setRenderedContent(sanitized);
} catch (error) {
// Fallback to safe content
console.warn('Content rendering failed:', error);
setRenderedContent(
fallbackContent || '<p>Content could not be displayed safely</p>'
);
}
}, [content, fallbackContent]);
return (
<div
dangerouslySetInnerHTML={{ __html: renderedContent }}
className="content-container"
/>
);
};
User feedback on errors
// userFeedback.ts - Error feedback system with XSS protection
const useUserFeedback = () => {
const [toast, setToast] = useState<{message: string, type: 'error' | 'warning' | 'success'} | null>(null);
const showUserError = (message: string) => {
// Sanitize error message to prevent XSS in feedback
const sanitizedMessage = DOMPurify.sanitize(message);
setToast({
message: sanitizedMessage,
type: 'error'
});
// Auto-hide after 5 seconds
setTimeout(() => setToast(null), 5000);
};
const showUserWarning = (message: string) => {
const sanitizedMessage = DOMPurify.sanitize(message);
setToast({
message: sanitizedMessage,
type: 'warning'
});
};
return { toast, showUserError, showUserWarning };
};
// Toast component
const Toast = ({ toast }: { toast: any }) => {
if (!toast) return null;
return (
<div className={`toast toast-${toast.type}`}>
{DOMPurify.sanitize(toast.message)}
</div>
);
};
8. Testing Strategy
Unit testing (component + utility coverage plan, target %)
Component Test Coverage Targets:
- ContentRenderer: 95% coverage
- RichTextEditor: 90% coverage
- SanitizedContent: 100% coverage
- ContentItem: 85% coverage
Utility Test Coverage Targets:
- sanitizeHTML: 100% coverage
- DOMPurify integration: 95% coverage
- XSS validation functions: 90% coverage
Integration testing strategy
// integration.test.ts - Integration tests for XSS protection flow
describe('Content Security Integration', () => {
test('should sanitize malicious content before renderingtest('should sanitize malicious content before rendering', async () => {
const maliciousInput = '<script>alert("XSS")</script><img src=x onerror=alert("XSS")>';
const { container } = render(<ContentRenderer content={maliciousInput} />);
// Verify no script tags are present
expect(container.querySelector('script')).toBeNull();
// Verify sanitized content is rendered safely
const safeContent = container.innerHTML;
expect(safeContent).not.toContain('alert("XSS")');
expect(safeContent).toContain('<script>');
});
test('should allow safe HTML tags and attributes', async () => {
const safeInput = '<p>Hello <strong>world</strong></p><a href="https://example.com">Link</a>';
const { container } = render(<ContentRenderer content={safeInput} />);
// Verify allowed tags are preserved
expect(container.querySelector('p')).toBeInTheDocument();
expect(container.querySelector('strong')).toBeInTheDocument();
expect(container.querySelector('a')).toHaveAttribute('href', 'https://example.com');
});
});
// E2E test for XSS protection in user input flow
describe('XSS Protection End-to-End', () => {
test('user content should be sanitized in real-time editor', async () => {
const page = await browser.newPage();
await page.goto('/editor');
// Type malicious content into the editor
await page.type('#editor-input', '<img src=x onerror=alert("XSS")>');
// Submit the content
await page.click('#submit-button');
// Verify sanitized content is displayed in output
const output = await page.$('#content-output');
const content = await output?.textContent();
expect(content).not.toContain('onerror');
expect(content).toContain('<img src="x" onerror="alert');
});
});
E2E testing scope and tool (Cypress / Playwright) — which flows get covered
Playwright E2E Coverage:
- User content submission with XSS payloads
- Rich text editor input sanitization
- Content rendering in various contexts (comments, posts, notifications)
- Admin dashboard content display with user-generated input
- Cross-browser XSS vulnerability testing (Chrome, Firefox, Safari)
Visual regression testing
Visual Regression Strategy:
- Use Percy or Chromatic for visual diffing of content rendering
- Test all content display contexts (light/dark mode, mobile/tablet views)
- Monitor for unexpected HTML tag rendering or attribute leakage
- Include edge cases like malformed HTML, nested tags, and special characters
Accessibility testing automation (axe-core)
Accessibility Coverage:
- Ensure sanitized content doesn't break ARIA attributes or semantic structure
- Validate that screen readers properly announce sanitized content
- Test keyboard navigation through sanitized rich text elements
Test data strategy and fixtures
XSS Test Data Suite:
const xssPayloads = [
{ name: 'Basic script tag', payload: '<script>alert(1)</script>' },
{ name: 'Event handler injection', payload: '<img src=x onerror=alert(1)>' },
{ name: 'CSS injection', payload: '<style>body{color:red}</style>' },
{ name: 'URL-based attack', payload: '<a href="javascript:alert(1)">Click</a>' },
{ name: 'Nested malicious tags', payload: '<div><script>evil()</script></div>' },
{ name: 'Unicode XSS', payload: '<img src="x" onerror="alert(1)">\u003cscript\u003ealert(1)\u003c/script\u003e' }
];
const safeContent = [
{ name: 'Safe HTML', payload: '<p>Paragraph with <strong>bold</strong></p>' },
{ name: 'Safe links', payload: '<a href="https://example.com">Example</a>' },
{ name: 'Safe lists', payload: '<ul><li>Item 1</li><li>Item 2</li></ul>' }
];
9. Accessibility (A11y)
WCAG 2.1 AA compliance checklist for this feature
Accessibility Requirements:
- All sanitized content must maintain semantic structure (headings, lists, etc.)
- Content rendered after sanitization must pass axe-core accessibility checks
- Screen readers must properly announce sanitized content without breaking context
- Keyboard navigation must work through all sanitized elements (links, buttons)
- Focus management must be preserved in sanitized rich text content
- Color contrast must remain compliant after content sanitization
Keyboard navigation support
Keyboard Interaction Patterns:
- Users must be able to tab through sanitized links and interactive elements
- Content editors must support keyboard shortcuts for content insertion
- Sanitized rich text should maintain proper tab order and focus flow
- Keyboard-only users must be able to navigate all sanitized content sections
Focus management strategy (focus trapping, focus restoration)
Focus Handling Strategy:
- Sanitized content containers must not interfere with global focus flow
- When rich text editors are focused, keyboard navigation should work properly
- Focus restoration must occur after content sanitization operations
- Modal contexts with sanitized content should trap focus within the modal
ARIA roles and attributes plan
ARIA Implementation:
- Sanitized content should not override existing ARIA roles in allowed tags
- Rich text elements must maintain proper semantic structure for screen readers
- Content sections should have appropriate ARIA labels when needed
- Interactive elements within sanitized content must have proper ARIA attributes
Screen reader compatibility
Screen Reader Testing:
- Content sanitized with DOMPurify must be readable by NVDA, JAWS, and VoiceOver
- Complex sanitized content should not introduce screen reader navigation issues
- All sanitized elements must be announced properly to users with assistive technologies
10. Security Considerations
XSS prevention strategy
Core XSS Protection Architecture:
- All user-generated content must pass through DOMPurify before rendering
- Content sanitization occurs at the boundary between user input and display
- No inline JavaScript or event handlers are allowed in sanitized content
- Content is validated and sanitized server-side before storage, but client-side sanitization provides defense in depth
- HTML tags are filtered to only allow safe elements (p, strong, em, a, ul, ol, li, etc.)
CSRF protection
CSRF Mitigation in Content Handling:
- Content submission forms must include CSRF tokens for all user-generated content
- Content sanitization does not affect CSRF protection mechanisms
- Server-side validation ensures all content submissions are properly authenticated
Content Security Policy (CSP) headers relevant to this feature
CSP Implementation:
- Content Security Policy headers must include
script-src 'unsafe-inline'for DOMPurify to function properly default-src 'self'ensures all content is loaded from the same originimg-src 'self' data:allows for safe image handling in sanitized contentstyle-src 'self'prevents unsafe CSS injection in sanitized content
Sensitive data handling in frontend state
Data Protection Strategy:
- User-generated content is sanitized before entering any frontend state
- No raw user input is stored in component or global state without sanitization
- Sensitive data (like API keys, tokens) never appears in sanitized content
- Content sanitization occurs before any state updates or rendering
Input sanitization approach
Input Sanitization Pipeline:
- User input is captured via content editors or forms
- Raw content passes through DOMPurify with strict configuration
- Sanitized content is validated for acceptable HTML structure
- Content is then rendered in the DOM or stored in state
- All content operations (edit, delete, save) maintain sanitized state
11. Internationalization (i18n)
Translation key structure and naming convention
Content Security Translation Keys:
content.security.warning.xssDetected: "Potential XSS detected in content"content.security.error.maliciousContent: "Malicious content blocked"content.security.info.sanitized: "Content has been sanitized for safety"
RTL support considerations
RTL Content Sanitization:
- Sanitized content must properly handle RTL text direction in all supported languages
- Content rendering should not break RTL layout when sanitized HTML is used
- Language-specific content sanitization must preserve proper text flow in RTL contexts
Date, number, currency formatting strategy
Content Formatting Strategy:
- Sanitized content should not interfere with locale-specific date/time formatting
- User-generated content that includes dates must be sanitized before formatting
- Currency and number formatting should work correctly with sanitized content
Locale detection and switching strategy
Localization Handling:
- Content sanitization should work consistently across all supported locales
- Language-specific content filtering rules must be maintained in all locales
- Sanitization configuration should not vary based on locale settings
12. Monitoring & Observability
Real User Monitoring (RUM) metrics to track
XSS Protection Metrics:
- XSS attempt detection rate per user session
- Content sanitization success rate (percentage of content properly sanitized)
- Sanitization latency distribution for user-generated content
- Error count when sanitizing content (should be zero in production)
- User feedback on content display issues
Error tracking integration (Sentry / Datadog)
Error Monitoring Strategy:
- Track failed content sanitization attempts as critical errors
- Log XSS attempt patterns to detect potential attack vectors
- Monitor DOMPurify error rates and configuration issues
- Track content rendering errors that may indicate sanitization failures
Custom events and user journey tracking
User Journey Monitoring:
- Track content submission flow with XSS protection
- Monitor user interaction with sanitized content in different contexts
- Record success/failure rates of content sanitization operations
- Track user feedback on sanitized content display quality
Performance monitoring and regression detection
Performance Metrics:
- Sanitization operation time per content size
- Memory usage during content sanitization operations
- DOM rendering performance after content sanitization
- Regression detection for content sanitization performance
13. File & Folder Structure (brief — implementation detail, not primary LLD interview content)
Proposed top-level directory structure (feature-based vs. layer-based — with justification)
Feature-Based Structure:
/src
/security
/xss
xssProtection.ts
contentSanitizer.ts
sanitizerConfig.ts
/content
contentRenderer.tsx
richTextEditor.tsx
/components
/content
ContentRenderer.tsx
SanitizedContent.tsx
/hooks
useContentSanitization.ts
Module boundaries and import rules (no cross-feature imports, barrel export strategy)
Import Strategy:
- Security modules should not import from feature-specific content components
- Content sanitization logic is isolated and reusable across features
- All content sanitization should go through the centralized sanitizer module
Shared vs. feature-specific code separation
Shared Security Module:
- XSS protection logic is shared across all content display features
- Sanitization configuration is centralized for consistent behavior
- Content renderer components are feature-specific but use shared sanitization logic
14. Dependencies & Third-Party Libraries
Key libraries selected with justification
DOMPurify - Core XSS Protection Library:
- Industry standard for HTML sanitization
- Actively maintained with security updates
- Configurable to allow safe HTML tags and attributes
- Supports all modern browsers and environments
Helmet - Content Security Policy Management:
- Provides CSP header management for XSS protection
- Integrates well with React applications and server-side rendering
- Allows granular control over content security policies
Alternatives considered and rejected (with reasons)
Alternative 1: DOMPurify vs. jsdom
- jsdom is too heavy for client-side content sanitization
- DOMPurify has better performance characteristics for frontend use cases
Alternative 2: DOMPurify vs. html5parser
- html5parser is more complex to configure for XSS protection
- DOMPurify has better documentation and community support
Licensing and bundle size impact assessment
Bundle Impact:
- DOMPurify adds ~20KB to bundle size (minified)
- Helmet adds ~5KB to bundle size (minified)
- Both libraries are MIT licensed with no commercial restrictions
15. Open Questions, Risks & Trade-offs
Decisions that need product/engineering alignment
Configuration Alignment:
- Which HTML tags and attributes should be allowed in sanitized content?
- How strict should the XSS sanitization be for different content types?
- Should user-generated content be sanitized in all contexts or only specific ones?
Known trade-offs made in this design
Performance vs. Security Trade-off:
- DOMPurify adds processing overhead to content rendering
- This is acceptable for security benefits in user-generated content
- Performance optimization can be achieved through caching and batching
Risks and mitigations
Risk 1: False Positive Content Sanitization
- Risk: Legitimate content being incorrectly sanitized
- Mitigation: Maintain extensive test suite with edge case coverage
Risk 2: DOMPurify Configuration Vulnerabilities
- Risk: Configuration changes could introduce XSS vulnerabilities
- Mitigation: Regular security audits and automated testing of sanitizer configuration
Technical debt acknowledged
Technical Debt Areas:
- Need to regularly update DOMPurify to latest version for security patches
- Sanitization logic may need refactoring as content requirements evolve
- Testing coverage for edge cases in content sanitization could be expanded
2. Component Architecture
graph TD
A[UserDashboard] --> B[UserCard]
A --> C[UserList]
A --> D[UserForm]
A --> E[UserModal]
C --> F[UserItem]
D --> G[InputField]
D --> H[SubmitButton]
E --> I[ModalHeader]
E --> J[ModalBody]
E --> K[ModalFooter]
F --> L[Avatar]
F --> M[UserName]
F --> N[UserEmail]
Component Tree Breakdown
Smart Components (Container Components):
UserDashboard- orchestrates user data fetching and state managementUserForm- handles form submission and validation logicUserModal- manages modal lifecycle and user interaction flow
Dumb Components (Presentational Components):
UserCard- renders user information in a card layoutUserList- displays collection of user cardsUserItem- individual user card componentInputField- generic input field with validationSubmitButton- styled submit button componentAvatar- user avatar image displayUserName- user name display with sanitizationUserEmail- user email display with sanitization
TypeScript Props Contracts
// UserCard props
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
avatarUrl?: string;
};
onEdit?: () => void;
onDelete?: () => void;
}
// UserForm props
interface UserFormProps {
initialData?: Partial<UserFormData>;
onSubmit: (data: UserFormData) => Promise<void>;
onCancel: () => void;
isSubmitting?: boolean;
}
// UserModal props
interface UserModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
UI State Machine
stateDiagram-v2
[*] --> Idle
Idle --> Editing : Open Edit Modal
Editing --> Saving : Submit Form
Saving --> Success : Successful Save
Saving --> Error : Save Failed
Success --> Idle : Close Modal
Error --> Editing : Retry Edit
Editing --> Cancelled : Cancel Form
Cancelled --> Idle : Close Modal
Component Responsibilities
- UserDashboard: Fetches user list, manages global state, renders user list and forms
- UserCard: Displays individual user data with XSS-safe rendering
- UserList: Renders collection of UserCards with proper list virtualization
- UserForm: Handles form state, validation, and submission with XSS prevention
- UserModal: Manages modal lifecycle and proper focus trapping
Reusability Decisions
InputFieldis designed to be generic and reusable across different formsUserCardis specific to user data but can be extended for other entity typesAvatarcomponent handles image loading with XSS protection for src attributes
Component Lifecycle Considerations
UserFormimplements proper cleanup of form state on unmountUserModalensures focus restoration when closedUserListimplements proper list virtualization to handle large datasets efficiently
// Example of XSS-safe rendering in UserCard component
const UserCard: React.FC<UserCardProps> = ({ user }) => {
// Sanitize user input before rendering
const safeName = sanitizeHTML(user.name);
const safeEmail = sanitizeHTML(user.email);
return (
<div className="user-card">
{user.avatarUrl && (
<img
src={sanitizeURL(user.avatarUrl)}
alt={safeName}
onError={(e) => {
// Fallback to default avatar on error
(e.target as HTMLImageElement).src = '/default-avatar.png';
}}
/>
)}
<h3 dangerouslySetInnerHTML={{ __html: safeName }} />
<p dangerouslySetInnerHTML={{ __html: safeEmail }} />
</div>
);
};
XSS Prevention Implementation
The component architecture implements multiple layers of XSS protection:
- Input Sanitization: All user inputs are sanitized before processing
- HTML Rendering:
dangerouslySetInnerHTMLis used only with pre-sanitized content - URL Validation: All URLs are validated and sanitized before use in
srcattributes - Content Security Policy: Components are designed to work with CSP headers
// Sanitization utility functions
const sanitizeHTML = (html: string): string => {
// Remove script tags and other dangerous elements
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'u'],
ALLOWED_ATTR: ['class']
});
};
const sanitizeURL = (url: string): string => {
try {
const parsedUrl = new URL(url);
// Only allow http/https protocols
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return '#';
}
// Validate domain if needed
return parsedUrl.toString();
} catch (e) {
return '#';
}
};
3. State Management Design
Local state vs. global state decisions
For XSS prevention, we primarily need to manage:
- User input data (both raw and sanitized)
- Sanitization configuration settings
- Security policy state (enabled/disabled, thresholds)
- Error states for failed sanitization attempts
Local state is appropriate for:
- Component-level input buffers and validation flags
- Temporary sanitization previews
- UI state for security policy configuration
Global state is appropriate for:
- Security policy configuration (applied across the app)
- Sanitization rule sets
- Error tracking for security incidents
State management approach
We'll use Zustand as our state management solution because:
- It provides a clean, functional API that's easy to test
- It supports both local and global state with minimal boilerplate
- It has excellent TypeScript support with type inference
- It's lightweight (under 1KB) and doesn't require complex setup like Redux Toolkit
- It allows for easy integration with React Query for server state
State shape and normalization strategy
// SecurityState interface
interface SecurityState {
policies: {
xssProtectionEnabled: boolean;
sanitizeHtml: boolean;
removeScripts: boolean;
maxInputLength: number;
};
rules: {
allowedTags: string[];
allowedAttributes: string[];
disallowedPatterns: RegExp[];
};
errorTracking: {
recentErrors: SecurityError[];
errorCount: number;
};
}
interface SecurityError {
id: string;
timestamp: Date;
type: 'xss' | 'malicious-input';
context: string;
originalInput: string;
sanitizedOutput?: string;
}
Server state vs. client state separation
Client state:
- XSS policy configuration
- Local input sanitization previews
- Security error tracking
Server state:
- Sanitization rule sets (fetched from backend)
- Security policy configurations (applied by admin)
- User input validation rules
Cache invalidation strategy
When security policies change:
- Invalidate all cached input sanitization results
- Revalidate user inputs that were previously processed
- Clear error tracking cache for reprocessing
Optimistic updates design
For input sanitization, we use optimistic updates:
- User types input → immediately show sanitized preview
- If sanitization fails, rollback to original input with error notification
- Update global security policy when changed by admin
Critical-path hook implementation
// useSecurity.ts - Core security state management hook
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface SecurityPolicy {
xssProtectionEnabled: boolean;
sanitizeHtml: boolean;
removeScripts: boolean;
maxInputLength: number;
}
interface SecurityState {
policies: SecurityPolicy;
rules: {
allowedTags: string[];
allowedAttributes: string[];
disallowedPatterns: RegExp[];
};
errorTracking: {
recentErrors: SecurityError[];
errorCount: number;
};
setPolicy: (key: keyof SecurityPolicy, value: boolean | number) => void;
updateRules: (rules: Partial<SecurityState['rules']>) => void;
addError: (error: SecurityError) => void;
clearErrors: () => void;
sanitizeInput: (input: string) => string;
}
interface SecurityError {
id: string;
timestamp: Date;
type: 'xss' | 'malicious-input';
context: string;
originalInput: string;
sanitizedOutput?: string;
}
// Sanitization utility - this is the core XSS prevention logic
const sanitizeHTML = (input: string): string => {
// Using DOMPurify as the core sanitization library
const clean = DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
ALLOWED_ATTR: ['class'],
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
FORBID_ATTR: ['onload', 'onclick', 'onerror', 'href']
});
return clean;
};
const useSecurityStore = create<SecurityState>()(
persist(
(set, get) => ({
policies: {
xssProtectionEnabled: true,
sanitizeHtml: true,
removeScripts: true,
maxInputLength: 1000
},
rules: {
allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
allowedAttributes: ['class'],
disallowedPatterns: [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/on\w+\s*=\s*["'][^"']*["']/gi,
/javascript:/gi,
/vbscript:/gi
]
},
errorTracking: {
recentErrors: [],
errorCount: 0
},
setPolicy: (key, value) => {
set((state) => ({
policies: {
...state.policies,
[key]: value
}
}));
},
updateRules: (newRules) => {
set((state) => ({
rules: {
...state.rules,
...newRules
}
}));
},
addError: (error) => {
set((state) => {
const newErrors = [error, ...state.errorTracking.recentErrors].slice(0, 5);
return {
errorTracking: {
recentErrors: newErrors,
errorCount: state.errorTracking.errorCount + 1
}
};
});
},
clearErrors: () => {
set((state) => ({
errorTracking: {
recentErrors: [],
errorCount: 0
}
}));
},
sanitizeInput: (input: string): string => {
const state = get();
// Early return if XSS protection is disabled
if (!state.policies.xssProtectionEnabled) {
return input;
}
// Check for malicious patterns
const hasMaliciousPatterns = state.rules.disallowedPatterns.some(
pattern => pattern.test(input)
);
if (hasMaliciousPatterns) {
const error: SecurityError = {
id: `error-${Date.now()}`,
timestamp: new Date(),
type: 'malicious-input',
context: 'Input contains potentially malicious patterns',
originalInput: input,
sanitizedOutput: input // Keep original for error tracking
};
// Add to error tracking (optimistic update)
set((state) => ({
errorTracking: {
recentErrors: [error, ...state.errorTracking.recentErrors].slice(0, 5),
errorCount: state.errorTracking.errorCount + 1
}
}));
// Return sanitized version to prevent XSS
return sanitizeHTML(input);
}
// Apply sanitization if enabled
if (state.policies.sanitizeHtml) {
return sanitizeHTML(input);
}
return input;
}
}),
{
name: 'security-storage',
partialize: (state) => ({
policies: state.policies,
rules: state.rules
})
}
)
);
// Custom hook for input sanitization with error handling
export const useSecurityInput = () => {
const [input, setInput] = useState<string>('');
const [sanitized, setSanitized] = useState<string>('');
const [error, setError] = useState<SecurityError | null>(null);
const { sanitizeInput, addError } = useSecurityStore();
const handleInputChange = (value: string) => {
setInput(value);
try {
const sanitizedValue = sanitizeInput(value);
setSanitized(sanitizedValue);
setError(null);
} catch (err) {
const securityError: SecurityError = {
id: `input-error-${Date.now()}`,
timestamp: new Date(),
type: 'xss',
context: 'Input sanitization failed',
originalInput: value,
sanitizedOutput: value
};
addError(securityError);
setError(securityError);
setSanitized(value); // Fallback to original input on error
}
};
return {
input,
sanitized,
error,
handleInputChange,
setSanitized
};
};
// Hook for security policy management
export const useSecurityPolicy = () => {
const { policies, setPolicy } = useSecurityStore();
const togglePolicy = (key: keyof SecurityPolicy) => {
setPolicy(key, !policies[key]);
};
return {
...policies,
togglePolicy
};
};
// Hook for security error tracking
export const useSecurityErrors = () => {
const { errorTracking, clearErrors } = useSecurityStore();
return {
errors: errorTracking.recentErrors,
count: errorTracking.errorCount,
clear: clearErrors
};
};
The state management design ensures:
- Security-first: XSS protection is enabled by default with configurable policies
- Type safety: Full TypeScript support for all security states and operations
- Performance: Optimistic updates with proper error handling
- Extensibility: Easy to add new security rules and policies
- Persistence: Security configurations persist across sessions
- Error tracking: Comprehensive error monitoring for security incidents
4. Routing & Navigation
Route Structure and Hierarchy
graph TD
A[Root] --> B[Public Routes]
A --> C[Protected Routes]
B --> D[Home]
B --> E[Login]
B --> F[Register]
C --> G[Dashboard]
C --> H[Profile]
C --> I[Settings]
C --> J[Admin]
The application follows a protected route structure with:
- Public routes (Home, Login, Register) accessible without authentication
- Protected routes (Dashboard, Profile, Settings, Admin) requiring valid session
- Nested route structure for complex features like Admin with sub-routes
Code Splitting Strategy
Route-based code splitting is implemented using React.lazy and Suspense:
// routes/AppRoutes.tsx
import { lazy, Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
const Home = lazy(() => import('../pages/Home'));
const Login = lazy(() => import('../pages/Login'));
const Dashboard = lazy(() => import('../pages/Dashboard'));
const Admin = lazy(() => import('../pages/Admin'));
export const AppRoutes = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/admin" element={
<ProtectedRoute roles={['admin']}>
<Admin />
</ProtectedRoute>
} />
</Routes>
</Suspense>
);
};
Justification: Route-based splitting provides better bundle size optimization and aligns with the principle of loading only necessary code for each route.
Navigation Guards and Authentication Flows
Authentication guards are implemented using React Context and custom hooks:
// hooks/useAuth.ts
import { useContext, useEffect } from 'react';
import { AuthContext } from '../context/AuthContext';
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
// components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
interface ProtectedRouteProps {
children: React.ReactNode;
roles?: string[];
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
roles
}) => {
const { isAuthenticated, user, loading } = useAuth();
if (loading) return <div>Loading...</div>;
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
if (roles && user && !roles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return <>{children}</>;
};
Deep Linking Support and URL Parameter Design
URL parameters are designed with security in mind:
// utils/validateParams.ts
export const validateRouteParams = (params: Record<string, string>) => {
const allowedKeys = ['id', 'userId', 'page', 'limit'];
// Validate parameter names
const invalidKeys = Object.keys(params).filter(
key => !allowedKeys.includes(key)
);
if (invalidKeys.length > 0) {
throw new Error(`Invalid route parameters: ${invalidKeys.join(', ')}`);
}
// Validate parameter values
const validationErrors = Object.entries(params).map(([key, value]) => {
if (key === 'id' && !/^\d+$/.test(value)) {
return `Invalid ID format: ${value}`;
}
if (key === 'page' && (!/^\d+$/.test(value) || parseInt(value) < 1)) {
return `Invalid page number: ${value}`;
}
return null;
}).filter(Boolean);
if (validationErrors.length > 0) {
throw new Error(`Parameter validation failed: ${validationErrors.join('; ')}`);
}
};
// components/SecureDashboard.tsx
import { useParams } from 'react-router-dom';
import { validateRouteParams } from '../utils/validateParams';
export const SecureDashboard = () => {
const params = useParams();
try {
validateRouteParams(params);
} catch (error) {
console.error('Route parameter validation failed:', error);
return <Navigate to="/error" replace />;
}
return (
<div>
{/* Dashboard content */}
<h1>Dashboard for user {params.userId}</h1>
</div>
);
};
Browser History Management and Scroll Restoration
Scroll restoration is handled using React Router's built-in capabilities:
// main.tsx
import { BrowserRouter } from 'react-router-dom';
import { ScrollRestoration } from 'react-router-dom';
export const App = () => {
return (
<BrowserRouter>
<ScrollRestoration />
<AppRoutes />
</BrowserRouter>
);
};
// components/Navigation.tsx
import { useLocation } from 'react-router-dom';
export const Navigation = () => {
const location = useLocation();
useEffect(() => {
// Custom scroll handling logic if needed
window.scrollTo(0, 0);
}, [location.pathname]);
return (
<nav>
{/* Navigation items */}
</nav>
);
};
XSS Prevention in Routing Context
To prevent XSS in routing contexts:
- URL sanitization: All route parameters are validated and sanitized before use
- Content security policy: CSP headers prevent inline script execution
- Route parameter encoding: Parameters are properly encoded when used in URLs
- Redirect validation: Redirects only allow whitelisted domains
// utils/sanitizeUrl.ts
export const sanitizeRedirectUrl = (url: string): string => {
try {
// Validate URL format
const urlObj = new URL(url, window.location.origin);
// Only allow same-origin or whitelisted domains
const allowedDomains = ['example.com', 'api.example.com'];
if (urlObj.origin !== window.location.origin) {
const isAllowed = allowedDomains.some(domain =>
urlObj.hostname.endsWith(domain)
);
if (!isAllowed) {
throw new Error('Redirect to unauthorized domain');
}
}
// Remove any potentially dangerous protocols
if (['javascript:', 'data:'].some(protocol =>
url.startsWith(protocol)
)) {
throw new Error('Potentially dangerous URL protocol');
}
return urlObj.href;
} catch (error) {
console.warn('Invalid redirect URL:', error);
return '/'; // Fallback to home
}
};
5. API Integration Layer
API Client Design Pattern
For robust API integration, we'll implement a centralized Axios client with request/response interceptors and proper error handling. The client will be built using the Axios instance pattern with custom configuration to ensure consistent behavior across all API calls.
// api/client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'https://api.example.com';
const apiClient: AxiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor for adding auth tokens
apiClient.interceptors.request.use(
(config: AxiosRequestConfig) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for handling errors
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle token expiration
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default apiClient;
Request/Response Interceptors
The interceptors provide centralized handling for authentication, error normalization, and response transformation:
- Request Interceptor: Automatically injects auth tokens and handles CSRF tokens
- Response Interceptor: Handles authentication failures, normalizes error responses, and logs API errors
Error Handling and Retry Strategy
API errors are handled with a robust retry mechanism that follows exponential backoff:
// api/errorHandler.ts
import { AxiosError } from 'axios';
export class ApiError extends Error {
constructor(
public status: number,
public message: string,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
export const handleApiError = (error: AxiosError): ApiError => {
if (error.response) {
const { status, data } = error.response;
return new ApiError(
status,
data?.message || `API Error: ${status}`,
data?.code
);
}
if (error.request) {
return new ApiError(503, 'Network Error: Unable to reach server');
}
return new ApiError(500, 'Unknown Error occurred');
};
export const retryRequest = async (
fn: () => Promise<any>,
retries = 3,
delay = 1000
): Promise<any> => {
for (let i = 0; i <= retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries) throw error;
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
};
Loading, Error, and Empty States Design
API responses are handled through a consistent state management pattern:
// types/api.ts
export type ApiResponse<T> = {
data: T | null;
loading: boolean;
error: ApiError | null;
refetch: () => void;
};
export type ApiState<T> = {
data: T | null;
loading: boolean;
error: ApiError | null;
};
Data Fetching Strategy
For optimal performance, we'll use a hybrid approach:
- CSR (Client-Side Rendering) for dynamic content
- SSR (Server-Side Rendering) for SEO-critical pages
- SWR (Stale-While-Revalidate) for data caching and revalidation
// hooks/useApi.ts
import { useState, useCallback } from 'react';
import apiClient from '@/api/client';
import { ApiError, handleApiError } from '@/api/errorHandler';
export const useApi = <T>(url: string) => {
const [state, setState] = useState<ApiState<T>>({
data: null,
loading: false,
error: null,
});
const fetchData = useCallback(async () => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const response = await apiClient.get<T>(url);
setState({
data: response.data,
loading: false,
error: null,
});
} catch (error) {
const apiError = handleApiError(error as AxiosError);
setState({
data: null,
loading: false,
error: apiError,
});
}
}, [url]);
return {
...state,
refetch: fetchData,
};
};
API Response Transformation Layer
All API responses are normalized to domain models to ensure consistent data structure:
// api/transformers.ts
export interface UserResponse {
id: string;
name: string;
email: string;
avatar?: string;
}
export interface User {
id: string;
name: string;
email: string;
avatarUrl?: string;
}
export const transformUserResponse = (response: UserResponse): User => ({
id: response.id,
name: response.name,
email: response.email,
avatarUrl: response.avatar ? `${API_BASE_URL}/avatars/${response.avatar}` : undefined,
});
Sequence Diagram for Primary Data Flow
sequenceDiagram
participant U as User
participant C as Component
participant H as Hook
participant A as API Client
participant S as Server
U->>C: Trigger data fetch
C->>H: Call useApi hook
H->>A: Make API request
A->>S: Send HTTP request
S->>A: Return JSON response
A->>H: Pass normalized data
H->>C: Update component state
C->>U: Render UI with data
6. Performance Design
Core Web Vitals targets (LCP, INP, CLS) with specific numbers
- Largest Contentful Paint (LCP): Target ≤ 2.5 seconds for 90% of users
- Interaction to Next Paint (INP): Target ≤ 200ms for 90% of interactions
- Cumulative Layout Shift (CLS): Target ≤ 0.1 for 90% of users
Bundle size budget per route
- Home/Landing: ≤ 500KB (including all dependencies)
- Product Listing: ≤ 750KB (with virtualized lists)
- Product Detail: ≤ 600KB (with lazy-loaded assets)
- User Dashboard: ≤ 800KB (with feature-specific chunks)
- Admin Panel: ≤ 1.2MB (with heavy UI libraries)
Critical rendering path optimization
- CSS inlining: Critical CSS for above-the-fold content
- JavaScript bundling: Code splitting with dynamic imports
- Preload hints: Critical resources (fonts, key images) with
<link rel="preload"> - Resource hints:
prefetchfor next-page resources - Render-blocking CSS: Move non-critical CSS to async loading
Image optimization strategy (formats, lazy loading, responsive images)
// Image component with optimization
const OptimizedImage: React.FC<{
src: string;
alt: string;
width?: number;
height?: number;
priority?: boolean;
}> = ({ src, alt, width, height, priority }) => {
return (
<img
src={src}
alt={alt}
width={width}
height={height}
loading={priority ? "eager" : "lazy"}
decoding="async"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
srcSet={`
${src}?w=320 320w,
${src}?w=480 480w,
${src}?w=640 640w,
${src}?w=960 960w,
${src}?w=1280 1280w
`}
/>
);
};
Font loading strategy
- Font display:
swapfor all web fonts to prevent FOIT - Preload critical fonts:
<link rel="preload" as="font">for first-paint fonts - Font subset: Use only necessary character sets (e.g., Latin, Cyrillic)
- System font fallbacks: Use
system-uias base with custom fonts as enhancement
Memoization strategy (useMemo, useCallback, React.memo) — what to memoize and what not to
What to memoize:
- Expensive computations in render (e.g., sorting, filtering)
- Complex object transformations
- Callbacks that depend on expensive computations
What NOT to memoize:
- Simple state access (e.g.,
user.name) - Props that change frequently
- Components with simple renders
const ExpensiveComponent: React.FC<{ data: any[] }> = ({ data }) => {
const processedData = useMemo(() => {
return data
.filter(item => item.active)
.map(item => ({ ...item, processed: expensiveCalculation(item) }))
.sort((a, b) => a.priority - b.priority);
}, [data]);
const handleClick = useCallback((id: string) => {
// Handle click logic
}, []);
return (
<div>
{processedData.map(item => (
<Item key={item.id} item={item} onClick={handleClick} />
))}
</div>
);
};
Virtualization for large lists (react-window / react-virtual)
For lists with > 1000 items:
import { FixedSizeList as List } from 'react-window';
const VirtualizedList: React.FC<{ items: Item[] }> = ({ items }) => {
const Row = ({ index, style }: { index: number; style: any }) => (
<div style={style}>
<Item item={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
};
Prefetching and preloading strategy
Preload for:
- Critical JavaScript bundles (e.g., main chunk)
- Fonts needed immediately
- Images that will appear in viewport
Prefetch for:
- Next page resources (e.g., dashboard assets when on home)
- User journey continuation paths
// Prefetching hook
const usePrefetch = (url: string) => {
useEffect(() => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
return () => {
document.head.removeChild(link);
};
}, [url]);
};
Performance monitoring and optimization
- Bundle analysis: Use Webpack Bundle Analyzer to identify large dependencies
- Lighthouse scoring: Target 90+ for performance in CI/CD pipeline
- Real user monitoring: Track actual user performance with RUM tools
- Analytics integration: Monitor key metrics like LCP, FID, CLS in production
Memory usage optimization
- Component cleanup: Properly clean up event listeners and timers
- Memory leaks prevention: Use
useEffectcleanup functions for subscriptions - State size management: Avoid storing large objects in state when possible
Server-side rendering considerations
- SSR bundle size: Keep SSR bundles under 1MB to avoid cold start issues
- Critical data hydration: Fetch and hydrate critical data on server
- Client-side rehydration: Ensure client-side state matches server-rendered content
Client-side performance heuristics
- Lazy loading: Load components only when needed (e.g., modals, sidebars)
- Debouncing: Debounce expensive operations like search filters
- Throttling: Throttle scroll or resize handlers to prevent performance degradation
This performance design ensures optimal loading, rendering, and interaction experiences while maintaining code quality and scalability for enterprise-grade applications.
7. Error Handling & Resilience
Error Boundary Placement Strategy
Error boundaries should be strategically placed to prevent cascading failures while maintaining user experience. The placement follows a hierarchical approach:
- Application-level boundary - Catches unhandled errors in the main app tree
- Feature-level boundaries - Isolate errors within specific feature areas (e.g., dashboard, forms)
- Component-level boundaries - Handle errors in individual components that might fail independently
graph TD
A[App Root] --> B[ErrorBoundary]
B --> C[DashboardFeature]
B --> D[UserForm]
B --> E[Navigation]
C --> F[DashboardGrid]
C --> G[AnalyticsChart]
D --> H[FormInputs]
D --> I[SubmitButton]
Global Error Handler Design
A centralized error handler manages uncaught exceptions and provides consistent user feedback:
// src/core/error/GlobalErrorHandler.tsx
import React from 'react';
interface GlobalErrorState {
hasError: boolean;
error?: Error;
errorInfo?: React.ErrorInfo;
}
class GlobalErrorHandler extends React.Component<{}, GlobalErrorState> {
constructor(props: {}) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): GlobalErrorState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log to monitoring service
console.error('Global error caught:', error, errorInfo);
// Report to external error tracking service
this.reportError(error, errorInfo);
}
reportError = (error: Error, errorInfo: React.ErrorInfo) => {
// Send to Sentry or similar monitoring service
if (window.Sentry) {
window.Sentry.captureException(error, {
contexts: { react: errorInfo }
});
}
};
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h1>Something went wrong.</h1>
<p>We're working to fix this issue.</p>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
export default GlobalErrorHandler;
Offline / Network Failure Handling
Implement robust offline detection and graceful degradation:
// src/core/network/NetworkManager.ts
import { useEffect, useState } from 'react';
export const useNetworkStatus = () => {
const [isOnline, setIsOnline] = useState<boolean>(true);
const [isRetrying, setIsRetrying] = useState<boolean>(false);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
const retryOperation = async (operation: () => Promise<any>) => {
setIsRetrying(true);
try {
const result = await operation();
return result;
} catch (error) {
console.error('Operation failed, retrying...', error);
throw error;
} finally {
setIsRetrying(false);
}
};
return { isOnline, isRetrying, retryOperation };
};
Graceful Degradation Patterns
Implement fallback strategies for critical features:
// src/features/dashboard/DashboardFallback.tsx
import React from 'react';
interface DashboardFallbackProps {
error?: Error;
retry?: () => void;
}
const DashboardFallback: React.FC<DashboardFallbackProps> = ({
error,
retry
}) => {
return (
<div className="dashboard-fallback">
<h2>Dashboard Unavailable</h2>
<p>
{error
? "We're having trouble loading your dashboard data."
: "The dashboard is currently unavailable."}
</p>
{retry && (
<button onClick={retry} className="retry-button">
Retry Loading
</button>
)}
<div className="fallback-content">
<p>Showing cached data...</p>
<div className="cached-data-placeholder">
{/* Placeholder for cached dashboard content */}
</div>
</div>
</div>
);
};
export default DashboardFallback;
User Feedback on Errors
Implement multiple error feedback mechanisms:
// src/core/toast/ToastManager.tsx
import React from 'react';
import { ToastContainer, toast } from 'react-toastify';
interface ToastOptions {
type?: 'success' | 'error' | 'info' | 'warning';
autoClose?: number;
position?: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left';
hideProgressBar?: boolean;
}
export const ToastManager = () => (
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
);
export const showErrorMessage = (
message: string,
options?: ToastOptions
) => {
toast.error(message, {
position: "top-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
...options
});
};
export const showSuccessMessage = (
message: string,
options?: ToastOptions
) => {
toast.success(message, {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
...options
});
};
Error Recovery Strategy
Implement structured error recovery for common failure scenarios:
// src/core/error/ErrorRecovery.ts
export class ErrorRecovery {
static handleXSSAttempt = (potentialXSS: string) => {
// Sanitize input before processing
const sanitized = this.sanitizeInput(potentialXSS);
// Log security event
console.warn('Potential XSS attempt detected:', sanitized);
return sanitized;
};
static sanitizeInput = (input: string): string => {
// Remove dangerous patterns
const dangerousPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/on\w+\s*=\s*["'][^"']*["']/gi,
/javascript:/gi,
/vbscript:/gi
];
let sanitized = input;
dangerousPatterns.forEach(pattern => {
sanitized = sanitized.replace(pattern, '');
});
return sanitized;
};
static handleNetworkError = (error: Error) => {
// Check if it's a network error
if (error.name === 'NetworkError' ||
error.message.includes('network') ||
error.message.includes('timeout')) {
// Show offline message or retry mechanism
return {
type: 'network',
message: 'Network connection lost. Please check your internet connection.',
retryable: true
};
}
return {
type: 'generic',
message: 'An unexpected error occurred.',
retryable: false
};
};
static handleSecurityError = (error: Error) => {
// Log security event for monitoring
if (window.Sentry) {
window.Sentry.captureException(error, {
tags: { security: 'xss' }
});
}
return {
type: 'security',
message: 'Security validation failed. Please try again.',
retryable: false
};
};
}
Error Boundary Implementation
// src/core/error/FeatureErrorBoundary.tsx
import React from 'react';
interface FeatureErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: React.ErrorInfo;
}
class FeatureErrorBoundary extends React.Component<
{ featureName: string; fallback?: React.ReactNode },
FeatureErrorBoundaryState
> {
constructor(props: { featureName: string; fallback?: React.ReactNode }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): FeatureErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error(`Error in ${this.props.featureName}:`, error, errorInfo);
// Report to monitoring service
if (window.Sentry) {
window.Sentry.captureException(error, {
contexts: {
react: errorInfo,
feature: this.props.featureName
}
});
}
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="feature-error">
<h3>Something went wrong in {this.props.featureName}</h3>
<p>We're working to fix this issue.</p>
<button onClick={() => window.location.reload()}>
Reload Component
</button>
</div>
);
}
return this.props.children;
}
}
export default FeatureErrorBoundary;
Testing Strategy for Error Handling
// src/core/error/__tests__/GlobalErrorHandler.test.tsx
import React from 'react';
import { render } from '@testing-library/react';
import GlobalErrorHandler from '../GlobalErrorHandler';
describe('GlobalErrorHandler', () => {
it('should catch and display error boundary', () => {
const ThrowingComponent = () => {
throw new Error('Test error');
};
const { container } = render(
<GlobalErrorHandler>
<ThrowingComponent />
</GlobalErrorHandler>
);
expect(container).toBeInTheDocument();
});
it('should handle network errors gracefully', () => {
const mockError = new Error('Network error');
const recoveryResult = ErrorRecovery.handleNetworkError(mockError);
expect(recoveryResult.type).toBe('network');
expect(recoveryResult.retryable).toBe(true);
});
});
Error Recovery Workflow
stateDiagram-v2
[*] --> NormalOperation
NormalOperation --> ErrorDetected : Error occurs
ErrorDetected --> RecoveryAttempt : Retry logic
RecoveryAttempt --> Success : Operation succeeds
RecoveryAttempt --> Fallback : Retry failed
Fallback --> UserFeedback : Show fallback UI
Success --> NormalOperation : Continue normal flow
[*] --> OfflineMode : Network offline
OfflineMode --> RetryOnline : Network restored
RetryOnline --> NormalOperation : Resume normal operation
Security-First Error Handling
// src/core/security/SecurityErrorManager.ts
import { showErrorMessage } from '../toast/ToastManager';
export class SecurityErrorManager {
static handleXSSAttempt = (input: string) => {
// Log attempt for security monitoring
console.warn('XSS attempt detected:', {
input: input,
timestamp: new Date().toISOString()
});
// Show user-friendly error without exposing technical details
showErrorMessage(
'Security validation failed. Please check your input and try again.'
);
// Return sanitized version for safe processing
return this.sanitizeInput(input);
};
static sanitizeInput = (input: string): string => {
// Implement comprehensive XSS sanitization
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
static handleSecurityEvent = (event: string, details?: any) => {
// Log security events for monitoring and alerting
const securityEvent = {
event,
timestamp: new Date().toISOString(),
details,
userAgent: navigator.userAgent,
url: window.location.href
};
console.log('Security event:', securityEvent);
// Send to security monitoring service
if (window.Sentry) {
window.Sentry.captureEvent({
message: `Security event: ${event}`,
contexts: { security: securityEvent }
});
}
};
}
8. Testing Strategy
Unit Testing Approach
Component-level testing (70% of test coverage)
Focus on testing the core logic and rendering behavior of each component in isolation. Use React Testing Library with Jest for comprehensive coverage.
// Example: Component unit test structure
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect } from 'vitest';
describe('UserCard', () => {
it('renders user data correctly', () => {
const user = { id: 1, name: 'John Doe', email: 'john@example.com' };
render(<UserCard user={user} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
it('handles click events properly', async () => {
const user = { id: 1, name: 'John Doe' };
const onClick = vi.fn();
render(<UserCard user={user} onClick={onClick} />);
await userEvent.click(screen.getByText('John Doe'));
expect(onClick).toHaveBeenCalledWith(1);
});
});
Utility function testing (20% of test coverage)
Test all utility functions that handle data transformation, validation, and security logic.
// Example: Security utility tests
import { sanitizeHTML, validateInput } from '@/utils/security';
describe('Security Utilities', () => {
it('sanitizes HTML content properly', () => {
const maliciousInput = '<script>alert("XSS")</script><p>Safe content</p>';
const sanitized = sanitizeHTML(maliciousInput);
expect(sanitized).not.toContain('<script>');
expect(sanitized).toContain('<p>Safe content</p>');
});
it('validates input length constraints', () => {
expect(validateInput('short', { maxLength: 10 })).toBe(true);
expect(validateInput('very long input that exceeds limit', { maxLength: 10 })).toBe(false);
});
});
Test Coverage Targets:
- Components: 85% line coverage, 70% branch coverage
- Utilities: 90% line coverage, 85% branch coverage
- Security logic: 100% coverage
Integration Testing Strategy
API integration tests (25% of integration tests)
Test the complete flow from component interaction to API call with mocked backend responses.
// Example: Integration test for form submission
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('UserForm Integration', () => {
it('submits form and handles success response', async () => {
const mockSubmit = vi.fn().mockResolvedValue({ success: true });
render(<UserForm onSubmit={mockSubmit} />);
await userEvent.type(screen.getByLabelText('Name'), 'John Doe');
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
expect(mockSubmit).toHaveBeenCalledWith({ name: 'John Doe' });
});
});
Cross-component interaction tests (15% of integration tests)
Test how components interact with each other through state management and context.
E2E Testing Scope
End-to-end test coverage (10% of E2E tests)
Use Playwright for comprehensive end-to-end testing covering critical user flows:
// Example: E2E test for XSS prevention in comment submission
import { test, expect } from '@playwright/test';
test('prevents XSS injection in comment submission', async ({ page }) => {
await page.goto('/comments');
// Attempt to submit malicious content
await page.fill('#comment-input', '<script>alert("XSS")</script>');
await page.click('#submit-button');
// Verify content was sanitized
const commentElement = await page.locator('.comment-content');
expect(await commentElement.innerHTML()).not.toContain('<script>');
expect(await commentElement.innerHTML()).toContain('alert("XSS")');
});
Critical E2E flows covered:
- User comment submission with HTML content
- User profile updates with potentially malicious input
- Form validation and sanitization end-to-end
Visual Regression Testing
Screenshot testing with Percy or Chromatic:
Automate visual regression testing for components that render user-generated content to ensure XSS sanitization doesn't break UI layout.
Accessibility Testing Automation
Automated A11y testing with axe-core:
Integrate axe-core into test suite to catch accessibility issues that might be introduced by security sanitization:
// Example: A11y test integration
import { axe, toHaveNoViolations } from 'axe-core';
expect.extend(toHaveNoViolations);
it('maintains accessibility after sanitization', async () => {
const user = { name: '<strong>John</strong>' };
const { container } = render(<UserCard user={user} />);
const axeResults = await axe.run(container);
expect(axeResults).toHaveNoViolations();
});
Test Data Strategy
Test data fixtures and factories:
Create realistic test data that includes edge cases for XSS scenarios:
// Example: Test data factory for security testing
const createXSSTestData = () => ({
maliciousHTML: '<script>alert("XSS")</script>',
mixedContent: '<p>Safe text <img src=x onerror=alert("XSS")></p>',
encodedContent: '<script>alert("XSS")</script>',
validContent: '<p>Valid user content</p>'
});
// Test data for form validation scenarios
const createFormTestData = () => ({
validInputs: { name: 'John Doe', email: 'john@example.com' },
invalidInputs: {
name: '<script>alert("XSS")</script>',
email: 'john@example.com'
}
});
Test Environment Considerations
Isolated test environments:
All tests run in isolated environments with clean state, ensuring security logic is tested without side effects from previous test runs.
Security context testing:
Test that security measures work in different environments (development, staging, production) to ensure consistent behavior across deployment contexts.
9. Accessibility (A11y)
WCAG 2.1 AA compliance checklist for this feature
The feature must meet all Level A and Level AA success criteria from WCAG 2.1:
- 1.1.1 Non-text Content (Level A): All non-text content must have text alternatives
- 1.3.1 Info and Relationships (Level A): Information, structure, and relationships must be programmatically determinable
- 1.3.2 Meaningful Sequence (Level A): Content must follow a meaningful sequence when read linearly
- 1.4.1 Use of Color (Level A): Color must not be the only visual means of conveying information
- 1.4.3 Contrast (Minimum) (Level AA): Text must have a contrast ratio of at least 4.5:1
- 1.4.6 Contrast (Enhanced) (Level AA): Text must have a contrast ratio of at least 7:1 for large text
- 2.4.1 Bypass Blocks (Level A): Mechanisms must be available to bypass blocks of content
- 2.4.3 Focus Order (Level A): Focus order must follow an intuitive sequence
- 2.4.6 Headings and Labels (Level AA): Headings must be properly structured and labels must be descriptive
- 3.1.1 Language of Page (Level A): The language of the page must be programmatically determinable
- 3.2.1 On Focus (Level A): Changing focus must not cause unexpected changes
- 3.2.2 On Input (Level A): Changes in input must not cause unexpected changes
- 4.1.2 Name, Role, Value (Level A): All components must have programmatically determinable names, roles, and values
Keyboard navigation support
All interactive elements must be fully operable via keyboard:
- Tab order: Follows logical reading order and semantic structure
- Focus indicators: Visible, consistent, and clearly distinguishable (minimum 3:1 contrast ratio)
- Escape key: For modals and dropdowns to close
- Enter/Space: For activating buttons, links, checkboxes, and radio buttons
- Arrow keys: For navigating dropdown menus, lists, and tree structures
Focus management strategy
Focus trapping: When modals open, focus is trapped within the modal until closed:
const useFocusTrap = (isOpen: boolean, modalRef: React.RefObject<HTMLDivElement>) => {
useEffect(() => {
if (!isOpen) return;
const handleFocusTrap = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) || [];
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
};
document.addEventListener('keydown', handleFocusTrap);
return () => document.removeEventListener('keydown', handleFocusTrap);
}, [isOpen]);
};
Focus restoration: When modals close, focus returns to the previously focused element:
const useFocusRestoration = (isOpen: boolean, previousFocusRef: React.RefObject<HTMLElement>) => {
useEffect(() => {
if (!isOpen) {
const previousFocus = previousFocusRef.current;
if (previousFocus && typeof previousFocus.focus === 'function') {
previousFocus.focus();
}
}
}, [isOpen]);
};
ARIA roles and attributes plan
All custom components must implement appropriate ARIA roles:
- Role:
buttonfor interactive elements that aren't native buttons - Role:
dialogfor modal overlays witharia-modal="true" - Role:
alertfor error messages and notifications - Attribute:
aria-labelfor decorative elements or unlabeled controls - Attribute:
aria-describedbyfor additional context on form fields - Attribute:
aria-expandedfor expandable/collapsible components - Attribute:
aria-hiddenfor off-screen content that should be ignored by screen readers
Screen reader compatibility
- Semantic HTML: Use native HTML elements where possible (e.g.,
<button>,<input>,<nav>) - Landmark regions: Use ARIA landmarks (
main,navigation,complementary) to organize content - Live regions: Use
aria-live="polite"for dynamic content updates that don't interrupt users - Dynamic content announcements: Use
aria-live="assertive"for urgent updates like form validation errors - Descriptive labels: All interactive elements must have meaningful, descriptive labels
// Example of accessible form field with proper ARIA attributes
const AccessibleInputField = ({
id,
label,
error,
value,
onChange
}: {
id: string;
label: string;
error?: string;
value: string;
onChange: (value: string) => void;
}) => {
const errorId = `${id}-error`;
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
aria-describedby={error ? errorId : undefined}
aria-invalid={!!error}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
{error && (
<div id={errorId} role="alert" aria-live="assertive">
{error}
</div>
)}
</div>
);
};
Keyboard navigation and focus patterns
All interactive components must support:
- Tab navigation: Follows semantic structure (form elements, then navigation, then content)
- Shift+Tab: Moves backward through the focus order
- Arrow keys: For navigating lists, dropdowns, and menus (up/down for list navigation)
- Enter/Space: Activates interactive elements
- Escape key: Closes modals and dropdowns
Screen reader announcement patterns
Critical user feedback must be announced to screen readers:
const useScreenReaderAnnouncer = () => {
const [announcement, setAnnouncement] = useState<string | null>(null);
const announce = useCallback((message: string, priority: 'polite' | 'assertive' = 'polite') => {
setAnnouncement(message);
// Clear announcement after a short delay to prevent screen reader stacking
setTimeout(() => setAnnouncement(null), 1000);
}, []);
return {
announcement,
announce
};
};
Focus management in multi-step forms
Multi-step forms must maintain focus context:
- Step indicators: Clear indication of current step and progress
- Form field grouping: Logical grouping with
fieldsetandlegend - Focus restoration: Return focus to the first error field when validation fails
- Accessible navigation: Clear "previous" and "next" buttons with appropriate ARIA roles
Dynamic content updates
When content changes dynamically:
- Use
aria-liveto announce changes without disrupting user flow - Provide sufficient context in live regions for screen reader users
- Avoid excessive announcements that could overwhelm users
Color contrast compliance
All text and interactive elements must meet WCAG 2.1 contrast requirements:
- Normal text: Minimum 4.5:1 contrast ratio
- Large text (18pt+ or 14pt+ bold): Minimum 3:1 contrast ratio
- Icons and graphics: Must maintain sufficient contrast with surrounding content
Alternative text strategies
All images must have appropriate alternative text:
- Descriptive alt text for informative images
- Empty alt text (
alt="") for decorative images - Long descriptions for complex images using
aria-describedby
Language and internationalization support
- Proper language declaration via
langattribute on root elements - Dynamic language switching with appropriate ARIA attributes
- Text direction support for RTL languages (e.g., Arabic, Hebrew)
Testing accessibility
- Automated testing: Use axe-core or similar tools in CI pipeline
- Manual keyboard testing: Verify all functionality works via keyboard alone
- Screen reader testing: Test with NVDA, JAWS, and VoiceOver
- Color contrast verification: Use tools like WebAIM's Contrast Checker
Accessibility testing automation
// Example of accessibility test using axe-core in Jest
import { axe, toHaveNoViolations } from 'axe-core';
expect.extend(toHaveNoViolations);
it('should not have any accessibility violations', async () => {
const html = await renderToString(<Component />);
const results = await axe.run(html);
expect(results).toHaveNoViolations();
});
Focus management in dropdowns and menus
Dropdown components must:
- Trap focus within the dropdown when open
- Restore focus to trigger element when closed
- Support keyboard navigation within dropdown items
- Announce selection changes to screen readers
Accessible error handling and validation
Form validation errors must:
- Be announced to screen readers via
aria-live - Include clear error messages that describe what went wrong
- Highlight the problematic field with appropriate ARIA attributes
- Provide guidance on how to correct the error
Keyboard-accessible modal patterns
Modal dialogs must:
- Be announced as a modal when opened
- Trap focus within the modal content
- Restore focus to the trigger element when closed
- Support escape key to close the modal
- Have proper ARIA roles (
role="dialog",aria-modal="true")
Semantic HTML structure
All components must use semantic HTML:
<!-- Proper heading hierarchy -->
<h1>Main page title</h1>
<h2>Section heading</h2>
<h3>Subsection heading</h3>
<!-- Proper landmark structure -->
<main>
<nav aria-label="Main navigation">
<!-- Navigation links -->
</nav>
<section aria-labelledby="content-heading">
<h2 id="content-heading">Content heading</h2>
<!-- Content -->
</section>
</main>
10. Security Considerations
XSS Prevention Strategy
Cross-Site Scripting (XSS) is a critical security vulnerability that occurs when untrusted data is embedded into web pages without proper sanitization. In a frontend context, XSS can be exploited through user-generated content, API responses, or dynamic DOM manipulation.
Primary Prevention Approaches:
- Content Security Policy (CSP) Implementation
// CSP header configuration in server-side response
const cspPolicy = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Only for legacy support
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self' data:",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'"
].join('; ');
// Apply to HTTP headers or meta tags
const CSP_META_TAG = `<meta http-equiv="Content-Security-Policy" content="${cspPolicy}">`;
- DOM-based XSS Prevention
// Safe DOM manipulation utilities
const safeInnerHTML = (element: HTMLElement, content: string) => {
// Sanitize content before setting innerHTML
const sanitizer = new DOMPurify();
element.innerHTML = sanitizer.sanitize(content);
};
const safeTextContent = (element: HTMLElement, content: string) => {
// Use textContent for user-generated content
element.textContent = content;
};
- Input Validation and Sanitization
// Input sanitization middleware
const sanitizeInput = (input: string): string => {
// Remove or escape dangerous characters
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
// HTML sanitization using DOMPurify
const sanitizeHTML = (html: string): string => {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['class'],
ALLOW_DATA_ATTR: false,
});
};
CSRF Protection
Cross-Site Request Forgery (CSRF) protection is implemented through secure token management:
// CSRF token management in React context
interface CSRFContextType {
token: string;
refresh: () => void;
}
const CSRFContext = React.createContext<CSRFContextType | null>(null);
// Token generation and validation middleware
const CSRFMiddleware = {
generateToken: () => {
return crypto.randomUUID();
},
validateToken: (token: string, expectedToken: string) => {
return token === expectedToken;
}
};
Secure Data Handling
Sensitive data in frontend state is managed with strict access controls:
// Secure state management for sensitive data
class SecureStateManager {
private sensitiveData: Map<string, any> = new Map();
setSensitive(key: string, data: any) {
// Store in memory only, never in localStorage/sessionStorage
this.sensitiveData.set(key, data);
}
getSensitive(key: string) {
return this.sensitiveData.get(key);
}
removeSensitive(key: string) {
this.sensitiveData.delete(key);
}
}
Security Headers Implementation
// Security headers configuration
const securityHeaders = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy': cspPolicy,
'Referrer-Policy': 'strict-origin-when-cross-origin'
};
Input Sanitization Strategy
-
HTML Content Sanitization
- Use DOMPurify for user-generated HTML content
- Whitelist allowed HTML tags and attributes
- Prevent inline JavaScript execution
-
Attribute Sanitization
const sanitizeAttributes = (attributes: Record<string, any>) => {
const allowedAttrs = ['class', 'id', 'data-*'];
const sanitized: Record<string, any> = {};
Object.entries(attributes).forEach(([key, value]) => {
if (allowedAttrs.some(attr =>
attr === key || attr === 'data-*' && key.startsWith('data-')
)) {
sanitized[key] = value;
}
});
return sanitized;
};
- URL Validation
const validateUrl = (url: string): boolean => {
try {
const parsed = new URL(url);
return parsed.origin === window.location.origin ||
parsed.protocol === 'https:' ||
parsed.protocol === 'http:';
} catch {
return false;
}
};
Third-Party Library Security
All third-party dependencies are vetted for security vulnerabilities:
-
Dependency Scanning
- Use
npm auditor Snyk for vulnerability detection - Regular security updates and patching
- Dependency tree analysis to minimize attack surface
- Use
-
Secure Library Usage
// Example of secure library integration
const secureLibraryIntegration = {
// Use only audited libraries
// Avoid inline script execution from external sources
// Implement proper error boundaries for third-party components
initialize: () => {
// Validate library versions against security advisories
// Set up proper error handling for external libraries
}
};
Security Testing Strategy
-
Automated XSS Detection
- Integration with security scanning tools (OWASP ZAP, Burp Suite)
- Regular automated security tests in CI pipeline
- Fuzz testing for input validation
-
Manual Security Review
- Code review focusing on DOM manipulation and user input handling
- Penetration testing for common XSS vectors
- Security audit of third-party integrations
Risk Mitigation
-
Defense in Depth
- Multiple layers of XSS protection (CSP, input sanitization, DOM manipulation safety)
- Regular security training for development team
- Incident response plan for security breaches
-
Monitoring and Alerting
// Security monitoring hook
const useSecurityMonitor = () => {
useEffect(() => {
const handleXSSAttempt = (event: Event) => {
// Log potential XSS attempts
console.warn('Potential XSS attempt detected:', event);
// Send to security monitoring service
if (window.Sentry) {
Sentry.captureMessage('Potential XSS attempt', {
level: 'warning',
extra: { event }
});
}
};
// Add listeners for suspicious DOM mutations
document.addEventListener('DOMNodeInserted', handleXSSAttempt);
return () => {
document.removeEventListener('DOMNodeInserted', handleXSSAttempt);
};
}, []);
};
Implementation Considerations
-
Performance Impact
- Sanitization adds computational overhead but is necessary for security
- Batch processing of content to minimize performance impact
- Caching sanitized results where appropriate
-
User Experience
- Ensure sanitization doesn't break legitimate user content
- Provide clear error messages for content that fails validation
- Maintain accessibility standards during sanitization
-
Compliance Requirements
- Adherence to OWASP Top 10 security guidelines
- GDPR compliance for user data handling
- Industry-specific security standards (PCI-DSS, HIPAA) where applicable
The XSS prevention strategy follows a defense-in-depth approach combining multiple security controls to ensure robust protection against cross-site scripting vulnerabilities in the frontend application.
11. Internationalization (i118n)
Translation Key Structure and Naming Convention
Translation keys follow a hierarchical, descriptive naming convention to ensure maintainability and scalability:
{feature}.{component}.{property}
Examples:
dashboard.welcome.messageauth.login.form.email.placeholderproduct.card.price.label
Keys are written in lowercase with dot notation for readability and to support automatic tooling.
RTL Support Considerations
RTL layout support is implemented using CSS logical properties and direction-aware styling:
- Use
margin-inline-startinstead ofmargin-left - Apply
direction: rtlto root container when needed - Implement
text-align: rightfor RTL text alignment - Use CSS variables to dynamically switch layout properties
Date, Number, Currency Formatting Strategy
All date/time formatting uses the browser's Intl.DateTimeFormat API with locale-specific options:
const formatDate = (date: Date, locale: string) => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
};
const formatCurrency = (amount: number, locale: string, currency: string) => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(amount);
};
Locale Detection and Switching Strategy
Automatic locale detection uses browser's navigator.language with fallback to 'en-US'. Users can manually switch locales via a dedicated dropdown in the header.
const detectLocale = (): string => {
const savedLocale = localStorage.getItem('locale');
if (savedLocale) return savedLocale;
const browserLocale = navigator.language;
const supportedLocales = ['en-US', 'fr-FR', 'es-ES'];
return supportedLocales.includes(browserLocale)
? browserLocale
: 'en-US';
};
The locale switcher updates the app context and persists selection in localStorage for session consistency.
i18n Implementation Pattern
The i18n system uses a React Context with a custom hook for seamless integration:
// i18n/i18n-context.tsx
import { createContext, useContext } from 'react';
interface I18nContextType {
locale: string;
messages: Record<string, string>;
formatMessage: (id: string, values?: Record<string, unknown>) => string;
setLocale: (locale: string) => void;
}
const I18nContext = createContext<I18nContextType | undefined>(undefined);
export const useI18n = () => {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n must be used within an I18nProvider');
}
return context;
};
// i18n/translation-loader.ts
const loadTranslations = async (locale: string) => {
try {
const translations = await import(`../locales/${locale}.json`);
return translations.default;
} catch (error) {
console.error(`Failed to load translations for ${locale}`, error);
return {};
}
};
Translation File Organization
Translation files are organized by locale in src/locales/{locale}.json:
{
"dashboard": {
"welcome": {
"message": "Welcome to our platform"
}
},
"auth": {
"login": {
"form": {
"email": {
"placeholder": "Enter your email"
}
}
}
}
}
Testing i18n Implementation
Testing includes:
- Translation key existence validation
- RTL layout rendering verification
- Date/time formatting consistency checks
- Missing translation detection in development mode
Performance Considerations
To minimize bundle size:
- Translation files are loaded on-demand via dynamic imports
- Only the current locale's translation file is loaded at runtime
- Translation keys are extracted and validated during build process
Accessibility in i18n
All translated content maintains proper ARIA labeling and semantic structure:
- Language attributes (
langattribute) are set on content containers - Screen readers properly announce translated text with correct pronunciation
- Focus management works consistently across locales
Migration Strategy
When adding new translations:
- Add keys to default English translation file
- Generate translation files for all supported locales using translation tools
- Validate key consistency across all locale files
- Implement fallback logic for missing keys in development mode
12. Monitoring & Observability
Real User Monitoring (RUM) Metrics to Track
The frontend monitoring strategy focuses on key performance indicators that directly impact user experience and business outcomes:
- First Contentful Paint (FCP): Target <2s for 95% of users
- Largest Contentful Paint (LCP): Target <2.5s for 95% of users
- Cumulative Layout Shift (CLS): Target <0.1 for 95% of users
- Time to Interactive (TTI): Target <5s for 95% of users
- Navigation Time: Track page load times across different route transitions
- Error Rate: Monitor unhandled JavaScript errors and failed API calls
- User Session Duration: Track engagement patterns to identify potential issues
Error Tracking Integration
Sentry is selected as the primary error tracking solution due to:
- Native React integration with automatic breadcrumbs
- Performance monitoring capabilities (RUM)
- Rich error grouping and context capture
- Support for both client-side and server-side errors
Error handling strategy:
// Error boundary implementation with Sentry integration
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Report to Sentry with additional context
Sentry.captureException(error, {
contexts: { react: errorInfo },
tags: { component: this.constructor.name }
});
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
Custom Events and User Journey Tracking
Event tracking strategy:
- User Action Events: Track clicks, form submissions, navigation events
- Feature Adoption Events: Track usage of key features (e.g., "user created new project")
- Performance Events: Log slow API responses, failed renders, etc.
// Event tracking utility with performance monitoring
const trackEvent = (eventName: string, properties?: Record<string, any>) => {
// Send to analytics service
analytics.track(eventName, properties);
// Send to RUM for performance monitoring
if (performanceObserver) {
performanceObserver.observe({ entryTypes: ['navigation', 'paint'] });
}
};
Performance Monitoring and Regression Detection
Performance baseline establishment:
- Establish performance baselines for each feature using historical data
- Set up automated regression alerts when performance degrades by >10%
- Monitor Core Web Vitals across different device types and network conditions
Monitoring dashboard requirements:
- Real-time performance metrics per route
- Error rate trends over time
- User session health indicators
- Feature usage analytics with performance correlation
Alerting thresholds:
- Critical: LCP > 4s, CLS > 0.25, TTI > 8s
- Warning: LCP > 3s, CLS > 0.15, TTI > 6s
- Performance degradation: >10% increase in any key metric
Integration with Backend Monitoring
The frontend monitoring integrates with backend observability through:
- Correlated request IDs: All API calls include trace IDs that connect frontend and backend telemetry
- Unified error context: Frontend errors include stack traces, user context, and API response data
- Cross-service performance tracking: End-to-end request tracing across frontend → backend → database
Data Privacy and Compliance
All monitoring data is processed with:
- User anonymization: Remove PII from error reports and performance data
- GDPR compliance: Implement user consent mechanisms for tracking
- Data retention policies: Set automatic data cleanup for monitoring artifacts
- Audit logging: Track who accesses monitoring dashboards and what actions they perform
Alerting and Incident Response
Alert channels:
- Slack notifications for critical performance regressions
- Email alerts for sustained degradation in key metrics
- PagerDuty escalation for system-wide outages
Response protocols:
- Critical alerts (LCP > 4s) trigger immediate investigation
- Warning alerts (LCP > 3s) initiate performance review
- Performance degradation (10%+ increase) trigger root cause analysis
Observability Tooling Stack
Primary tools:
- Sentry: Error tracking and performance monitoring
- DataDog: Infrastructure and application performance monitoring
- Google Analytics 4: User behavior and engagement tracking
- Lighthouse CI: Automated performance testing in CI pipeline
Monitoring dashboard requirements:
- Route-specific performance metrics
- Real-time error rate visualization
- User session health indicators
- Feature adoption and usage analytics
This monitoring approach ensures comprehensive visibility into frontend performance, error rates, and user experience while maintaining compliance with privacy regulations and providing actionable insights for continuous improvement.
13. File & Folder Structure (brief — implementation detail, not primary LLD interview content)
Proposed Directory Structure
/src
/components
/shared
Button/
Button.tsx
Button.module.css
index.ts
Input/
Input.tsx
Input.module.css
index.ts
Modal/
Modal.tsx
Modal.module.css
index.ts
/features
/user-profile
UserProfileCard/
UserProfileCard.tsx
UserProfileCard.module.css
index.ts
EditProfileForm/
EditProfileForm.tsx
EditProfileForm.module.css
index.ts
index.ts
/features
/user-management
UserList/
UserList.tsx
UserList.module.css
index.ts
UserCard/
UserCard.tsx
UserCard.module.css
index.ts
index.ts
/hooks
useAuth.ts
useUser.ts
index.ts
/services
api/
userApi.ts
authApi.ts
index.ts
utils/
sanitizer.ts
validator.ts
index.ts
/types
api.ts
user.ts
index.ts
/utils
domUtils.ts
stringUtils.ts
index.ts
/contexts
AuthContext.tsx
ThemeContext.tsx
index.ts
/styles
globals.css
variables.css
index.css
/assets
images/
icons/
/pages
Home.tsx
UserProfile.tsx
Login.tsx
index.ts
/lib
security/
sanitizer.ts
validator.ts
index.ts
App.tsx
main.tsx
Module Boundaries and Import Rules
- No cross-feature imports: Components in
/features/user-managementcannot directly import from/features/payment-processing - Barrel exports: Each feature directory has an
index.tsthat re-exports public components and types - Shared components:
/components/sharedcan be imported by any feature, but features cannot import from each other's internal structure
Shared vs. Feature-Specific Code Separation
- Shared code lives in
/components/shared,/hooks,/utils, and/lib/security - Feature-specific code lives under
/features/<feature-name>with its own sub-components and logic - Security utilities are in
/lib/securityto ensure they're not accidentally bypassed or overridden by feature-specific implementations
This structure enables clear ownership boundaries, prevents circular dependencies, and makes it easy to identify security-critical utilities that must be consistently applied across the codebase.
14. Dependencies & Third-Party Libraries
Key Libraries Selected with Justification
1. DOMPurify
npm install dompurify
- Justification: Industry-standard HTML sanitization library that effectively removes XSS attack vectors while preserving legitimate HTML content. Provides comprehensive protection against DOM-based XSS attacks in user-generated content rendering.
- Bundle size: ~25KB (minified)
- Security: Regular security audits and vulnerability disclosures handled by maintainers
2. Helmet.js (or equivalent CSP middleware)
npm install helmet
- Justification: Provides Content Security Policy (CSP) headers to prevent XSS through script injection. Essential for defense-in-depth approach.
- Bundle size: ~15KB (minified)
- Security: Maintained by Express.js team with regular updates
3. React Hook Form
npm install react-hook-form
- Justification: Robust form handling with built-in validation that prevents XSS in form submissions. Reduces manual DOM manipulation that could introduce vulnerabilities.
- Bundle size: ~25KB (minified)
- Security: Built-in sanitization and validation features
4. Axios
npm install axios
- Justification: Production-ready HTTP client with built-in request/response interceptors for consistent security handling across all API calls.
- Bundle size: ~20KB (minified)
- Security: Regular security updates and proper handling of CORS headers
Alternatives Considered and Rejected
1. DOMPurify vs. Custom Regex Sanitization
- Rejected: Custom regex solutions are inherently incomplete and vulnerable to edge cases. DOMPurify has undergone extensive security review and is battle-tested in production environments.
2. Cheerio vs. DOMPurify for HTML Sanitization
- Rejected: Cheerio is designed for server-side DOM manipulation, not client-side XSS prevention. DOMPurify is specifically built for sanitizing HTML content in browser environments.
3. Manual CSP Implementation vs. Helmet.js
- Rejected: Custom CSP implementation is error-prone and requires deep understanding of security headers. Helmet.js provides battle-tested defaults with easy customization.
4. Formik vs. React Hook Form
- Rejected: While Formik is popular, React Hook Form offers better performance and more granular control over form validation and sanitization without sacrificing security.
Licensing and Bundle Size Impact Assessment
License Compatibility: All selected libraries use MIT or BSD-3-Clause licenses, compatible with enterprise software deployment. No GPL dependencies that would require open-sourcing our codebase.
Bundle Size Impact:
- Total additional bundle size: ~85KB (minified)
- This represents ~2.5% of typical frontend bundle size for enterprise applications
- Optimized via tree-shaking and conditional loading in production builds
Tree-shaking Strategy:
- DOMPurify: Import only required modules (e.g.,
dompurify/dist/purify.min.js) - Helmet: Import only middleware components needed for specific security policies
- React Hook Form: Use only the validation and controller modules required
Security Library Integration Strategy
Security Layered Approach:
- Input Sanitization: DOMPurify for user-generated content rendering
- Content Security Policy: Helmet.js for HTTP header security
- Form Validation: React Hook Form with built-in XSS prevention
- API Security: Axios interceptors for consistent request handling
Security Audit Integration:
- Regular security scanning using Snyk or OWASP Dependency-Check
- Automated vulnerability alerts for library updates
- Security policy integration with CI/CD pipeline to block vulnerable dependencies
Performance Considerations:
- DOMPurify: Async processing capability for large content (not blocking UI)
- Helmet.js: Header generation happens once per request, no runtime overhead
- React Hook Form: Optimized for performance with minimal re-renders
- Axios: Efficient request/response handling with built-in caching strategies
Monitoring Integration:
- Security logging for sanitization events (via Winston or similar)
- Alerting for security policy violations in production
- Integration with security monitoring platforms (e.g., Datadog, New Relic)
15. Open Questions, Risks & Trade-offs
Decisions that need product/engineering alignment
- Content sanitization granularity: Should we sanitize all user-generated content at ingestion time, or apply context-aware sanitization (e.g., HTML in comments vs. blog posts)? This affects both performance and security coverage.
- Third-party library selection: Which DOM purifier or sanitization library to use (DOMPurify vs. sanitize-html vs. custom solution)? This decision impacts bundle size, maintenance burden, and security guarantees.
- Real-time vs. batch sanitization: Should XSS prevention happen in real-time as content is entered, or as a batch operation during content submission? Real-time validation affects UX but increases complexity.
Known trade-offs made in this design
- Performance vs. security: Using strict DOM sanitization (e.g., DOMPurify) introduces runtime overhead, but more permissive approaches may leave gaps in XSS protection. We've chosen to prioritize security over micro-optimization.
- User experience vs. content integrity: Enforcing strict sanitization may strip out legitimate HTML that users expect to work (e.g., inline styles). We've opted for strict sanitization to prevent security issues, accepting potential UX friction.
- Development velocity vs. security coverage: Implementing comprehensive XSS protection requires extensive test coverage and validation, which increases initial development time but ensures better long-term security.
Risks and mitigations
-
Risk: Incomplete sanitization rules may miss edge cases in HTML parsing, leading to XSS vulnerabilities.
- Mitigation: Regular security audits and integration with automated vulnerability scanners (e.g., OWASP ZAP, Snyk).
-
Risk: Performance degradation due to content sanitization on large payloads or frequent updates.
- Mitigation: Implement caching for sanitized content and use Web Workers for heavy sanitization operations in background threads.
-
Risk: False positives in content filtering that break legitimate user-generated content.
- Mitigation: Maintain a configurable allowlist for common legitimate HTML elements and attributes, with clear documentation of what's filtered out.
Technical debt acknowledged
- Debt: Custom sanitization logic may not cover all edge cases that future HTML parsers or browser implementations might introduce, requiring ongoing maintenance and updates.
- Debt: If we choose to implement a custom sanitization solution instead of using established libraries like DOMPurify, we're assuming responsibility for maintaining security patches and staying up-to-date with known vulnerabilities.
- Debt: Testing coverage for XSS prevention is extensive but may not cover all possible combinations of malicious input, requiring ongoing vigilance and threat modeling.