Skip to main content

XSS Prevention Strategies in Modern Web Applications

Comprehensive guide to implementing robust XSS protection mechanisms including input sanitization, Content Security Policy, and secure coding practices for frontend applications.

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

  1. What specific types of user input are we protecting against (HTML injection, JavaScript injection, DOM-based XSS)?
  2. Are there any existing content sanitization libraries in our tech stack that we should leverage?
  3. What's the expected scale of content processing (e.g., 100s vs 1000s of user-generated content items)?
  4. Do we need to support rich text editing or just plain text input?
  5. Are there any specific regulatory or compliance requirements (e.g., GDPR, HIPAA) that impact our XSS strategy?
  6. What's the expected browser support matrix (e.g., IE11, mobile browsers)?
  7. 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 state
  • RichTextEditor - Handles rich text input with formatting toolbar integration
  • AdminDashboard - Container for content management workflows

Dumb Components:

  • SanitizedContent - Pure rendering of sanitized HTML content
  • ContentInput - Simple text input field with validation
  • FormattingToolbar - UI controls for content formatting
  • ContentPreview - Read-only view of formatted content
  • ContentActions - 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:

  1. Context API: For session and permissions that need to be available across the admin dashboard
  2. Zustand: For complex content state management and caching with minimal boilerplate
  3. 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

  1. Content versioning: Increment version number on content changes
  2. Time-based invalidation: Cache entries expire after 1 hour
  3. Manual invalidation: Clear cache when content is published or moderated
  4. 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:

  1. Admin dashboard is only accessed by authenticated users, so it can be lazy-loaded
  2. Public content pages should load quickly for all users
  3. Content creation/editing components can be chunked separately from read-only views
// 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('&lt;script&gt;');
  });
  
  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('&lt;img src=&quot;x&quot; onerror=&quot;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 origin
  • img-src 'self' data: allows for safe image handling in sanitized content
  • style-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:

  1. User input is captured via content editors or forms
  2. Raw content passes through DOMPurify with strict configuration
  3. Sanitized content is validated for acceptable HTML structure
  4. Content is then rendered in the DOM or stored in state
  5. 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 management
  • UserForm - handles form submission and validation logic
  • UserModal - manages modal lifecycle and user interaction flow

Dumb Components (Presentational Components):

  • UserCard - renders user information in a card layout
  • UserList - displays collection of user cards
  • UserItem - individual user card component
  • InputField - generic input field with validation
  • SubmitButton - styled submit button component
  • Avatar - user avatar image display
  • UserName - user name display with sanitization
  • UserEmail - 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

  • InputField is designed to be generic and reusable across different forms
  • UserCard is specific to user data but can be extended for other entity types
  • Avatar component handles image loading with XSS protection for src attributes

Component Lifecycle Considerations

  • UserForm implements proper cleanup of form state on unmount
  • UserModal ensures focus restoration when closed
  • UserList implements 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:

  1. Input Sanitization: All user inputs are sanitized before processing
  2. HTML Rendering: dangerouslySetInnerHTML is used only with pre-sanitized content
  3. URL Validation: All URLs are validated and sanitized before use in src attributes
  4. 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:

  1. It provides a clean, functional API that's easy to test
  2. It supports both local and global state with minimal boilerplate
  3. It has excellent TypeScript support with type inference
  4. It's lightweight (under 1KB) and doesn't require complex setup like Redux Toolkit
  5. 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:

  1. Invalidate all cached input sanitization results
  2. Revalidate user inputs that were previously processed
  3. Clear error tracking cache for reprocessing

Optimistic updates design

For input sanitization, we use optimistic updates:

  1. User types input → immediately show sanitized preview
  2. If sanitization fails, rollback to original input with error notification
  3. 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:

  1. Security-first: XSS protection is enabled by default with configurable policies
  2. Type safety: Full TypeScript support for all security states and operations
  3. Performance: Optimistic updates with proper error handling
  4. Extensibility: Easy to add new security rules and policies
  5. Persistence: Security configurations persist across sessions
  6. 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.

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:

  1. URL sanitization: All route parameters are validated and sanitized before use
  2. Content security policy: CSP headers prevent inline script execution
  3. Route parameter encoding: Parameters are properly encoded when used in URLs
  4. 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: prefetch for 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: swap for 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-ui as 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 useEffect cleanup 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:

  1. Application-level boundary - Catches unhandled errors in the main app tree
  2. Feature-level boundaries - Isolate errors within specific feature areas (e.g., dashboard, forms)
  3. 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, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;');
  };

  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: '&lt;script&gt;alert("XSS")&lt;/script&gt;',
  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: button for interactive elements that aren't native buttons
  • Role: dialog for modal overlays with aria-modal="true"
  • Role: alert for error messages and notifications
  • Attribute: aria-label for decorative elements or unlabeled controls
  • Attribute: aria-describedby for additional context on form fields
  • Attribute: aria-expanded for expandable/collapsible components
  • Attribute: aria-hidden for 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 fieldset and legend
  • 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-live to 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 lang attribute 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:

  1. 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}">`;
  1. 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;
};
  1. Input Validation and Sanitization
// Input sanitization middleware
const sanitizeInput = (input: string): string => {
  // Remove or escape dangerous characters
  return input
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
};

// 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

  1. HTML Content Sanitization

    • Use DOMPurify for user-generated HTML content
    • Whitelist allowed HTML tags and attributes
    • Prevent inline JavaScript execution
  2. 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;
};
  1. 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:

  1. Dependency Scanning

    • Use npm audit or Snyk for vulnerability detection
    • Regular security updates and patching
    • Dependency tree analysis to minimize attack surface
  2. 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

  1. Automated XSS Detection

    • Integration with security scanning tools (OWASP ZAP, Burp Suite)
    • Regular automated security tests in CI pipeline
    • Fuzz testing for input validation
  2. 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

  1. 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
  2. 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

  1. Performance Impact

    • Sanitization adds computational overhead but is necessary for security
    • Batch processing of content to minimize performance impact
    • Caching sanitized results where appropriate
  2. User Experience

    • Ensure sanitization doesn't break legitimate user content
    • Provide clear error messages for content that fails validation
    • Maintain accessibility standards during sanitization
  3. 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.message
  • auth.login.form.email.placeholder
  • product.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-start instead of margin-left
  • Apply direction: rtl to root container when needed
  • Implement text-align: right for 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 (lang attribute) 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:

  1. Add keys to default English translation file
  2. Generate translation files for all supported locales using translation tools
  3. Validate key consistency across all locale files
  4. 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:

  1. Critical alerts (LCP > 4s) trigger immediate investigation
  2. Warning alerts (LCP > 3s) initiate performance review
  3. 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-management cannot directly import from /features/payment-processing
  • Barrel exports: Each feature directory has an index.ts that re-exports public components and types
  • Shared components: /components/shared can 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/security to 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:

  1. Input Sanitization: DOMPurify for user-generated content rendering
  2. Content Security Policy: Helmet.js for HTTP header security
  3. Form Validation: React Hook Form with built-in XSS prevention
  4. 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.

Was this article helpful?

Topics in this article

More from Frontend LLD

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.