Skip to main content

Comment System Low-Level Design for Scalable Social Platforms

Comprehensive LLD for building a scalable comment system with real-time updates, moderation, and user engagement features.

A scalable comment system requires careful architecture to handle concurrent users, real-time updates, and moderation workflows while maintaining performance and user experience across large-scale social platforms.

1. Problem Statement & Requirements

Clarifying questions

  1. What's the target device/browser support? Will we need to support IE11 or mobile Safari?
  2. Do we need real-time comment updates (WebSocket) or can we rely on polling?
  3. Should comments support nested replies and threading? How deep should nesting go?
  4. Do we need offline comment creation with sync-on-reconnect capability?
  5. What's the expected scale of comments (100s vs 1000s per post)?
  6. Do we need internationalization support for comment content (multi-language comments)?
  7. Will this feature integrate with existing design systems or use custom components?

Functional requirements

  • Users can create, read, update, and delete comments on posts
  • Comments support nested replies (threaded comments)
  • Users can like/dislike comments
  • Comments show author information and timestamps
  • Moderation features (flag, report, hide inappropriate comments)
  • Real-time updates for new comments on the same post
  • Comment search and filtering capabilities
  • Ability to mention other users in comments (with autocomplete)
  • Rich text support for comments (bold, italic, links)

Non-functional requirements

  • Performance: Comments list should render under 200ms for 100 comments
  • Accessibility: Full keyboard navigation and screen reader support
  • Scalability: Support for 10k+ concurrent users commenting on the same post
  • Reliability: Comments should persist through page reloads and network failures
  • Security: XSS protection and input sanitization for comment content

Constraints and assumptions

  • Comments are tied to specific posts (no global comments)
  • Users must be authenticated to create or modify comments
  • Comments are stored in a database with proper indexing for post ID and timestamp
  • Comment content is limited to 1000 characters (with soft limit of 500)
  • Moderation is handled by backend services, not frontend logic
  • UI updates for comments are throttled to prevent excessive re-renders

Out of scope

  • Comment versioning and history tracking
  • Real-time collaborative editing of comments
  • Emoji reactions beyond simple like/dislike
  • Comment analytics and engagement metrics
  • Comment moderation dashboard (admin-only interface)
  • Rich media attachments in comments (images, videos)
  • Comment scheduling or publishing at future times
  • Cross-post comment sharing or reposting functionality

2. Component Architecture

graph TD
    A[CommentSystem] --> B[CommentList]
    A --> C[CommentForm]
    A --> D[CommentItem]
    B --> E[CommentSkeleton]
    B --> F[CommentThread]
    D --> G[CommentAvatar]
    D --> H[CommentContent]
    D --> I[CommentActions]
    F --> J[ReplyForm]
    F --> K[ReplyList]
    K --> L[ReplyItem]

Component Tree Breakdown

Smart Components (Container Components):

  • CommentSystem - Main container managing state and API interactions
  • CommentList - Fetches and renders comment threads with pagination
  • CommentForm - Handles form submission and validation

Dumb Components (Presentational Components):

  • CommentItem - Renders individual comment with avatar, content, actions
  • CommentAvatar - Displays user avatar with fallback handling
  • CommentContent - Formats and displays comment text with markdown support
  • CommentActions - Renders action buttons (reply, like, report)
  • ReplyForm - Nested form for reply submission
  • ReplyItem - Renders individual reply with nested structure

TypeScript Props Contracts

// CommentSystem Props
interface CommentSystemProps {
  resourceId: string;
  resourceType: 'post' | 'article' | 'media';
  currentUser: User | null;
  maxDepth?: number;
  pageSize?: number;
  enableReply?: boolean;
  enableLike?: boolean;
  enableReport?: boolean;
}

// CommentItem Props
interface CommentItemProps {
  comment: CommentWithReplies;
  currentUser: User | null;
  onReply?: (commentId: string) => void;
  onLike?: (commentId: string) => void;
  onReport?: (commentId: string) => void;
  enableReply?: boolean;
  enableLike?: boolean;
  enableReport?: boolean;
  depth: number;
}

// CommentForm Props
interface CommentFormProps {
  resourceId: string;
  resourceType: 'post' | 'article' | 'media';
  parentCommentId?: string;
  currentUser: User | null;
  onSubmit: (content: string) => Promise<void>;
  onCancel?: () => void;
  placeholder?: string;
}

UI State Machine

stateDiagram-v2
    [*] --> Idle
    Idle --> Editing : StartEdit
    Editing --> Saving : Submit
    Saving --> Success : Success
    Saving --> Error : Failure
    Success --> Idle : Reset
    Error --> Editing : RetryEdit
    Error --> Idle : CancelEdit
    Editing --> Idle : CancelEdit
    
    [*] --> Loading : FetchComments
    Loading --> Loaded : Success
    Loading --> Error : Failure
    Loaded --> Idle : ShowComments
    
    [*] --> ReplyEditing : StartReply
    ReplyEditing --> ReplySaving : SubmitReply
    ReplySaving --> ReplySuccess : Success
    ReplySaving --> ReplyError : Failure
    ReplySuccess --> Idle : ResetReply
    ReplyError --> ReplyEditing : RetryReply

Component Responsibilities

CommentSystem:

  • Manages overall comment state and lifecycle
  • Handles API calls for comments CRUD operations
  • Coordinates between list, form, and individual comment components
  • Implements pagination and infinite scroll logic

CommentList:

  • Renders list of comments with proper threading
  • Handles pagination and loading states
  • Manages skeleton loading placeholders
  • Implements virtualization for large comment sets

CommentItem:

  • Renders individual comment with proper nesting
  • Handles user interactions (like, report, reply)
  • Manages nested reply rendering with proper depth tracking
  • Implements accessibility attributes for screen readers

CommentForm:

  • Handles form state and validation
  • Manages submission flow with optimistic updates
  • Implements proper error handling and user feedback
  • Supports nested reply form functionality

Reusability Decisions

  1. Highly Reusable Components:

    • CommentAvatar - Can be used in any user interaction context
    • CommentContent - Markdown rendering and text formatting logic
    • CommentActions - Standard comment interaction buttons
  2. Feature-Specific Components:

    • CommentSystem - Specific to comment functionality
    • ReplyForm - Nested reply logic specific to this feature

Component Lifecycle Considerations

  1. Mounting:

    • Fetch initial comments on mount
    • Initialize user session state
    • Set up scroll position restoration
  2. Updating:

    • Handle new comment additions with proper animations
    • Update reply counts when nested comments are added
    • Manage form state transitions during editing
  3. Unmounting:

    • Cancel ongoing API requests
    • Clear form state and validation errors
    • Restore scroll position for navigation
// Example CommentItem lifecycle management
const CommentItem: React.FC<CommentItemProps> = ({ comment, currentUser, ...handlers }) => {
  const [isEditing, setIsEditing] = useState(false);
  const [isReplying, setIsReplying] = useState(false);
  
  useEffect(() => {
    // Handle focus restoration for accessibility
    if (isEditing) {
      // Focus the comment form when editing starts
      const form = document.getElementById(`comment-form-${comment.id}`);
      form?.focus();
    }
  }, [isEditing]);

  const handleEditStart = () => {
    setIsEditing(true);
  };

  const handleReplyStart = () => {
    setIsReplying(true);
  };

  return (
    <div className="comment-item" data-comment-id={comment.id}>
      {isEditing ? (
        <CommentForm 
          resourceId={comment.resourceId}
          parentCommentId={comment.id}
          currentUser={currentUser}
          onSubmit={handleEditSubmit}
          onCancel={() => setIsEditing(false)}
        />
      ) : (
        <>
          <CommentAvatar user={comment.author} />
          <CommentContent content={comment.content} />
          <CommentActions 
            comment={comment}
            currentUser={currentUser}
            onReply={handleReplyStart}
            onLike={handlers.onLike}
            onReport={handlers.onReport}
          />
        </>
      )}
      
      {isReplying && (
        <ReplyForm 
          parentCommentId={comment.id}
          currentUser={currentUser}
          onSubmit={handleReplySubmit}
          onCancel={() => setIsReplying(false)}
        />
      )}
    </div>
  );
};

3. State Management Design

Local state vs. global state decisions

For a comment system, local state is appropriate for:

  • Individual comment form states (input text, validation errors)
  • UI-specific states (expanded/collapsed, editing mode)
  • Temporary draft states during composition

Global state is appropriate for:

  • Comments list and pagination state
  • User authentication context (needed for posting/replying)
  • Active comment thread context (for nested replies)
  • User preferences (auto-expand, sort order)

State management approach

Zustand is selected for this feature due to:

  1. Simplicity: Minimal boilerplate compared to Redux Toolkit
  2. TypeScript native: First-class support with clean typing
  3. Scalability: Easy to extract to Redux if needed later
  4. Middleware support: Built-in support for logging, persistence, and async operations

State shape and normalization strategy

// Normalized state structure for optimal performance
interface Comment {
  id: string;
  content: string;
  author: {
    id: string;
    name: string;
    avatarUrl?: string;
  };
  timestamp: Date;
  likes: number;
  likedByCurrentUser: boolean;
  replies: string[]; // Array of reply comment IDs
  parentId?: string; // For nested replies
  status: 'published' | 'pending' | 'deleted';
}

interface CommentThread {
  rootCommentId: string;
  comments: Record<string, Comment>;
  replyTo: string | null; // ID of comment being replied to
  expandedReplies: Set<string>; // IDs of expanded reply threads
  sortKey: 'newest' | 'oldest' | 'popular';
}

interface CommentState {
  threads: Record<string, CommentThread>;
  currentUser: User | null;
  loading: boolean;
  error: string | null;
}

Server state vs. client state separation

Server State (fetched from API):

  • Comments data (content, author, timestamp)
  • User authentication status
  • Comment metadata (likes, reply count)

Client State (managed locally):

  • Draft comment content
  • UI state (expanded, editing, loading)
  • Temporary optimistic updates

Cache invalidation strategy

  1. Comment creation: Invalidate thread cache and append new comment
  2. Comment deletion: Remove from cache and update parent reply count
  3. Like toggle: Update local copy, invalidate server cache on success
  4. Thread expansion: Cache expanded reply threads for performance

Optimistic updates design

For immediate UI feedback, optimistic updates are applied for:

  • Comment creation (show in list immediately)
  • Like toggling (update local count immediately)
  • Reply creation (show in parent thread immediately)

Critical-path hook implementation

// src/features/comments/hooks/useCommentThread.ts
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import type { Comment, User } from '../types';

interface CommentThreadState {
  threads: Record<string, CommentThread>;
  currentUser: User | null;
  loading: boolean;
  error: string | null;
  
  // Actions
  initializeThread: (threadId: string, initialComments: Comment[]) => void;
  addComment: (threadId: string, comment: Comment) => void;
  updateComment: (threadId: string, commentId: string, updates: Partial<Comment>) => void;
  deleteComment: (threadId: string, commentId: string) => void;
  toggleLike: (threadId: string, commentId: string) => Promise<void>;
  setReplyTo: (threadId: string, commentId: string | null) => void;
  expandReplies: (threadId: string, commentId: string) => void;
  setSortOrder: (threadId: string, sortKey: 'newest' | 'oldest' | 'popular') => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
}

interface CommentThread {
  rootCommentId: string;
  comments: Record<string, Comment>;
  replyTo: string | null;
  expandedReplies: Set<string>;
  sortKey: 'newest' | 'oldest' | 'popular';
}

const useCommentThreadStore = create<CommentThreadState>(
  immer((set, get) => ({
    threads: {},
    currentUser: null,
    loading: false,
    error: null,

    initializeThread: (threadId, initialComments) => {
      set((state) => {
        const comments = initialComments.reduce((acc, comment) => {
          acc[comment.id] = comment;
          return acc;
        }, {} as Record<string, Comment>);
        
        state.threads[threadId] = {
          rootCommentId: initialComments[0]?.id || '',
          comments,
          replyTo: null,
          expandedReplies: new Set(),
          sortKey: 'newest',
        };
      });
    },

    addComment: (threadId, comment) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (!thread) return;
        
        // Optimistic update
        thread.comments[comment.id] = comment;
        if (comment.parentId) {
          const parent = thread.comments[comment.parentId];
          if (parent) {
            parent.replies.push(comment.id);
          }
        }
      });
    },

    updateComment: (threadId, commentId, updates) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (!thread || !thread.comments[commentId]) return;
        
        Object.assign(thread.comments[commentId], updates);
      });
    },

    deleteComment: (threadId, commentId) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (!thread || !thread.comments[commentId]) return;
        
        const comment = thread.comments[commentId];
        delete thread.comments[commentId];
        
        // Remove from parent's replies list
        if (comment.parentId) {
          const parent = thread.comments[comment.parentId];
          if (parent) {
            parent.replies = parent.replies.filter(id => id !== commentId);
          }
        }
      });
    },

    toggleLike: async (threadId, commentId) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (!thread || !thread.comments[commentId]) return;
        
        const comment = thread.comments[commentId];
        const wasLiked = comment.likedByCurrentUser;
        
        // Optimistic update
        comment.likes += wasLiked ? -1 : 1;
        comment.likedByCurrentUser = !wasLiked;
      });

      try {
        // API call to update server
        await api.updateCommentLike(commentId, !wasLiked);
      } catch (error) {
        // Revert optimistic update on failure
        set((state) => {
          const thread = state.threads[threadId];
          if (!thread || !thread.comments[commentId]) return;
          
          const comment = thread.comments[commentId];
          comment.likes += wasLiked ? 1 : -1;
          comment.likedByCurrentUser = wasLiked;
        });
      }
    },

    setReplyTo: (threadId, commentId) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (thread) {
          thread.replyTo = commentId;
        }
      });
    },

    expandReplies: (threadId, commentId) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (thread) {
          thread.expandedReplies.add(commentId);
        }
      });
    },

    setSortOrder: (threadId, sortKey) => {
      set((state) => {
        const thread = state.threads[threadId];
        if (thread) {
          thread.sortKey = sortKey;
        }
      });
    },

    setLoading: (loading) => set({ loading }),
    setError: (error) => set({ error }),
  }))
);

// Custom hook for comment thread operations
export const useCommentThread = (threadId: string) => {
  const {
    threads,
    currentUser,
    loading,
    error,
    addComment,
    updateComment,
    deleteComment,
    toggleLike,
    setReplyTo,
    expandReplies,
    setSortOrder,
    setLoading,
    setError
  } = useCommentThreadStore();

  const thread = threads[threadId];
  
  return {
    // State
    thread,
    currentUser,
    loading,
    error,
    
    // Actions
    addComment,
    updateComment,
    deleteComment,
    toggleLike,
    setReplyTo,
    expandReplies,
    setSortOrder,
    setLoading,
    setError
  };
};

4. Routing & Navigation

Route Structure and Hierarchy

graph TD
    A[Root] --> B[Public Routes]
    A --> C[Protected Routes]
    B --> D[/login]
    B --> E[/register]
    B --> F[/posts/:postId]
    C --> G[/dashboard]
    C --> H[/comments]
    H --> I[/comments/:commentId/edit]
    H --> J[/comments/:commentId/reply]

The comment system will be integrated into the existing blog/posting platform with protected routes for authenticated users to manage their comments. Public routes will include read-only access to comments on posts, while protected routes will handle creation, editing, and deletion of comments.

Code Splitting Strategy

Route-based code splitting with dynamic imports:

// routes/comments.tsx
const CommentList = React.lazy(() => import('components/comments/CommentList'));
const CommentForm = React.lazy(() => import('components/comments/CommentForm'));
const CommentEdit = React.lazy(() => import('components/comments/CommentEdit'));

// Route configuration with lazy loading
const routes = [
  {
    path: '/posts/:postId/comments',
    element: <CommentList />,
    loader: commentLoader,
  },
  {
    path: '/comments/:commentId/edit',
    element: <CommentEdit />,
    loader: commentEditLoader,
  },
];

Justification: Route-based splitting is preferred over component-based because:

  1. Comments are only relevant in specific contexts (post detail pages)
  2. Reduces initial bundle size for users not viewing comments
  3. Enables better code organization by feature boundaries
// guards/CommentGuard.tsx
const CommentGuard: React.FC = ({ children }) => {
  const { isAuthenticated, user } = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login', { 
        replace: true,
        state: { from: location.pathname }
      });
    }
  }, [isAuthenticated, navigate]);

  return isAuthenticated ? children : null;
};

// Protected route wrapper
const ProtectedCommentRoute: React.FC = () => (
  <CommentGuard>
    <Outlet />
  </CommentGuard>
);

Deep Linking Support and URL Parameter Design

URL structure will follow REST conventions:

  • /posts/:postId/comments - List all comments for a post
  • /comments/:commentId - View specific comment (deep link)
  • /comments/:commentId/edit - Edit comment form
  • /posts/:postId/comments/reply-to/:parentId - Reply to specific comment

Query parameters for filtering:

  • ?sort=created_at&order=desc - Sort comments by creation date
  • ?page=2&limit=10 - Pagination support

Browser History Management and Scroll Restoration

// hooks/useScrollRestoration.ts
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

export const useScrollRestoration = () => {
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    // Restore scroll position when navigating back
    const handlePopState = () => {
      const savedPosition = sessionStorage.getItem(`scrollPosition_${location.pathname}`);
      if (savedPosition) {
        window.scrollTo(0, parseInt(savedPosition));
      }
    };

    window.addEventListener('popstate', handlePopState);
    
    return () => {
      window.removeEventListener('popstate', handlePopState);
      // Save current position before navigation
      sessionStorage.setItem(`scrollPosition_${location.pathname}`, window.scrollY.toString());
    };
  }, [location]);

  // Reset scroll to top on new route
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [location.pathname]);
};
// App.tsx - Main router setup with scroll restoration
const App: React.FC = () => {
  useScrollRestoration();
  
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/posts/:postId/comments" element={<CommentList />} />
        <Route path="/comments/:commentId/edit" element={
          <ProtectedCommentRoute>
            <CommentEdit />
          </ProtectedCommentRoute>
        } />
      </Routes>
    </BrowserRouter>
  );
};

Scroll restoration strategy:

  1. Save scroll position when navigating away from a route
  2. Restore scroll position when returning to the same route
  3. Reset scroll to top when loading new content (new post, new page)
  4. Handle browser back/forward navigation properly

The scroll restoration ensures that users don't lose their place when navigating between comment lists and individual comment views, maintaining a seamless user experience.

5. API Integration Layer

API Client Design Pattern

We'll implement a custom fetch wrapper with axios as the underlying HTTP client for better request/response handling and interceptors support.

// api/client.ts
import axios, { AxiosInstance } 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 auth token injection
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor for error handling and normalization
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Handle auth errors - redirect to login or refresh token
      localStorage.removeItem('authToken');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Request/Response Interceptors

  • Auth Token Injection: Automatically adds Bearer token to all requests
  • Error Normalization: Converts API errors into consistent error objects
  • Retry Strategy: Implements exponential backoff for transient network errors

Error Handling and Retry Strategy

// 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) {
    return new APIError(
      error.response.status,
      error.response.data?.message || 'API Error',
      error.response.data?.code
    );
  }
  
  if (error.request) {
    return new APIError(503, 'Network Error - Please check your connection');
  }
  
  return new APIError(500, 'Unknown Error occurred');
};

// Retry logic with exponential backoff
export const retryWithBackoff = async (
  fn: () => Promise<any>,
  retries = 3,
  delay = 1000
): Promise<any> => {
  try {
    return await fn();
  } catch (error) {
    if (retries > 0 && [502, 503, 504].includes((error as AxiosError).response?.status || 0)) {
      await new Promise(resolve => setTimeout(resolve, delay));
      return retryWithBackoff(fn, retries - 1, delay * 2);
    }
    throw error;
  }
};

Loading, Error, and Empty States Design

All comment operations will follow a consistent loading/error/empty state pattern:

// types/comment.ts
export type CommentState = 'idle' | 'loading' | 'success' | 'error';

export interface Comment {
  id: string;
  content: string;
  author: {
    id: string;
    name: string;
    avatar?: string;
  };
  createdAt: string;
  updatedAt: string;
  likes: number;
  replies: Comment[];
  isLiked?: boolean;
}

export interface CommentsResponse {
  comments: Comment[];
  total: number;
  page: number;
  limit: number;
}

Data Fetching Strategy

For comment system, we'll use Client-Side Rendering (CSR) with React Query for optimal performance and caching:

  • Comments List: Fetch on demand when user navigates to comments section
  • Real-time Updates: Polling for new comments (every 30s) with WebSocket fallback
  • Caching Strategy:
    • Cache comments for 30 seconds (short-lived due to real-time nature)
    • Use React Query's built-in cache invalidation on mutations

API Response Transformation Layer

// api/transformers.ts
import { Comment } from 'types/comment';

export const transformCommentResponse = (rawComment: any): Comment => ({
  id: rawComment.id,
  content: rawComment.content,
  author: {
    id: rawComment.author.id,
    name: rawComment.author.name,
    avatar: rawComment.author.avatar,
  },
  createdAt: rawComment.created_at,
  updatedAt: rawComment.updated_at,
  likes: rawComment.likes_count || 0,
  replies: rawComment.replies?.map(transformCommentResponse) || [],
  isLiked: rawComment.is_liked,
});

Sequence Diagram for Primary Data Flows

sequenceDiagram
    participant U as User
    participant C as CommentComponent
    participant Q as ReactQuery
    participant A as API Client
    participant S as Server

    U->>C: View comments section
    C->>Q: Fetch comments (queryKey: ['comments', 'postId'])
    Q->>A: GET /api/comments?postId=123
    A->>S: Request comments
    S-->>A: Response with comments
    A-->>Q: Return transformed comments
    Q-->>C: Update UI with comments
    C->>U: Render comments list

    U->>C: Submit new comment
    C->>Q: Mutate (addComment)
    Q->>A: POST /api/comments
    A->>S: Create comment request
    S-->>A: Created comment response
    A-->>Q: Update cache with new comment
    Q-->>C: Update UI with new comment
    C->>U: Show success feedback

Comment API Endpoints

// api/endpoints.ts
export const COMMENT_API_ENDPOINTS = {
  GET_COMMENTS: (postId: string, page: number = 1, limit: number = 20) => 
    `/api/comments?postId=${postId}&page=${page}&limit=${limit}`,
  CREATE_COMMENT: '/api/comments',
  UPDATE_COMMENT: (commentId: string) => `/api/comments/${commentId}`,
  DELETE_COMMENT: (commentId: string) => `/api/comments/${commentId}`,
  LIKE_COMMENT: (commentId: string) => `/api/comments/${commentId}/like`,
  UNLIKE_COMMENT: (commentId: string) => `/api/comments/${commentId}/unlike`,
};

Data Fetching Implementation

// hooks/useComments.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from 'api/client';
import { Comment, CommentsResponse } from 'types/comment';
import { transformCommentResponse } from 'api/transformers';

export const useComments = (postId: string, page: number = 1) => {
  return useQuery<CommentsResponse, Error>({
    queryKey: ['comments', postId, page],
    queryFn: async () => {
      const response = await apiClient.get<CommentsResponse>(
        COMMENT_API_ENDPOINTS.GET_COMMENTS(postId, page)
      );
      return response.data;
    },
    staleTime: 30000, // 30 seconds
    cacheTime: 60000, // 1 minute
  });
};

export const useCreateComment = () => {
  const queryClient = useQueryClient();
  
  return useMutation<Comment, Error, { content: string; postId: string }>({
    mutationFn: async ({ content, postId }) => {
      const response = await apiClient.post<Comment>(
        COMMENT_API_ENDPOINTS.CREATE_COMMENT,
        { content, postId }
      );
      return transformCommentResponse(response.data);
    },
    onSuccess: (newComment, { postId }) => {
      // Invalidate and refetch comments
      queryClient.invalidateQueries({ queryKey: ['comments', postId] });
      
      // Optimistically update cache
      queryClient.setQueryData<CommentsResponse>(
        ['comments', postId],
        (oldData) => {
          if (!oldData) return undefined;
          return {
            ...oldData,
            comments: [newComment, ...oldData.comments],
            total: oldData.total + 1,
          };
        }
      );
    },
  });
};

6. Performance Design

Core Web Vitals Targets

  • Largest Contentful Paint (LCP): <2.5s for 95% of users
  • Interaction to Next Paint (INP): <200ms for 90% of interactions
  • Cumulative Layout Shift (CLS): <0.1 for 95% of users

Bundle Size Budget per Route

  • Comments List Page: <150KB (including all dependencies)
  • Comment Creation Modal: <80KB
  • Comment Editing Flow: <60KB

Critical Rendering Path Optimization

// Critical CSS for comment components to reduce FOUC
const criticalCommentStyles = `
  .comment-container { 
    min-height: 60px; 
    opacity: 0;
    transition: opacity 0.2s ease-in-out;
  }
  .comment-container.loaded {
    opacity: 1;
  }
  .comment-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
  }
`;

Image Optimization Strategy

  • Formats: WebP for modern browsers, JPEG fallback
  • Lazy loading: loading="lazy" attribute on all user avatars
  • Responsive images: srcSet with multiple sizes (1x, 1.5x, 2x)
  • Preloading: Critical avatars (first 3 comments) with preload link tags

Font Loading Strategy

// Critical font loading for comment UI
const fontPreload = `
  <link rel="preload" href="/fonts/inter-regular.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/fonts/inter-bold.woff2" as="font" type="font/woff2" crossorigin>
`;

// Font display optimization
const fontStyles = `
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-regular.woff2') format('woff2');
    font-display: swap;
  }
`;

Memoization Strategy

// Memoize expensive comment processing operations
const CommentItem = React.memo(({ comment, onReply }: CommentProps) => {
  const processedContent = useMemo(() => {
    return processMarkdown(comment.content);
  }, [comment.content]);

  const formattedDate = useMemo(() => {
    return formatRelativeTime(comment.createdAt);
  }, [comment.createdAt]);

  return (
    <div className="comment-container">
      <img 
        src={comment.author.avatar} 
        alt={comment.author.name}
        loading="lazy"
        className="comment-avatar"
      />
      <div className="comment-content">
        <span className="comment-author">{comment.author.name}</span>
        <p className="comment-text">{processedContent}</p>
        <span className="comment-timestamp">{formattedDate}</span>
      </div>
    </div>
  );
});

// Memoize expensive state calculations in hooks
const useCommentState = () => {
  const [comments, setComments] = useState<Comment[]>([]);
  
  const sortedComments = useMemo(() => {
    return [...comments].sort((a, b) => 
      new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
    );
  }, [comments]);

  return { comments: sortedComments };
};

Virtualization for Large Lists

// Virtualized comment list using react-window
import { FixedSizeList as List } from 'react-window';

const VirtualizedCommentList = ({ comments }: { comments: Comment[] }) => {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <CommentItem comment={comments[index]} />
    </div>
  );

  return (
    <List
      height={600}
      itemCount={comments.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
};

Prefetching and Preloading Strategy

// Prefetch next page of comments when user scrolls near bottom
const useCommentPrefetch = (comments: Comment[], hasMore: boolean) => {
  useEffect(() => {
    const handleScroll = () => {
      if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000) {
        // Prefetch next page of comments
        prefetchCommentsPage();
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [hasMore]);
};

// Preload user avatars for top N comments
const preloadCommentAvatars = (comments: Comment[]) => {
  const avatarUrls = comments.slice(0, 5).map(c => c.author.avatar);
  avatarUrls.forEach(url => {
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = url;
    document.head.appendChild(link);
  });
};

7. Error Handling & Resilience

Error Boundary Placement Strategy

Error boundaries should be strategically placed to prevent cascading failures while providing meaningful feedback to users. The placement strategy includes:

  1. Global Error Boundary - Wrapped around the entire application to catch unhandled errors
  2. Component-Level Boundaries - For critical components like comment list and form submission
  3. List Item Boundaries - Individual comment items to prevent one bad comment from breaking the entire list
graph TD
    A[App] --> B[ErrorBoundary]
    B --> C[CommentList]
    B --> D[CommentForm]
    C --> E[CommentItem]
    E --> F[CommentContent]

Global Error Handler Design

A centralized error handler will capture unhandled errors and provide appropriate user feedback:

// GlobalErrorHandler.tsx
import React from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error;
  errorInfo?: React.ErrorInfo;
}

class GlobalErrorHandler extends React.Component<{}, ErrorBoundaryState> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Unhandled error caught by global boundary:', error, errorInfo);
    // Log to monitoring service
    this.logErrorToService(error, errorInfo);
  }

  private logErrorToService = (error: Error, errorInfo: React.ErrorInfo) => {
    // Implementation for sending to Sentry/Datadog
  };

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h2>Something went wrong.</h2>
          <p>We're working to fix this issue. Please try again later.</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default GlobalErrorHandler;

Offline / Network Failure Handling

The comment system should gracefully handle network failures:

// OfflineCommentService.ts
class OfflineCommentService {
  private readonly OFFLINE_STORAGE_KEY = 'offline-comments';
  
  async submitComment(comment: CommentPayload): Promise<CommentResponse> {
    try {
      const response = await this.submitToAPI(comment);
      return response;
    } catch (error) {
      if (this.isNetworkError(error)) {
        // Store offline and retry later
        this.storeOffline(comment);
        this.scheduleRetry();
        throw new Error('Comment saved offline. Will retry when connection restored.');
      }
      throw error;
    }
  }

  private isNetworkError(error: any): boolean {
    return (
      error.name === 'NetworkError' ||
      error.code === 'ECONNABORTED' ||
      !navigator.onLine
    );
  }

  private storeOffline(comment: CommentPayload) {
    const offlineComments = this.getStoredComments();
    offlineComments.push({
      ...comment,
      id: Date.now().toString(),
      status: 'pending',
      timestamp: new Date().toISOString()
    });
    localStorage.setItem(this.OFFLINE_STORAGE_KEY, JSON.stringify(offlineComments));
  }

  private getStoredComments(): OfflineComment[] {
    const stored = localStorage.getItem(this.OFFLINE_STORAGE_KEY);
    return stored ? JSON.parse(stored) : [];
  }

  private async scheduleRetry() {
    // Retry logic with exponential backoff
    setTimeout(() => this.retryOfflineComments(), 5000);
  }

  private async retryOfflineComments() {
    const offlineComments = this.getStoredComments();
    const successful: CommentResponse[] = [];
    
    for (const comment of offlineComments) {
      try {
        const response = await this.submitToAPI(comment);
        successful.push(response);
      } catch (error) {
        // Keep in offline queue for next retry
        console.error('Offline comment retry failed:', error);
      }
    }

    // Update local storage to remove successfully submitted comments
    const remaining = offlineComments.filter(
      comment => !successful.some(s => s.id === comment.id)
    );
    localStorage.setItem(this.OFFLINE_STORAGE_KEY, JSON.stringify(remaining));
  }
}

Graceful Degradation Patterns

When API calls fail, the system should provide fallback behavior:

// CommentList.tsx
interface CommentListProps {
  postId: string;
  fallbackComments?: Comment[];
}

const CommentList: React.FC<CommentListProps> = ({ 
  postId, 
  fallbackComments 
}) => {
  const [comments, setComments] = useState<Comment[]>(fallbackComments || []);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchComments = async () => {
      try {
        const data = await api.fetchComments(postId);
        setComments(data);
      } catch (err) {
        setError('Failed to load comments. Showing cached version.');
        // Optionally show fallback UI or use cached data
      } finally {
        setLoading(false);
      }
    };

    fetchComments();
  }, [postId]);

  if (loading) {
    return <div className="loading-skeleton">Loading comments...</div>;
  }

  if (error) {
    return (
      <div className="comment-error">
        <p>{error}</p>
        <p>Showing last known comments</p>
      </div>
    );
  }

  return (
    <div className="comment-list">
      {comments.map(comment => (
        <CommentItem key={comment.id} comment={comment} />
      ))}
    </div>
  );
};

User Feedback on Errors

Comprehensive error feedback using toast notifications:

// useToast.ts
import { useState } from 'react';

type ToastType = 'success' | 'error' | 'warning' | 'info';

interface Toast {
  id: string;
  type: ToastType;
  message: string;
  duration?: number;
}

const useToast = () => {
  const [toasts, setToasts] = useState<Toast[]>([]);

  const addToast = (type: ToastType, message: string, duration = 5000) => {
    const id = Date.now().toString();
    setToasts(prev => [...prev, { id, type, message, duration }]);
    
    // Auto-remove toast after duration
    setTimeout(() => {
      setToasts(prev => prev.filter(t => t.id !== id));
    }, duration);
  };

  const removeToast = (id: string) => {
    setToasts(prev => prev.filter(t => t.id !== id));
  };

  return { toasts, addToast, removeToast };
};

// CommentForm.tsx
const CommentForm = ({ postId }: { postId: string }) => {
  const [error, setError] = useState<string | null>(null);
  const { addToast } = useToast();
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const newComment = await api.submitComment({
        postId,
        content: e.target.content.value
      });
      
      addToast('success', 'Comment submitted successfully!');
      // Reset form or update local state
    } catch (err) {
      setError('Failed to submit comment. Please try again.');
      addToast('error', 'Failed to submit comment');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && (
        <div className="error-message">
          {error}
        </div>
      )}
      <textarea name="content" required />
      <button type="submit">Post Comment</button>
    </form>
  );
};

Error Recovery Strategy

Implement a multi-tiered recovery approach:

  1. Immediate Retry: For transient network issues (503, 429)
  2. Offline Queue: Store failed submissions for retry when online
  3. User Intervention: Provide manual retry options
  4. Fallback UI: Show cached or default content during failures
// CommentErrorRecovery.ts
class CommentErrorRecovery {
  private readonly MAX_RETRY_ATTEMPTS = 3;
  private readonly RETRY_DELAY_MS = 5000;

  async handleCommentError(
    error: Error,
    commentData: CommentPayload,
    attempt = 1
  ): Promise<CommentResponse | null> {
    if (attempt > this.MAX_RETRY_ATTEMPTS) {
      return null; // Give up after max attempts
    }

    if (this.isTransientError(error)) {
      // Wait before retry with exponential backoff
      await this.delay(this.RETRY_DELAY_MS * attempt);
      return this.retrySubmit(commentData, attempt + 1);
    }

    if (this.isNetworkError(error)) {
      // Save to offline queue
      this.saveToOfflineQueue(commentData);
      return null;
    }

    // For permanent errors, don't retry
    return null;
  }

  private isTransientError(error: Error): boolean {
    const transientCodes = [503, 429, 500];
    return transientCodes.includes(error.response?.status || 0);
  }

  private isNetworkError(error: Error): boolean {
    return error.name === 'NetworkError' || !navigator.onLine;
  }

  private async delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

8. Testing Strategy

Unit Testing

  • Component tests: Cover all interactive states (loading, error, success) and edge cases for comment list, comment form, and nested replies
  • Utility tests: Test comment formatting logic, user mention parsing, and markdown rendering helpers
  • Hook tests: Validate custom hooks for comment submission, deletion, and reply handling with mocked API responses
  • Target coverage: 90%+ for components, 85%+ for utilities and hooks
  • Testing library: Jest with React Testing Library (RTL) for DOM interactions, testing-library/react for custom renderers

Integration Testing Strategy

  • Form submission flow: Test complete comment creation including validation, API call, and UI update
  • Reply nesting: Validate nested comment rendering and interaction in multi-level reply scenarios
  • State synchronization: Ensure UI updates correctly when comments are added, edited, or deleted from server
  • User interaction sequences: Test comment editing flow (open edit, make changes, save/cancel)
  • Performance edge cases: Test with large comment trees and ensure proper rendering behavior

E2E Testing Scope

  • Tools: Playwright for cross-browser compatibility and reliable DOM interaction testing
  • Critical flows covered:
    • Comment creation with various content types (text, mentions, markdown)
    • Reply threading and nested comment display
    • Comment editing and deletion workflows
    • User authentication state transitions affecting comment visibility
    • Real-time comment updates in collaborative scenarios (if applicable)
  • Cross-browser coverage: Chrome, Firefox, Safari (desktop), Mobile Safari and Chrome

Visual Regression Testing

  • Tool: Percy or Chromatic for automated visual testing of comment UI states
  • Test cases:
    • Default comment list view
    • Comment with mentions and markdown formatting
    • Reply thread expansion/collapse states
    • Error states (network failure, validation errors)
    • Dark/light mode variants

Accessibility Testing Automation

  • Tool: axe-core integration in test suite with @axe-core/react for automated A11y checks
  • Coverage: All comment component states must pass WCAG 2.1 AA compliance for:
    • Keyboard navigation through all comment interactions
    • Proper ARIA labeling for nested replies and form elements
    • Sufficient color contrast in all comment content states
    • Focus management during edit and reply operations

Test Data Strategy and Fixtures

  • Test data patterns:
    • Minimal valid comment structure (id, author, content)
    • Complex nested reply trees with various depths
    • Edge case scenarios (empty content, long content, special characters)
  • Fixtures:
    • Mock API responses with different status codes (200, 400, 401, 500)
    • User profile fixtures with different roles and permissions
    • Multi-user scenarios for comment ownership and visibility tests

9. Accessibility (A11y)

WCAG 2.1 AA compliance checklist for this feature

The comment system must meet WCAG 2.1 AA standards with the following key requirements:

  • Text Contrast: All text must have a contrast ratio of at least 4.5:1 against its background (minimum 3:1 for large text)
  • Keyboard Navigation: Full functionality must be operable via keyboard alone (Tab/Shift+Tab navigation)
  • Focus Management: Clear visual focus indicators for all interactive elements
  • Screen Reader Support: Proper ARIA roles and semantic HTML structure
  • Error Handling: Clear error messages with programmatically associated labels
  • Form Validation: Real-time validation feedback with accessible error messages

Keyboard navigation support

All comment interactions must be fully keyboard accessible:

  1. Navigation: Users can tab through all interactive elements (comment input, submit button, reply buttons)
  2. Comment Submission: Enter key submits comments when focused on input field
  3. Reply Flow: Tab navigation through reply form elements, Esc key to cancel
  4. Pagination: Keyboard navigation for comment list pagination controls

Focus management strategy

Focus Trapping: When reply modals open, focus is trapped within the modal until closed:

const useFocusTrap = (isOpen: boolean, ref: React.RefObject<HTMLElement>) => {
  useEffect(() => {
    if (!isOpen) return;
    
    const handleTabKey = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return;
      
      const focusableElements = ref.current?.querySelectorAll(
        'button, input, 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', handleTabKey);
    return () => document.removeEventListener('keydown', handleTabKey);
  }, [isOpen]);
};

ARIA roles and attributes plan

Core Comment Components:

  • role="article" for each comment container
  • aria-describedby for error messages tied to form fields
  • aria-expanded for reply toggle buttons
  • aria-label for icon-only buttons (e.g., "Reply to comment")
  • aria-current="page" for active pagination item

Input Fields:

// Comment input field with ARIA attributes
<input
  type="text"
  id="comment-input"
  aria-describedby="comment-help-text comment-error"
  aria-invalid={hasError}
  aria-label="Add a comment"
/>

Screen reader compatibility

Comment List Structure:

<section aria-labelledby="comments-heading">
  <h2 id="comments-heading">Comments</h2>
  <ul role="list">
    <li role="article" aria-labelledby="comment-1-author">
      <h3 id="comment-1-author">Jane Doe</h3>
      <p>Comment content here...</p>
    </li>
  </ul>
</section>

Reply Form Accessibility:

// Reply form with proper labeling and ARIA
<form 
  role="dialog" 
  aria-labelledby="reply-form-title"
  aria-describedby="reply-form-description"
>
  <h3 id="reply-form-title">Reply to Comment</h3>
  <p id="reply-form-description">Enter your reply below</p>
  
  <label htmlFor="reply-input">
    Reply content
  </label>
  <textarea 
    id="reply-input"
    aria-describedby="reply-help-text"
  />
  
  <button type="submit" aria-disabled={isSubmitting}>
    Submit Reply
  </button>
</form>

Live Regions for Dynamic Updates:

// Announce new comment additions to screen readers
<div 
  aria-live="polite" 
  aria-atomic="true"
  className="sr-only"
>
  {newCommentCount > 0 && `Added ${newCommentCount} new comment${newCommentCount !== 1 ? 's' : ''}`}
</div>

Keyboard navigation implementation

Comment List Navigation:

  • Tab through comments in reading order
  • Enter key activates reply or expands comment content
  • Arrow keys navigate between nested comments
  • Escape key closes modals and cancels forms

Form Interaction:

  • Tab to form elements in logical order
  • Shift+Tab for reverse tabbing
  • Enter key submits forms when appropriate
  • Space key toggles checkbox states

Focus restoration patterns

When modals close, focus returns to the trigger element:

const useFocusRestoration = (isOpen: boolean, triggerRef: React.RefObject<HTMLElement>) => {
  useEffect(() => {
    if (!isOpen) return;
    
    const restoreFocus = () => {
      triggerRef.current?.focus();
    };
    
    // Handle escape key and modal close
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape') restoreFocus();
    };
    
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [isOpen]);
};

Additional accessibility considerations

  1. Skip Links: Provide "Skip to comments" link for keyboard users
  2. Reduced Motion: Respect prefers-reduced-motion media queries
  3. High Contrast Mode: Support for Windows High Contrast and macOS Dynamic Island
  4. Language Attributes: Proper lang attributes on content when multilingual
  5. Alternative Text: Placeholder images have descriptive alt text where appropriate

Testing approach

Accessibility testing will include:

  • Manual keyboard navigation testing
  • Screen reader testing with NVDA, JAWS, and VoiceOver
  • Automated axe-core testing in CI pipeline
  • WCAG 2.1 AA compliance validation
  • Focus order verification across all interactive elements

10. Security Considerations

XSS Prevention Strategy

The comment system must prevent cross-site scripting attacks by sanitizing all user-provided content before rendering. This includes:

  • Input sanitization: All comment text, usernames, and metadata must be sanitized using a robust HTML sanitizer like DOMPurify
  • Content security policy: Implement strict CSP headers to prevent inline script execution and restrict external resource loading
  • Attribute sanitization: Strip dangerous HTML attributes (onclick, onmouseover, etc.) from user content
  • URL validation: Validate and sanitize any URLs present in comments to prevent malicious redirects
// Example sanitization approach
import DOMPurify from 'dompurify';

const sanitizeCommentContent = (content: string): string => {
  return DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['b', 'i', 'u', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href', 'title'],
    ALLOWED_URI_REGEXP: /^https?:\/\/[^\s]+$/,
  });
};

CSRF Protection

To prevent cross-site request forgery attacks on comment operations:

  • CSRF tokens: Include CSRF tokens in all comment-related API requests
  • Token generation: Generate unique tokens per user session and validate on server-side
  • Token storage: Store CSRF tokens in secure, HTTP-only cookies or session storage
  • Request validation: Validate CSRF token on every comment creation/edit/delete operation

Content Security Policy (CSP) Headers

Implement strict CSP headers for the comment feature:

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' 'unsafe-inline' 'unsafe-eval'; 
  style-src 'self' 'unsafe-inline'; 
  img-src 'self' data: https:; 
  font-src 'self' https:;

Sensitive Data Handling in Frontend State

Protect sensitive information in frontend state:

  • No PII in local storage: Never store user identifiers or personal data in localStorage
  • Secure session management: Use secure, HttpOnly cookies for authentication tokens
  • State cleanup: Clear sensitive comment data from memory when component unmounts
  • Encrypted storage: For comments that need to persist, use encryption before storage

Input Sanitization Approach

Implement comprehensive input sanitization:

// Input validation and sanitization for comment creation
const validateAndSanitizeComment = (commentData: CommentInput): CommentInput => {
  const sanitized = {
    content: sanitizeCommentContent(commentData.content),
    author: commentData.author.trim(),
    parentId: commentData.parentId,
  };

  // Validate content length
  if (sanitized.content.length > MAX_COMMENT_LENGTH) {
    throw new Error('Comment exceeds maximum length');
  }

  // Validate author name format
  if (!/^[a-zA-Z0-9\s\-_]{2,50}$/.test(sanitized.author)) {
    throw new Error('Invalid author name format');
  }

  return sanitized;
};

API Request Security

Secure all comment-related API requests:

  • Rate limiting: Implement client-side rate limiting to prevent spam
  • Request signing: Sign API requests with user session tokens
  • Input length limits: Enforce maximum content lengths server-side
  • Content type validation: Validate request content types and headers

Security Testing Considerations

  • Penetration testing: Include XSS and CSRF vulnerability scans in security testing
  • Automated scanning: Integrate security scanners into CI/CD pipeline
  • Security audit logs: Log security-relevant events for monitoring and incident response

Known Vulnerabilities to Address

  • Comment injection attacks: Prevent malicious content from being injected into comment threads
  • Session hijacking: Protect against session theft through secure token handling
  • Denial of service: Implement rate limiting to prevent comment flooding attacks
  • Data exposure: Ensure no sensitive user data is inadvertently exposed through comments

The security design prioritizes defense in depth, combining client-side sanitization with server-side validation and comprehensive API protection mechanisms.

11. Internationalization (i18n)

Translation Key Structure and Naming Convention

Following the BEM-style naming convention with feature-scoped keys:

comments:
  placeholder: "Add a comment..."
  submit_button: "Post Comment"
  delete_confirm: "Are you sure you want to delete this comment?"
  deleted_notice: "Comment has been deleted"
  reply_to: "Reply to {author}"
  authored_by: "by {author}"
  timestamp: "{time} ago"
  no_comments: "No comments yet"
  load_more: "Load more comments"
  character_limit: "Characters remaining: {count}"
  error:
    submission_failed: "Failed to submit comment"
    deletion_failed: "Failed to delete comment"

RTL Support Considerations

RTL layout handling:

  • Comments container uses direction: rtl when locale is RTL
  • Reply buttons and indicators flip position in RTL context
  • User avatars maintain left/right alignment logic based on text direction
  • Text alignment for comments follows RTL conventions (right-aligned in RTL)

Date, Number, Currency Formatting Strategy

Using Intl.DateTimeFormat for timestamps:

// Format relative time like "2 hours ago"
const formatRelativeTime = (date: Date): string => {
  const now = new Date();
  const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
  
  const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
  if (diffInSeconds < 60) return rtf.format(-diffInSeconds, 'second');
  if (diffInSeconds < 3600) return rtf.format(-Math.floor(diffInSeconds / 60), 'minute');
  if (diffInSeconds < 86400) return rtf.format(-Math.floor(diffInSeconds / 3600), 'hour');
  return rtf.format(-Math.floor(diffInSeconds / 86400), 'day');
};

Locale Detection and Switching Strategy

Automatic locale detection from browser:

const detectLocale = (): string => {
  const browserLang = navigator.language || 'en-US';
  const supportedLocales = ['en-US', 'fr-FR', 'es-ES', 'ar-SA'];
  
  // Match to supported locales or default to en-US
  return supportedLocales.find(locale => 
    locale === browserLang || 
    locale.startsWith(browserLang.split('-')[0])
  ) || 'en-US';
};

Locale switching via user preference:

// In a context provider or global store
const switchLocale = (newLocale: string) => {
  localStorage.setItem('preferred_locale', newLocale);
  i18n.changeLanguage(newLocale);
};

Translation Implementation

Using react-i18next with fallback mechanisms:

// i18n config
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n
  .use(initReactI18next)
  .init({
    resources: {
      'en-US': { translation: require('./locales/en-US.json') },
      'fr-FR': { translation: require('./locales/fr-FR.json') },
      'ar-SA': { translation: require('./locales/ar-SA.json') }
    },
    fallbackLng: 'en-US',
    interpolation: {
      escapeValue: false
    }
  });

Translation Key Management

  • Keys organized by feature namespace (comments.*)
  • Fallback to English for missing translations
  • Translation files stored in src/locales/{locale}.json
  • Automated translation key extraction using i18next-parser
  • Translation quality checks in CI pipeline

Bidirectional Text Handling

For comments containing mixed content:

// Apply proper text direction to comment content
const CommentContent: React.FC<{ content: string }> = ({ content }) => {
  const isRTL = i18n.dir() === 'rtl';
  
  return (
    <div 
      className="comment-content"
      dir={isRTL ? 'rtl' : 'ltr'}
      style={{ 
        textAlign: isRTL ? 'right' : 'left',
        direction: isRTL ? 'rtl' : 'ltr'
      }}
    >
      {content}
    </div>
  );
};

Language-Specific Formatting Rules

Different pluralization rules for various languages:

// Using i18next with plural support
const t = useTranslation();
const commentCount = 5;

// Will automatically choose correct plural form based on locale
t('comments.character_limit', { count: commentCount });

Accessibility Considerations

  • Screen readers properly announce translated content
  • Language attributes (lang attribute) on comment containers for proper pronunciation
  • Dynamic language switching does not break screen reader navigation

Testing Strategy

  • Translation completeness testing via i18next-parser
  • RTL layout visual regression testing
  • Language switcher functional tests with different locales
  • Content readability validation across languages

12. Monitoring & Observability

Real User Monitoring (RUM) Metrics to Track

The comment system should track key performance indicators to ensure optimal user experience:

  • Comment Load Time: Time from component mount to full comment list rendering (target < 2s)
  • Comment Creation Latency: Time from user submission to successful API response (target < 1s)
  • Comment Edit/Update Time: Time from edit initiation to successful save (target < 1s)
  • Comment Deletion Latency: Time from delete action to successful API response (target < 1s)
  • Scroll Performance: Frame rate during infinite scroll operations (target > 60fps)
  • First Contentful Comment: Time from page load to first comment rendering (target < 1s)

Error Tracking Integration

Implement Sentry for comprehensive error monitoring:

// Error tracking integration with custom comment context
import * as Sentry from '@sentry/react';

const captureCommentError = (error: Error, context: any) => {
  Sentry.withScope((scope) => {
    scope.setExtra('commentContext', context);
    scope.setExtra('userId', getCurrentUserId());
    Sentry.captureException(error);
  });
};

Custom Events and User Journey Tracking

Track key user interactions with custom events:

// Event tracking for comment system
const trackCommentEvent = (event: string, properties?: Record<string, any>) => {
  const eventProperties = {
    component: 'CommentSystem',
    ...properties,
    timestamp: Date.now(),
  };
  
  // Send to analytics service (e.g., Mixpanel, Amplitude)
  analytics.track(event, eventProperties);
};

Events to track:

  • comment_created - with user_id, content_length, parent_comment_id
  • comment_edited - with user_id, edit_duration, content_change_size
  • comment_deleted - with user_id, deletion_reason
  • comment_loaded - with total_comments, load_time, page_number
  • comment_replied - with user_id, reply_target_id

Performance Monitoring and Regression Detection

Implement performance baselines:

// Performance monitoring hook
const useCommentPerformance = () => {
  const [metrics, setMetrics] = useState({
    loadTime: 0,
    renderTime: 0,
    apiLatency: 0,
  });

  const measureCommentLoad = (startTime: number, renderTime: number) => {
    const loadTime = performance.now() - startTime;
    setMetrics(prev => ({
      ...prev,
      loadTime,
      renderTime,
    }));
    
    // Alert if performance degrades beyond threshold
    if (loadTime > 3000) {
      trackCommentEvent('comment_load_performance_degraded', {
        loadTime,
        threshold: 3000,
      });
    }
  };

  return { metrics, measureCommentLoad };
};

Health Checks and Alerting

Implement system health monitoring:

// Comment system health check
const commentSystemHealth = {
  apiStatus: 'healthy', // healthy, degraded, unhealthy
  cacheStatus: 'active', // active, stale, disabled
  errorRate: 0.01, // percentage of failed requests
  latency: 250, // average ms
};

const healthCheckInterval = setInterval(() => {
  const errorRate = calculateErrorRate('comment');
  if (errorRate > 0.05) {
    // Alert on high error rate
    sendAlert('Comment system error rate above threshold', { errorRate });
  }
}, 30000); // Check every 30 seconds

Data Collection Strategy

  • Anonymous User Tracking: Collect usage patterns without personal identifiers
  • Feature Flagging: Track which comment features are most used (e.g., reply functionality, edit capability)
  • Geographic Distribution: Monitor comment activity by region for global performance tuning
  • Device Performance: Track performance differences across mobile vs. desktop

Integration with Existing Monitoring Stack

The comment system monitoring should integrate with:

  • Sentry for error tracking and performance monitoring
  • Datadog or New Relic for real-time dashboard visualization
  • Google Analytics for user behavior analytics
  • Custom dashboards for comment-specific KPIs (comment volume, engagement rate)
  • Alerting channels (Slack, email) for system degradation thresholds

Alert Thresholds and Escalation

Critical alerts:

  • API latency > 3s (escalate to on-call)
  • Error rate > 5% (notify engineering team)
  • Cache miss rate > 10% (trigger cache optimization)
  • Comment load time > 2s (notify frontend team)

Data Privacy Considerations

All monitoring data should:

  • Exclude PII (personal identifying information)
  • Comply with GDPR and CCPA regulations
  • Allow user opt-out for analytics tracking
  • Be aggregated for system health monitoring only

Observability Dashboard Components

Dashboard should include:

  1. Real-time comment activity (new comments per minute)
  2. API response times (95th percentile latency)
  3. Error rates by endpoint and error type
  4. User engagement metrics (comments per user, reply rate)
  5. Performance indicators (load times, frame rates)
  6. Cache effectiveness (hit rate, miss rate)

13. File & Folder Structure

Proposed Directory Structure

/src
  /features
    /comments
      /components
        CommentList.tsx
        CommentItem.tsx
        CommentForm.tsx
        CommentActions.tsx
      /hooks
        useComments.ts
        useCommentReplies.ts
      /types
        comment.types.ts
      /services
        comment.service.ts
      /utils
        comment.utils.ts
      CommentSection.tsx
      CommentSection.stories.tsx
  /shared
    /components
      Avatar.tsx
      MarkdownRenderer.tsx
      Tooltip.tsx
    /hooks
      useDebounce.ts
      useIntersectionObserver.ts
    /utils
      formatTimestamp.ts
      sanitizeHtml.ts
  /types
    comment.types.ts

Module Boundaries and Import Rules

  • Feature isolation: Comment system components are self-contained within /features/comments with no cross-feature imports
  • Shared components: Reusable UI elements like Avatar and MarkdownRenderer live in /shared/components
  • Barrel exports: Each feature has a barrel export (index.ts) for clean imports
  • Type safety: All types are centralized in their respective modules with clear import paths

Shared vs Feature-Specific Code Separation

Shared code (reusable across features):

  • Generic UI components (Avatar, Tooltip)
  • Utility functions (formatTimestamp, sanitizeHtml)
  • Common hooks (useDebounce, useIntersectionObserver)

Feature-specific code (comment system only):

  • Comment-related components and services
  • Custom comment hooks (useComments, useCommentReplies)
  • Comment-specific types and utilities

This structure supports clean separation of concerns while enabling easy reuse of common UI patterns and utility functions across the application. The feature-based organization makes it easy to understand and maintain the comment system as a cohesive unit while keeping shared components available for other features to leverage.

14. Dependencies & Third-Party Libraries

Key Libraries Selected with Justification

1. React Query (TanStack Query)

  • Justification: Excellent for server state management with built-in caching, invalidation, and automatic refetching. Provides optimal performance for comment threads with real-time updates and pagination.
  • Bundle Impact: ~15KB gzipped
  • Alternatives Considered: Redux Toolkit, Zustand - React Query offers superior caching strategy and built-in network state handling for comment systems.

2. React Hook Form

  • Justification: Production-ready form handling with validation, error management, and performance optimizations. Essential for comment creation forms with real-time validation.
  • Bundle Impact: ~8KB gzipped
  • Alternatives Considered: Formik - React Hook Form provides better performance and more intuitive API for complex forms.

3. React Markdown

  • Justification: Safe, performant markdown rendering with extensive customization options. Critical for displaying user-generated comment content safely.
  • Bundle Impact: ~12KB gzipped
  • Alternatives Considered: marked.js - React Markdown offers better integration with React and security features.

4. react-virtualized (or react-window)

  • Justification: Efficient virtualization for large comment lists. Essential for performance when displaying hundreds of comments.
  • Bundle Impact: ~5KB gzipped
  • Alternatives Considered: react-virtual - react-window offers better performance and more active maintenance.

5. date-fns

  • Justification: Modular date formatting with excellent i18n support and zero runtime dependencies. Required for relative timestamps in comments.
  • Bundle Impact: ~2KB gzipped
  • Alternatives Considered: moment.js - date-fns is more lightweight and modular, avoiding the "moment problem" with large bundles.

6. react-hotkeys

  • Justification: Clean keyboard shortcut handling for comment actions (e.g., Ctrl+Enter to submit). Improves accessibility and UX.
  • Bundle Impact: ~2KB gzipped
  • Alternatives Considered: hotkeys-js - react-hotkeys provides better React integration and component lifecycle management.

Alternatives Considered and Rejected

Zustand vs. Redux Toolkit

  • Rejected: While Zustand offers simpler API, Redux Toolkit provides better debugging tools, middleware support, and ecosystem integration for complex comment systems with multiple data sources.

React Context API Alone

  • Rejected: Context API lacks built-in caching, optimistic updates, and automatic refetching that comment systems require for real-time interactions.

React-Router v6

  • Rejected: While it provides excellent routing capabilities, the feature set is already well-covered by existing routing solutions and doesn't add significant value over the current ecosystem.

Licensing and Bundle Size Impact Assessment

Bundle Size Budget Allocation:

  • Core Comment System: ~40KB (excluding React, ReactDOM)
  • Performance Optimization: ~25KB (react-window, react-virtualized)
  • Form Handling & Validation: ~10KB (react-hook-form)
  • Markdown Rendering: ~15KB (react-markdown)
  • Date Formatting & Accessibility: ~5KB (date-fns, react-hotkeys)

Total Estimated Bundle Size: ~95KB gzipped for comment system dependencies

Licensing Compliance: All selected libraries are MIT licensed with no commercial restrictions. The team will maintain license compliance through automated tools and regular audits to ensure all dependencies meet company security standards.

Security Considerations: All libraries are actively maintained with regular security updates. The team will implement automated dependency scanning to detect vulnerabilities in the comment system's third-party dependencies.

15. Open Questions, Risks & Trade-offs

Decisions that need product/engineering alignment

  • Comment moderation workflow: Should comments be immediately visible or require approval? How do we handle the "pending" state and user notifications?
  • Comment threading depth limits: Should we support infinite nesting or cap at 3-5 levels? What's the performance impact of deep nesting?
  • Real-time vs. polling updates: Should comments update in real-time via WebSockets or rely on periodic polling? What's the acceptable latency for comment visibility?
  • Comment deletion policy: Can users delete their own comments? Should admins be able to delete any comment? How do we handle soft vs. hard deletion?

Known trade-offs made in this design

  • Simplified UI state machine: Prioritized clean component architecture over complex multi-step comment flows (e.g., draft saving, auto-save) to reduce cognitive load and complexity
  • Conservative optimistic updates: Used minimal optimistic updates to avoid race conditions and ensure data consistency, at cost of perceived responsiveness
  • Monolithic comment component: Chose single CommentList component over modular comment item components for better performance in large lists, despite reduced reusability
  • Client-side sorting: Implemented client-side comment ordering rather than server-side to reduce API roundtrips, but this may not scale well for very large comment sets

Risks and mitigations

  • Performance degradation with large comment threads: Risk of UI lag when rendering hundreds of nested comments. Mitigation: Implement virtualization and lazy loading for deep nesting
  • Race conditions in comment creation: Risk of duplicate comments or out-of-order display. Mitigation: Use optimistic updates with unique identifiers and proper server-side deduplication
  • Scalability of comment storage: Risk of comment database growth becoming unmanageable. Mitigation: Implement comment pruning and archiving strategies with configurable retention policies
  • Moderation abuse: Risk of spam or inappropriate content flooding the system. Mitigation: Implement rate limiting, content filtering, and moderation queue with automated flagging

Technical debt acknowledged

  • Hardcoded comment display logic: Comment rendering logic is currently hardcoded rather than abstracted into a flexible component system, making future customization more difficult
  • Limited comment analytics: No built-in tracking of engagement metrics or user behavior patterns around comments, requiring future feature development to add this capability
  • Basic notification system: Simple toast notifications rather than a comprehensive real-time messaging system that could handle various user interaction types
  • No comment search capability: Future requirement for searching within comments is not addressed in current design, requiring architectural refactoring later

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.