This document presents a complete low-level design for a shopping cart system that handles concurrent user interactions, maintains data consistency, and provides seamless user experience across multiple sessions and devices.
1. Problem Statement & Requirements
Clarifying questions
- What's the target browser support matrix? Are we supporting IE11, or just modern browsers?
- Do we need real-time cart updates across devices (e.g., mobile to web sync) or is it static per session?
- Will users need to access their cart offline (e.g., for purchase flow) or is it strictly online?
- What's the expected cart size range? Will we support 1000+ items or is it typically under 50?
- Are there any accessibility requirements beyond WCAG AA compliance (e.g., screen reader support for item details)?
- Do we need i18n support for cart display (currency, date formats, language)?
- Is there an existing design system we must integrate with?
Functional requirements (user-facing)
- Users can add/remove items from cart
- Users can update item quantities in cart
- Cart persists across browser sessions (local storage or server-side)
- Users can view item details (name, price, quantity, total)
- Users can proceed to checkout or continue shopping
- Cart shows item subtotal and overall total
- Users can apply discount codes or coupons
- Users can remove all items from cart
Non-functional requirements
- Performance: Cart should render and update within 100ms for up to 50 items
- Accessibility: Full keyboard navigation and ARIA support for screen readers
- Scalability: Support for 1000+ items in cart without performance degradation
- Responsiveness: Mobile-first design with touch-friendly controls
- Security: No sensitive data in client-side cart state
Constraints and assumptions
- Cart items are stored in browser local storage or server-side session
- No real-time synchronization across devices (per session only)
- Items are immutable once added to cart (quantity is the only mutable property)
- Cart updates are not persisted to backend until checkout
- No complex inventory logic (inventory is managed at item level, not cart level)
- Users can have only one active cart per session
Out of scope
- Cart sharing between users or devices
- Advanced inventory management (back-in-stock notifications, etc.)
- Multi-cart support (multiple carts per user)
- Cart abandonment tracking or analytics
- Integration with third-party payment providers
- Multi-currency support (currency is determined by user locale)
- Product reviews or ratings in cart items
- Wishlist functionality (separate feature)
2. Component Architecture
Component Tree Diagram
graph TD
A[ShoppingCart] --> B[CartItem]
A --> C[CartSummary]
A --> D[EmptyCart]
B --> E[ProductImage]
B --> F[ProductInfo]
B --> G[QuantitySelector]
B --> H[RemoveItemButton]
C --> I[Subtotal]
C --> J[ShippingEstimate]
C --> K[Total]
C --> L[CheckoutButton]
Component Breakdown
Smart Components (Container):
ShoppingCart- orchestrates cart state, handles API calls, manages UI flowCartItem- manages individual item state and interactionsCartSummary- calculates and displays cart totals
Dumb Components (Presentational):
ProductImage- renders product image with proper sizingProductInfo- displays product name, price, and descriptionQuantitySelector- handles quantity input with validationRemoveItemButton- triggers item removal from cartEmptyCart- renders empty state UI
TypeScript Props Contracts
// ShoppingCart Component
interface ShoppingCartProps {
cartItems: CartItem[];
isLoading: boolean;
error: string | null;
onRemoveItem: (itemId: string) => void;
onUpdateQuantity: (itemId: string, quantity: number) => void;
onCheckout: () => void;
}
// CartItem Component
interface CartItemProps {
item: CartItem;
onRemove: (itemId: string) => void;
onUpdateQuantity: (itemId: string, quantity: number) => void;
}
// CartSummary Component
interface CartSummaryProps {
subtotal: number;
shippingEstimate: number;
total: number;
isLoading: boolean;
onCheckout: () => void;
}
// CartItem type definition
interface CartItem {
id: string;
productId: string;
name: string;
price: number;
quantity: number;
image: string;
description: string;
}
UI State Machine
stateDiagram-v2
[*] --> Idle
Idle --> Loading : fetchCart
Loading --> Success : cartLoaded
Loading --> Error : fetchFailed
Success --> Updating : updateItem
Updating --> Success : itemUpdated
Success --> Removing : removeItem
Removing --> Success : itemRemoved
Error --> Idle : retry
Success --> Checkout : proceedToCheckout
Checkout --> Processing : submitOrder
Processing --> Success : orderPlaced
Component Responsibilities
ShoppingCart:
- Fetches and manages cart state from API
- Handles cart item updates (quantity, removal)
- Coordinates checkout flow
- Manages loading and error states
CartItem:
- Renders individual cart item details
- Handles quantity change events
- Triggers item removal from cart
- Displays product information and image
CartSummary:
- Calculates and displays cart totals
- Renders checkout button
- Handles checkout initiation
Reusability Decisions
ProductImageandProductInfoare reusable across product listing and cart contextsQuantitySelectoris generic enough for any quantity input scenarioRemoveItemButtonhas consistent styling and behavior across components
Component Lifecycle Considerations
- Cart items should be fetched on component mount or when user navigates to cart page
- Quantity changes should trigger immediate local updates with debounced API sync
- Error states should provide clear retry mechanisms
- Empty cart state should show appropriate CTA and fallback UI
// Example of component lifecycle management in ShoppingCart
const ShoppingCart: React.FC<ShoppingCartProps> = ({
cartItems,
isLoading,
error,
onRemoveItem,
onUpdateQuantity,
onCheckout
}) => {
// Component mounts - fetch cart items if not already loaded
useEffect(() => {
if (!cartItems.length && !isLoading) {
// fetchCartItems()
}
}, [cartItems, isLoading]);
// Handle cart updates with optimistic UI
const handleQuantityChange = (itemId: string, quantity: number) => {
// Optimistically update UI
onUpdateQuantity(itemId, quantity);
// Then sync with backend (with retry logic)
// api.updateCartItem(itemId, quantity).catch(handleError);
};
return (
<div className="shopping-cart">
{isLoading ? (
<div>Loading cart...</div>
) : error ? (
<div className="error">Error loading cart: {error}</div>
) : cartItems.length === 0 ? (
<EmptyCart />
) : (
<>
{cartItems.map(item => (
<CartItem
key={item.id}
item={item}
onRemove={onRemoveItem}
onUpdateQuantity={handleQuantityChange}
/>
))}
<CartSummary
subtotal={calculateSubtotal(cartItems)}
shippingEstimate={calculateShipping()}
total={calculateTotal(cartItems)}
isLoading={isLoading}
onCheckout={onCheckout}
/>
</>
)}
</div>
);
};
3. State Management Design
For the shopping cart feature, I'll use a hybrid approach combining React Context API for global state and Zustand for optimized local state management. This provides the right balance of simplicity, performance, and scalability.
Local vs Global State Decisions
Local state: Product item details, cart form inputs, UI states (loading, error) Global state: Cart items, user session, cart metadata (subtotal, tax, shipping)
State Management Approach
I'll use Zustand as the primary state management solution because:
- Zero dependencies (lightweight)
- Simple API with minimal boilerplate
- Excellent dev tools support
- Good performance characteristics for cart operations
- Easy to test and mock
State Shape and Normalization Strategy
// CartItem interface
interface CartItem {
id: string;
productId: string;
productName: string;
productDescription: string;
price: number;
quantity: number;
image: string;
variant?: string;
}
// CartState interface
interface CartState {
items: CartItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
isLoading: boolean;
error: string | null;
isUpdating: boolean;
}
// CartStore interface
interface CartStore extends CartState {
addItem: (item: Omit<CartItem, 'id'>) => void;
removeItem: (itemId: string) => void;
updateQuantity: (itemId: string, quantity: number) => void;
clearCart: () => void;
calculateTotals: () => void;
loadCartFromStorage: () => void;
saveCartToStorage: () => void;
}
Server State vs Client State Separation
Client state: Cart items, UI states, local storage sync Server state: Product inventory, pricing (for validation), user session
Cache Invalidation Strategy
- Invalidate cart items when product price changes
- Clear cart on user logout
- Sync with server on page reload (if session exists)
- Validate items against server before checkout
Optimistic Updates Design
All cart modifications (add, update, remove) use optimistic updates:
- UI immediately reflects changes
- Server request sent asynchronously
- If server fails, revert to previous state with error toast
- Server response can override optimistic changes if needed
Critical-path Hook Implementation
// cartStore.ts - Core Zustand implementation
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { CartItem, CartStore } from './types';
const calculateTotals = (items: CartItem[]) => {
const subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const tax = subtotal * 0.08; // 8% tax rate
const shipping = items.length > 0 ? 5.99 : 0;
return {
subtotal,
tax,
shipping,
total: subtotal + tax + shipping
};
};
const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
subtotal: 0,
tax: 0,
shipping: 0,
total: 0,
isLoading: false,
error: null,
isUpdating: false,
addItem: (newItem) => {
set((state) => {
const existingItem = state.items.find(item =>
item.productId === newItem.productId &&
item.variant === newItem.variant
);
if (existingItem) {
return {
items: state.items.map(item =>
item.id === existingItem.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
const newItemWithId = {
...newItem,
id: `cart-item-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
};
return {
items: [...state.items, newItemWithId]
};
});
get().calculateTotals();
},
removeItem: (itemId) => {
set((state) => ({
items: state.items.filter(item => item.id !== itemId)
}));
get().calculateTotals();
},
updateQuantity: (itemId, quantity) => {
if (quantity <= 0) {
get().removeItem(itemId);
return;
}
set((state) => ({
items: state.items.map(item =>
item.id === itemId ? { ...item, quantity } : item
)
}));
get().calculateTotals();
},
clearCart: () => {
set({
items: [],
subtotal: 0,
tax: 0,
shipping: 0,
total: 0
});
},
calculateTotals: () => {
set((state) => {
const { subtotal, tax, shipping, total } = calculateTotals(state.items);
return { subtotal, tax, shipping, total };
});
},
loadCartFromStorage: () => {
// Implementation for loading from localStorage
},
saveCartToStorage: () => {
// Implementation for saving to localStorage
}
}),
{
name: 'shopping-cart-storage',
partialize: (state) => ({ items: state.items })
}
)
);
// Custom hook for cart operations
export const useCart = () => {
const {
items,
subtotal,
tax,
shipping,
total,
isLoading,
error,
isUpdating,
addItem,
removeItem,
updateQuantity,
clearCart
} = useCartStore();
return {
items,
subtotal,
tax,
shipping,
total,
isLoading,
error,
isUpdating,
addItem,
removeItem,
updateQuantity,
clearCart
};
};
// Example usage in component
const CartItemComponent: React.FC<{ item: CartItem }> = ({ item }) => {
const { updateQuantity } = useCart();
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newQuantity = parseInt(e.target.value);
if (!isNaN(newQuantity)) {
updateQuantity(item.id, newQuantity);
}
};
return (
<div className="cart-item">
<img src={item.image} alt={item.productName} />
<div className="item-details">
<h3>{item.productName}</h3>
<p>${item.price.toFixed(2)}</p>
<input
type="number"
value={item.quantity}
onChange={handleQuantityChange}
min="1"
/>
<p>Total: ${(item.price * item.quantity).toFixed(2)}</p>
</div>
</div>
);
};
This implementation provides:
- Optimistic UI updates for immediate user feedback
- Persistent storage with localStorage middleware
- Type-safe state management with TypeScript interfaces
- Modular, testable components
- Clear separation of concerns between cart logic and UI
- Performance optimizations through proper state management patterns
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[/products]
B --> G[/product/:id]
C --> H[/cart]
C --> I[/checkout]
C --> J[/orders]
C --> K[/profile]
The application will have a protected route structure with the following hierarchy:
/loginand/registeras public routes for authentication/productsand/product/:idas public product browsing/cart,/checkout,/orders, and/profileas protected routes requiring authentication
Code Splitting Strategy
Route-based code splitting will be implemented using React.lazy and Suspense for optimal bundle loading:
// routes/index.tsx
import { lazy } from 'react';
const CartPage = lazy(() => import('pages/CartPage'));
const CheckoutPage = lazy(() => import('pages/CheckoutPage'));
const OrdersPage = lazy(() => import('pages/OrdersPage'));
// Protected route wrapper with authentication check
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
};
// Route configuration
const routes = [
{
path: '/cart',
element: (
<ProtectedRoute>
<CartPage />
</ProtectedRoute>
),
lazy: true
},
{
path: '/checkout',
element: (
<ProtectedRoute>
<CheckoutPage />
</ProtectedRoute>
),
lazy: true
}
];
Justification: Route-based splitting ensures that cart and checkout pages load only when needed, reducing initial bundle size. The cart page will be split as it contains complex state management and UI components.
Navigation Guards and Authentication Flows
Authentication guards will be implemented using React Router's navigation hooks:
// hooks/useAuthRedirect.ts
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from 'contexts/AuthContext';
export const useAuthRedirect = () => {
const navigate = useNavigate();
const { isAuthenticated, isLoading } = useAuth();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate('/login', { replace: true });
}
}, [isAuthenticated, isLoading, navigate]);
};
Deep Linking Support and URL Parameter Design
Cart URLs will support deep linking with the following structure:
/cart?items=productId1,productId2- Pre-populated cart from product listing/cart?from=product/:id- Cart with item added from specific product page
URL parameters will be parsed and normalized:
// utils/cartUrlParser.ts
export const parseCartParams = (searchParams: URLSearchParams) => {
const items = searchParams.get('items')?.split(',') || [];
const from = searchParams.get('from') || null;
return {
items: items.filter(Boolean),
from
};
};
Browser History Management and Scroll Restoration
Scroll restoration will be handled using React Router's built-in scroll restoration:
// app/App.tsx
import { BrowserRouter, useScrollRestoration } from 'react-router-dom';
const App = () => {
return (
<BrowserRouter>
<ScrollRestoration />
<Routes>
{/* Route definitions */}
</Routes>
</BrowserRouter>
);
};
// Custom scroll restoration hook for specific page behavior
const ScrollRestoration = () => {
const location = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [location.pathname]);
return null;
};
The scroll restoration will:
- Reset scroll position when navigating between cart and other protected routes
- Preserve scroll position within the cart page during state changes
- Handle anchor links properly for product details in cart items
Navigation Patterns and User Flow
The primary navigation flow includes:
- Product browsing →
/products→/product/:id - Add to cart →
/cart(with item added) - Checkout process →
/checkout→/orders - Cart to checkout → Direct navigation from cart page
Navigation between these states will be handled via:
- Programmatic navigation using
useNavigate() - Back button behavior with proper history stack management
- Clear visual indicators for cart item count and status
// components/CartButton.tsx
const CartButton = () => {
const navigate = useNavigate();
const { cartItems } = useCart();
const handleViewCart = () => {
navigate('/cart');
};
return (
<button
onClick={handleViewCart}
aria-label={`Cart with ${cartItems.length} items`}
>
Cart ({cartItems.length})
</button>
);
};
5. API Integration Layer
API Client Design Pattern
We'll implement a centralized API client using Axios with a custom wrapper for consistent request handling and error management. The client will be built as a singleton to ensure consistent configuration across the application.
// api/client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
class ApiClient {
private client: AxiosInstance;
constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
this.setupInterceptors();
}
private setupInterceptors() {
// Request interceptor for adding auth tokens
this.client.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
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle unauthorized access
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
}
public get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.client.get<T>(url, config);
}
public post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.client.post<T>(url, data, config);
}
public put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.client.put<T>(url, data, config);
}
public delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.client.delete<T>(url, config);
}
}
export const apiClient = new ApiClient(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api');
Request/Response Interceptors
The request interceptor handles authentication token injection, while the response interceptor manages error states and redirects to login for 401 responses. Both interceptors follow the principle of least surprise and maintain consistent error handling across all API calls.
Error Handling and Retry Strategy
We implement a retry mechanism with exponential backoff for transient network errors:
// api/retry.ts
export const retryWithBackoff = async <T>(
fn: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> => {
let lastError: Error;
for (let i = 0; i <= retries; i++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (i === retries) {
throw lastError;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
throw lastError;
};
Loading, Error, and Empty States Design
We implement a standardized loading state pattern that works across all cart operations:
// types/cart.ts
export type CartState = {
items: CartItem[];
total: number;
loading: boolean;
error: string | null;
};
export type CartItem = {
id: string;
productId: string;
quantity: number;
price: number;
name: string;
image: string;
};
export type CartOperation = 'add' | 'update' | 'remove' | 'clear';
Data Fetching Strategy
For cart operations, we use client-side rendering (CSR) with React Query for optimal performance and caching:
- Cart items: Fetch on demand when cart is opened or user navigates to cart page
- Product details: Pre-fetch for items in cart to ensure consistency
- Server-side rendering: Not required as cart is user-specific and client-side only
API Response Transformation Layer
We implement a consistent DTO-to-domain model mapping layer:
// api/mappers.ts
import { CartItem, CartState } from '../types/cart';
export const mapCartItem = (dto: any): CartItem => ({
id: dto.id,
productId: dto.productId,
quantity: dto.quantity,
price: dto.price,
name: dto.product.name,
image: dto.product.image,
});
export const mapCartResponse = (dto: any): CartState => ({
items: dto.items.map(mapCartItem),
total: dto.total,
loading: false,
error: null,
});
Sequence Diagram for Primary Data Flows
sequenceDiagram
participant U as User
participant C as Cart Component
participant Q as React Query
participant A as API Client
participant S as Server
U->>C: Open Cart
C->>Q: Query cart items
Q->>A: GET /cart/items
A->>S: Request cart items
S-->>A: Response with items
A-->>Q: Return parsed items
Q-->>C: Update UI with items
U->>C: Add Item to Cart
C->>Q: Mutate add item
Q->>A: POST /cart/items
A->>S: Create cart item
S-->>A: Response with updated cart
A-->>Q: Return updated cart
Q-->>C: Update UI with new item
U->>C: Remove Item from Cart
C->>Q: Mutate remove item
Q->>A: DELETE /cart/items/{id}
A->>S: Remove cart item
S-->>A: Response with updated cart
A-->>Q: Return updated cart
Q-->>C: Update UI with removed item
This API integration layer provides a robust foundation for cart operations with proper error handling, caching, and performance optimizations while maintaining clean separation between API concerns and business logic.
6. Performance Design
Core Web Vitals Targets
- Largest Contentful Paint (LCP): <2.5s for cart items and summary
- Interaction to Next Paint (INP): <200ms for cart interactions (add/remove items, quantity changes)
- Cumulative Layout Shift (CLS): <0.1 for cart UI elements and animations
Bundle Size Budget per Route
- Cart Page: <150KB (including React, dependencies, and cart-specific logic)
- Product Listing Page: <100KB (shared components and cart integration)
- Checkout Flow: <250KB (includes payment UI and cart state sync)
Critical Rendering Path Optimization
- Critical CSS: Inline essential cart styles (item list, total calculation)
- Lazy Loading: Non-critical components like "Recommended for You" loaded on scroll
- Resource Hints: Preload cart item images, prefetch product detail endpoints
Image Optimization Strategy
- Formats: WebP for modern browsers, JPEG fallback for older browsers
- Lazy Loading:
loading="lazy"attribute on cart item images - Responsive Images:
srcSetwith multiple sizes (320px, 480px, 768px, 1024px) - Image Compression: 85% quality for cart thumbnails, 90% for product detail images
Font Loading Strategy
- Font Display:
swapfor cart item text to prevent FOIT - Preload Critical Fonts:
preloadfor primary font families (e.g., Inter, Roboto) - Font Subsets: Load only cart item font weights (regular, medium) to reduce load time
Memoization Strategy
// Memoize expensive calculations in cart item rendering
const CartItem = React.memo(({ item, onQuantityChange, onRemove }: CartItemProps) => {
const itemTotal = useMemo(() => calculateItemTotal(item), [item]);
return (
<div className="cart-item">
<img src={item.image} alt={item.name} loading="lazy" />
<span>{item.name}</span>
<input
type="number"
value={item.quantity}
onChange={(e) => onQuantityChange(item.id, parseInt(e.target.value))}
aria-label={`Update quantity for ${item.name}`}
/>
<span>{formatCurrency(itemTotal)}</span>
<button onClick={() => onRemove(item.id)} aria-label={`Remove ${item.name}`}>
Remove
</button>
</div>
);
});
// Memoize cart summary calculation
const CartSummary = React.memo(({ items, onCheckout }: CartSummaryProps) => {
const subtotal = useMemo(() => calculateSubtotal(items), [items]);
const tax = useMemo(() => calculateTax(subtotal), [subtotal]);
const total = useMemo(() => subtotal + tax, [subtotal, tax]);
return (
<div className="cart-summary">
<div>Subtotal: {formatCurrency(subtotal)}</div>
<div>Tax: {formatCurrency(tax)}</div>
<div>Total: {format总收入}</div>
<button onClick={onCheckout} disabled={items.length === 0}>
Proceed to Checkout
</button>
</div>
);
});
Virtualization for Large Lists
For carts with >50 items, implement virtualization:
import { FixedSizeList as List } from 'react-window';
const VirtualizedCartItems = ({ items, onQuantityChange, onRemove }: VirtualizedCartProps) => {
const itemHeight = 100; // Fixed height for each cart item row
return (
<List
height={500}
itemCount={items.length}
itemSize={itemHeight}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<CartItem
item={items[index]}
onQuantityChange={onQuantityChange}
onRemove={onRemove}
/>
</div>
)}
</List>
);
};
Prefetching and Preloading Strategy
- Product Detail Preload: When user adds items to cart, prefetch product detail pages for 3-5 most recently added items
- Checkout Route Preload: When user navigates to cart, preload checkout route dependencies (payment components, shipping options)
- Cache Strategy: Use HTTP cache headers for cart item images and product metadata (max-age: 3600)
- Service Worker: Implement cache-first strategy for cart-related API responses to reduce latency on repeat visits
// Example prefetching implementation
const prefetchProductDetails = (productIds: string[]) => {
const prefetchPromises = productIds.slice(0, 5).map(id =>
fetch(`/api/products/${id}`).then(res => res.json())
);
Promise.allSettled(prefetchPromises).catch(err => {
console.warn('Failed to prefetch product details:', err);
});
};
7. Error Handling & Resilience
Error Boundary Placement Strategy
Error boundaries should be strategically placed to provide the best user experience while maintaining system stability. The error boundary hierarchy will follow these principles:
- Application-level boundary - Covers entire cart UI to handle unexpected runtime errors
- Product item boundary - Individual cart item rendering errors (e.g., malformed product data)
- Action-specific boundaries - For critical cart operations like add/remove items
graph TD
A[App] --> B[CartErrorBoundary]
B --> C[CartContainer]
C --> D[CartItemErrorBoundary]
D --> E[CartItemComponent]
B --> F[CartActions]
Global Error Handler Design
A centralized error handler will capture unhandled promise rejections and runtime errors:
// src/lib/errorHandler.ts
import { captureException } from '@sentry/browser';
export class CartErrorHandler {
static handleGlobalError(error: Error, errorInfo?: React.ErrorInfo) {
console.error('Cart global error:', error, errorInfo);
// Report to monitoring service
captureException(error, {
contexts: {
cart: { errorInfo }
}
});
// Show user-friendly message
if (typeof window !== 'undefined') {
const toast = document.getElementById('cart-error-toast');
if (!toast) {
// Create toast notification
this.showUserToast('Something went wrong with your cart. Please try again.');
}
}
}
static showUserToast(message: string) {
// Implementation for toast notification system
console.warn('Cart Error Toast:', message);
}
}
Offline / Network Failure Handling
The cart should gracefully handle network interruptions:
// src/hooks/useCartOfflineHandler.ts
import { useState, useEffect } from 'react';
import { useNetworkStatus } from '@/hooks/useNetworkStatus';
export const useCartOfflineHandler = () => {
const [isOnline, setIsOnline] = useState(true);
const [offlineItems, setOfflineItems] = useState<CartItem[]>([]);
const networkStatus = useNetworkStatus();
useEffect(() => {
setIsOnline(networkStatus.isOnline);
if (!networkStatus.isOnline && offlineItems.length > 0) {
// Show offline indicator
document.body.classList.add('cart-offline');
} else {
document.body.classList.remove('cart-offline');
}
}, [networkStatus.isOnline, offlineItems]);
const handleOfflineAction = (action: () => Promise<void>) => {
if (!isOnline) {
// Queue action for later execution
return new Promise<void>((resolve, reject) => {
// Store in local storage or queue
resolve();
});
}
return action();
};
return {
isOnline,
handleOfflineAction,
offlineItems
};
};
Graceful Degradation Patterns
When cart functionality is partially available:
// src/components/CartContainer.tsx
const CartContainer = () => {
const [cartState, setCartState] = useState<CartState>('loading');
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchCart = async () => {
try {
// Attempt to load cart
const cartData = await fetchCartFromAPI();
setCartState('loaded');
} catch (err) {
setError(err as Error);
setCartState('error');
// Fallback to local storage if available
const localCart = localStorage.getItem('cart');
if (localCart) {
setCartState('fallback-loaded');
}
}
};
fetchCart();
}, []);
if (cartState === 'error') {
return (
<div className="cart-error-fallback">
<p>Unable to load cart. Showing cached version.</p>
<button onClick={() => window.location.reload()}>
Retry
</button>
</div>
);
}
if (cartState === 'fallback-loaded') {
return (
<div className="cart-fallback">
<p>Showing cached cart data</p>
<CartList />
</div>
);
}
return <CartList />;
};
User Feedback on Errors
Comprehensive error feedback system:
// src/components/CartErrorToast.tsx
interface CartErrorToastProps {
error: Error;
onRetry?: () => void;
onClose?: () => void;
}
const CartErrorToast = ({ error, onRetry, onClose }: CartErrorToastProps) => {
const [isVisible, setIsVisible] = useState(true);
if (!isVisible) return null;
return (
<div className="cart-error-toast" role="alert">
<div className="toast-content">
<h3>Cart Error</h3>
<p>{error.message}</p>
{onRetry && (
<button
onClick={onRetry}
className="retry-button"
aria-label="Retry cart operation"
>
Retry
</button>
)}
</div>
<button
onClick={() => {
setIsVisible(false);
onClose?.();
}}
className="close-button"
aria-label="Close error toast"
>
×
</button>
</div>
);
};
export default CartErrorToast;
Error Recovery Strategy
// src/lib/cartRecovery.ts
export class CartRecovery {
static async recoverFromError(
error: Error,
cartId: string
): Promise<CartState | null> {
// Attempt recovery based on error type
if (error.name === 'NetworkError') {
return this.handleNetworkError(cartId);
}
if (error.name === 'ValidationError') {
return this.handleValidationError(cartId);
}
// For unknown errors, attempt to restore from cache
return this.restoreFromCache(cartId);
}
private static async handleNetworkError(cartId: string) {
// Try to restore from local cache
const cachedCart = localStorage.getItem(`cart-${cartId}`);
if (cachedCart) {
return JSON.parse(cachedCart);
}
// Return null to show fallback UI
return null;
}
private static async handleValidationError(cartId: string) {
// Clear invalid cart and create new one
localStorage.removeItem(`cart-${cartId}`);
return null;
}
private static async restoreFromCache(cartId: string) {
const cachedCart = localStorage.getItem(`cart-${cartId}`);
if (cachedCart) {
return JSON.parse(cachedCart);
}
return null;
}
}
8. Testing Strategy
Unit Testing Approach
- Component-level testing: Test all presentational and container components with Jest + React Testing Library (RTL)
- Utility function testing: Test business logic, formatting functions, and data transformation utilities with Jest
- Hook testing: Specifically test custom hooks like
useCartanduseProductSearchwith React Testing Library's renderHook - Test coverage targets:
- Components: 90% line coverage, 85% branch coverage
- Utilities: 95% line coverage
- Hooks: 85% line coverage
Integration Testing Strategy
- Feature integration: Test cart item lifecycle (add, update quantity, remove) with realistic API mocks
- Cross-component interactions: Test the relationship between CartItem, CartSummary, and ProductSearch components
- State synchronization: Verify that cart updates propagate correctly between components and global state
E2E Testing Scope
- Tools: Playwright for end-to-end testing (chosen over Cypress due to better TypeScript support and parallel test execution)
- Critical user flows covered:
- Add product to cart → view in cart → update quantity → remove item
- Cart persistence across page reloads (mocked localStorage)
- Error states (network failure, invalid product data)
- Multi-item cart operations and validation
- Test coverage: 100% of core user journeys, 80% of edge cases
Visual Regression Testing
- Tool: Chromatic for visual regression testing
- Scope: All cart component states (empty, populated, loading, error) across different screen sizes
- Baseline: Maintain visual consistency with existing design system
Accessibility Testing Automation
- Tool: axe-core integration in Jest tests
- Testing scope:
- WCAG 2.1 AA compliance for all cart components
- Keyboard navigation testing (Tab order, focus management)
- Screen reader compatibility for all interactive elements
- Test coverage: 100% of cart UI components
Test Data Strategy
- Fixtures: Create realistic product and cart item fixtures with varied data types (different categories, prices, quantities)
- Mock API responses: Use MSW (Mock Service Worker) to simulate various server states:
- Success responses with valid cart data
- Network errors (500, 404)
- Rate limiting scenarios
- Data generation: Use faker.js for generating test data that mimics real-world product variations
// Example unit test structure for cart item component
import { render, screen } from '@testing-library/react';
import { CartItem } from './CartItem';
import { mockProduct } from '../__fixtures__/product';
describe('CartItem', () => {
it('renders product name and price correctly', () => {
const item = {
...mockProduct,
quantity: 2,
price: 29.99
};
render(<CartItem item={item} onRemove={() => {}} onUpdateQuantity={() => {}} />);
expect(screen.getByText('Test Product')).toBeInTheDocument();
expect(screen.getByText('$29.99')).toBeInTheDocument();
});
it('handles quantity change correctly', () => {
const mockUpdate = jest.fn();
const item = { ...mockProduct, quantity: 1 };
render(<CartItem item={item} onRemove={() => {}} onUpdateQuantity={mockUpdate} />);
const quantityInput = screen.getByRole('spinbutton');
userEvent.type(quantityInput, '{backspace}3');
expect(mockUpdate).toHaveBeenCalledWith(item.id, 3);
});
});
9. Accessibility (A11y)
WCAG 2.1 AA compliance checklist for this feature
The shopping cart 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
- Focus Management: Clear visual focus indicators for all interactive elements
- Screen Reader Support: Proper ARIA labeling and semantic HTML structure
- Form Validation: Clear error messages with programmatically associated labels
- Skip Links: Option to skip to main content or cart content
Keyboard navigation support
All cart functionality must be fully navigable using only keyboard:
- Tab through all interactive elements (quantity inputs, remove buttons, checkout button)
- Enter/Space to activate buttons and links
- Arrow keys for quantity adjustment (when focused on input)
- Escape key to close cart modal (if applicable)
- Home/End keys for quick navigation in lists
Focus management strategy
Focus handling will be implemented with:
- Focus trapping within cart modals to prevent focus from escaping
- Focus restoration when modals close, returning focus to the previous element (e.g., "Cart" button)
- Programmatic focus on error messages when validation fails
- Visual focus indicators that are clearly visible and consistent with design system
ARIA roles and attributes plan
// Example ARIA implementation for cart item
interface CartItemProps {
id: string;
title: string;
price: number;
quantity: number;
onQuantityChange: (newQuantity: number) => void;
onRemove: () => void;
}
const CartItem: React.FC<CartItemProps> = ({
id,
title,
price,
quantity,
onQuantityChange,
onRemove
}) => {
return (
<div
role="article"
aria-labelledby={`item-title-${id}`}
className="cart-item"
>
<h3 id={`item-title-${id}`} className="cart-item-title">
{title}
</h3>
<div className="cart-item-quantity-control">
<label htmlFor={`quantity-input-${id}`}>
Quantity:
</label>
<input
id={`quantity-input-${id}`}
type="number"
min="1"
value={quantity}
onChange={(e) => onQuantityChange(Number(e.target.value))}
aria-describedby={`item-price-${id}`}
className="quantity-input"
/>
</div>
<p id={`item-price-${id}`} className="cart-item-price">
${price.toFixed(2)}
</p>
<button
onClick={onRemove}
aria-label={`Remove ${title} from cart`}
className="remove-button"
>
Remove
</button>
</div>
);
};
Screen reader compatibility
- All interactive elements have proper ARIA roles and labels
- Dynamic content updates announce changes to screen readers (e.g., quantity change notifications)
- Error states provide clear, descriptive messages that screen readers can read
- Semantic HTML structure (h3 for titles, proper heading hierarchy)
- Landmark regions for main content and navigation
Additional A11y considerations
- Empty cart state: Screen reader announces "Cart is empty" with appropriate landmark
- Loading states: ARIA live regions announce loading progress for cart items
- Error messages: Programmatically associated with relevant form controls
- Colorblind accessibility: Testing with colorblind simulators and ensuring contrast meets standards
- Zoom compatibility: Cart items must remain readable at 200% zoom level
Keyboard navigation flow example:
- User presses Tab to navigate through cart items
- Each item has focusable quantity input and remove button
- When quantity input is focused, arrow keys adjust value
- Remove buttons have clear ARIA labels for screen readers
- Cart summary and checkout button are focusable at end of navigation
Focus trap implementation:
const useFocusTrap = (isOpen: boolean, containerRef: React.RefObject<HTMLElement>) => {
useEffect(() => {
if (!isOpen || !containerRef.current) return;
const focusableElements = containerRef.current.querySelectorAll(
'button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen]);
};
10. Security Considerations
XSS Prevention Strategy
- Input sanitization: All user-provided data (product names, descriptions, custom notes) must be sanitized before rendering using DOMPurify or similar library
- Content Security Policy (CSP): Implement strict CSP headers to prevent inline script execution and restrict external resource loading
- Template literal escaping: All dynamic content rendered in JSX must be properly escaped to prevent script injection
- Sanitize product metadata: Product titles, descriptions, and images must be validated to ensure no malicious content
CSRF Protection
- CSRF tokens: Include anti-CSRF tokens in all cart mutation requests (add, update, remove items)
- Token storage: Store CSRF tokens in secure HTTP-only cookies or React context with proper lifecycle management
- Token validation: Server-side validation of CSRF tokens for all cart modification endpoints
Content Security Policy (CSP) Headers
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';
img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:;
Sensitive Data Handling in Frontend State
- Cart item secrets: Do not store sensitive cart data (like pricing for unauthorized users) in client-side state
- Tokenized payment data: Never store credit card information or sensitive payment tokens in React state
- Session isolation: Cart state should be tied to user session and cleared on logout or session expiration
- Memory cleanup: Ensure cart state is properly cleaned up when user navigates away or logs out
Input Sanitization Approach
// Example input sanitization for cart item descriptions
const sanitizeCartItemDescription = (description: string): string => {
// Remove potentially dangerous HTML tags
const cleanDescription = DOMPurify.sanitize(description, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
ALLOWED_ATTR: []
});
// Truncate excessively long descriptions
return cleanDescription.length > 500
? cleanDescription.substring(0, 500) + '...'
: cleanDescription;
};
Security Headers Implementation
// Server-side CSP header configuration
const cspHeaders = {
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:",
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block'
};
Token Management Security
- Secure cookies: CSRF tokens stored in secure, HttpOnly cookies for session management
- Token rotation: Implement token refresh mechanism for long-lived sessions
- Session timeout: Automatic cart state cleanup on session expiration
- Cross-site isolation: Ensure cart operations are tied to same-origin policies
Data Validation Strategy
- Type validation: All cart item data must pass strict TypeScript validation before processing
- Range validation: Quantity values must be within acceptable ranges (1-999)
- Format validation: Product IDs must match expected UUID or numeric patterns
- Rate limiting: Client-side rate limiting to prevent cart spam attacks
Authentication Boundary Considerations
- Cart isolation: Each authenticated user's cart is isolated by session ID or user ID in state
- Protected routes: Cart operations require valid authentication tokens
- Authorization checks: Verify user ownership of cart items before modification
- Session binding: Cart state should be bound to specific authenticated sessions
11. Internationalization (i18n)
Translation Key Structure and Naming Convention
Following BEM-style naming with feature-scoped keys to ensure maintainability:
cart:
title: "Shopping Cart"
empty: "Your cart is empty"
item:
quantity: "Quantity"
remove: "Remove"
subtotal: "Subtotal"
total:
label: "Total"
amount: "Total Amount"
actions:
continue: "Continue Shopping"
checkout: "Proceed to Checkout"
errors:
item_not_found: "Item not found in cart"
insufficient_stock: "Insufficient stock for {{item}}"
RTL Support Considerations
RTL layout handling through CSS classes and direction properties:
dir="rtl"attribute on root element for RTL locales- CSS
text-align: rightfor cart item text content margin-left→margin-rightfor spacing in RTL- Flip icon orientations (e.g., chevron icons) for RTL
Date, Number, Currency Formatting Strategy
Using Intl API with locale-specific formatting:
// cart/utils/formatters.ts
export const formatCurrency = (amount: number, locale: string, currency: string) => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
};
export const formatNumber = (num: number, locale: string) => {
return new Intl.NumberFormat(locale).format(num);
};
export const formatDate = (date: Date, locale: string) => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
Locale Detection and Switching Strategy
Automatic detection via browser navigator.language with manual override:
// cart/hooks/useLocale.ts
import { useEffect, useState } from 'react';
export const useLocale = () => {
const [currentLocale, setCurrentLocale] = useState<string>(() => {
// Check localStorage for user preference
const savedLocale = localStorage.getItem('preferredLocale');
if (savedLocale) return savedLocale;
// Fallback to browser language or default
const browserLang = navigator.language.split('-')[0];
return ['en', 'es', 'fr', 'de'].includes(browserLang) ? browserLang : 'en';
});
useEffect(() => {
// Apply to document and update context
document.documentElement.lang = currentLocale;
document.documentElement.dir = ['ar', 'he'].includes(currentLocale) ? 'rtl' : 'ltr';
}, [currentLocale]);
const changeLocale = (newLocale: string) => {
setCurrentLocale(newLocale);
localStorage.setItem('preferredLocale', newLocale);
};
return { locale: currentLocale, changeLocale };
};
Translation File Organization
// locales/en.json
{
"cart": {
"title": "Shopping Cart",
"empty": "Your cart is empty",
"item": {
"quantity": "Quantity",
"remove": "Remove",
"subtotal": "Subtotal"
},
"total": {
"label": "Total",
"amount": "Total Amount"
},
"actions": {
"continue": "Continue Shopping",
"checkout": "Proceed to Checkout"
}
}
}
// locales/es.json
{
"cart": {
"title": "Carrito de Compras",
"empty": "Tu carrito está vacío",
"item": {
"quantity": "Cantidad",
"remove": "Eliminar",
"subtotal": "Subtotal"
},
"total": {
"label": "Total",
"amount": "Monto Total"
},
"actions": {
"continue": "Seguir Comprando",
"checkout": "Proceder al Pago"
}
}
}
i18n Provider Implementation
// cart/context/i18nContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import { loadTranslations } from '../utils/translationLoader';
interface I18nContextType {
t: (key: string, params?: Record<string, any>) => string;
locale: string;
changeLocale: (locale: string) => void;
}
const I18nContext = createContext<I18nContextType | undefined>(undefined);
export const I18nProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [locale, setLocale] = useState('en');
const [translations, setTranslations] = useState<Record<string, any>>({});
useEffect(() => {
loadTranslations(locale).then(setTranslations);
}, [locale]);
const t = (key: string, params?: Record<string, any>): string => {
const keys = key.split('.');
let translation = translations;
for (const k of keys) {
translation = translation[k];
if (!translation) return key;
}
if (typeof translation === 'string') {
return params ? interpolate(translation, params) : translation;
}
return key;
};
const changeLocale = (newLocale: string) => {
setLocale(newLocale);
localStorage.setItem('preferredLocale', newLocale);
};
return (
<I18nContext.Provider value={{ t, locale, changeLocale }}>
{children}
</I18nContext.Provider>
);
};
export const useI18n = () => {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n must be used within an I18nProvider');
}
return context;
};
const interpolate = (str: string, params: Record<string, any>): string => {
return str.replace(/\{\{(\w+)\}\}/g, (_, key) => params[key] || '');
};
12. Monitoring & Observability
Real User Monitoring (RUM) Metrics to Track
The shopping cart feature should track key performance indicators to understand user behavior and system health:
- Cart Load Time: Time from cart initialization to full render (target: < 200ms for 95% of users)
- Add to Cart Latency: Time from user action to successful API response (target: < 500ms)
- Cart Update Frequency: How often users modify cart items (target: < 100ms between updates)
- Cart Abandonment Rate: Percentage of users who add items but don't proceed to checkout
- Cart Conversion Rate: Ratio of cart additions to actual purchases (target: > 3% conversion rate)
Error Tracking Integration
Integrating Sentry or Datadog for comprehensive error tracking:
// Error tracking integration example
import * as Sentry from '@sentry/react';
const cartErrorBoundary = {
error: (error, errorInfo) => {
Sentry.captureException(error, {
contexts: { cart: { errorInfo } },
tags: { feature: 'cart', severity: 'high' }
});
}
};
Custom Events and User Journey Tracking
Implement custom event tracking for key cart interactions:
// Event tracking for cart actions
const trackCartEvent = (event: string, properties?: Record<string, any>) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', event, {
...properties,
timestamp: Date.now(),
userId: getCurrentUserId()
});
}
};
// Example usage in cart component
const handleAddToCart = (product: Product) => {
trackCartEvent('cart_add_item', {
productId: product.id,
price: product.price,
quantity: 1
});
};
Performance Monitoring and Regression Detection
Establish performance baselines with automated regression detection:
- Bundle Size Monitoring: Track cart-related bundle size changes (target: < 5KB increase per release)
- Memory Usage: Monitor memory consumption during cart operations (target: < 50MB peak)
- Render Performance: Track component re-renders and identify unnecessary updates
// Performance monitoring hook
const useCartPerformance = () => {
const [metrics, setMetrics] = useState({
loadTime: 0,
renderCount: 0,
memoryUsage: 0
});
useEffect(() => {
const startTime = performance.now();
// Track render count
setMetrics(prev => ({
...prev,
loadTime: performance.now() - startTime,
renderCount: prev.renderCount + 1
}));
// Memory monitoring (browser-specific)
if ('memory' in performance) {
const memory = performance.memory as any;
setMetrics(prev => ({
...prev,
memoryUsage: memory.usedJSHeapSize
}));
}
}, []);
return metrics;
};
Alerting and Incident Response
Configure alerts for critical cart failures:
- Cart API Failure Rate: > 5% error rate over 5-minute window
- Cart Load Time Degradation: > 20% increase in load time from baseline
- User Session Drop: > 10% of users unable to add items to cart
Data Pipeline Integration
Connect to existing observability infrastructure:
// Centralized monitoring service
class CartMonitoringService {
private readonly metrics = new Map<string, number>();
trackMetric(name: string, value: number) {
this.metrics.set(name, value);
// Send to monitoring backend
if (process.env.NODE_ENV === 'production') {
fetch('/api/monitoring/cart', {
method: 'POST',
body: JSON.stringify({
metric: name,
value,
timestamp: Date.now(),
sessionId: getSessionId()
})
});
}
}
trackUserAction(action: string, properties: Record<string, any>) {
// Send user action telemetry
this.trackMetric(`cart_action_${action}`, 1);
}
}
Dashboard and Visualization
Create dashboards to visualize cart performance:
- Cart Health Dashboard: Real-time metrics for cart operations
- User Journey Maps: Flow from product view to cart addition to checkout
- Error Rate Trends: Historical analysis of cart-related failures
Integration with Existing Monitoring Stack
The cart monitoring system should integrate with existing tools:
- Grafana: For dashboard visualization and alerting
- Prometheus: For metric collection and storage
- ELK Stack: For log aggregation and search capability
- New Relic: For application performance monitoring integration
Privacy and Data Protection
Ensure all monitoring respects user privacy:
- Anonymized User Tracking: Use session IDs instead of personal identifiers
- Data Retention Policies: Define how long cart data is stored (typically 30 days)
- Compliance with GDPR/CCPA: Ensure all tracking is opt-in or compliant with regulations
Rollback and Recovery Mechanisms
Implement monitoring-driven recovery:
// Health check for cart functionality
const cartHealthCheck = async () => {
try {
const response = await fetch('/api/cart/health');
const health = await response.json();
if (health.status !== 'healthy') {
// Trigger alert and potential rollback
Sentry.captureMessage('Cart health check failed', {
level: 'error',
extra: { health }
});
}
} catch (error) {
Sentry.captureException(error, {
contexts: { cart: { healthCheck: true } }
});
}
};
Test Coverage for Monitoring
Ensure monitoring systems are tested:
- Unit tests for metric tracking functions
- Integration tests for error reporting to monitoring services
- End-to-end tests verifying dashboard data accuracy
- Load testing to validate monitoring under high traffic conditions
13. File & Folder Structure
Proposed Directory Structure
src/
├── features/
│ └── cart/
│ ├── components/
│ │ ├── CartItem.tsx
│ │ ├── CartSummary.tsx
│ │ └── CartEmptyState.tsx
│ ├── hooks/
│ │ └── useCart.ts
│ ├── types/
│ │ └── cart.types.ts
│ ├── utils/
│ │ └── cart.utils.ts
│ ├── index.ts
│ └── CartPage.tsx
├── shared/
│ ├── components/
│ │ └── Button.tsx
│ ├── hooks/
│ │ └── useDebounce.ts
│ └── types/
│ └── common.types.ts
├── stores/
│ └── cartStore.ts
└── routes/
└── CartRoute.tsx
Module Boundaries and Import Rules
- Cart feature is self-contained with no cross-feature imports
- Shared components are imported only by cart components or shared utilities
- Stores are imported only by hooks and containers within the feature
- Barrel exports are used for clean imports:
import { CartItem } from 'features/cart'
Shared vs Feature-Specific Code Separation
- Feature-specific: All cart business logic, UI components, and types live within
features/cart - Shared: Reusable UI primitives like Button, Input, and common hooks live in
shared/ - Stores: State management is feature-specific but follows global store patterns for consistency
This structure enables:
- Clear ownership boundaries
- Easy feature isolation and testing
- Scalable component reuse
- Maintainable code organization for large teams
14. Dependencies & Third-Party Libraries
Key Libraries Selected with Justification
React Query (TanStack Query)
- Justification: Excellent for server state management with built-in caching, invalidation, and background updates. Provides automatic refetching on focus and network reconnect, crucial for cart persistence across tabs.
- Bundle impact: ~15KB gzipped
- Alternative considered: Redux Toolkit - Too verbose for simple server state management, requires more boilerplate for caching and invalidation logic.
Zustand
- Justification: Lightweight state management solution ideal for local cart state (quantity changes, UI states). Zero dependencies and minimal boilerplate while offering better dev experience than Context API for complex local state.
- Bundle impact: ~2KB gzipped
- Alternative considered: Context API - Not scalable for complex UI state changes and lacks built-in persistence.
React Hook Form
- Justification: Production-ready form handling with optimal re-renders, validation, and error management. Perfect for cart checkout forms and address entry.
- Bundle impact: ~8KB gzipped
- Alternative considered: Formik - More verbose and heavier, not as performant for simple forms.
React Virtualized (react-window)
- Justification: Essential for performance when displaying large cart item lists (>50 items). Reduces DOM nodes and improves scroll performance.
- Bundle impact: ~3KB gzipped
- Alternative considered: react-virtual - Slightly more feature-rich but not necessary for cart list virtualization.
date-fns
- Justification: Modular date formatting and manipulation with zero dependencies. Used for cart item expiration tracking and display timestamps.
- Bundle impact: ~2KB gzipped
- Alternative considered: moment.js - Too heavy and has been deprecated in favor of more modular solutions.
lodash
- Justification: Utility functions for cart operations like deep equality checks, array manipulation, and object cloning. Used in cart item deduplication and state normalization.
- Bundle impact: ~12KB gzipped (but only tree-shaken to 3KB actual usage)
- Alternative considered: custom utilities - Would require more maintenance and risk of bugs in edge cases.
axios
- Justification: Robust HTTP client with interceptors, request cancellation, and built-in error handling. Critical for cart API communication.
- Bundle impact: ~10KB gzipped
- Alternative considered: fetch + polyfill - More boilerplate and less feature-rich for complex API interactions.
Alternatives Considered and Rejected
Redux Toolkit
- Rejected because: Overkill for cart state management; requires more boilerplate and configuration than needed. React Query already handles server state well.
MobX
- Rejected because: More complex mental model for developers; less predictable than Zustand for local state management.
Styled Components
- Rejected because: Not chosen due to existing design system constraints and preference for CSS Modules or Tailwind in this codebase.
Jotai
- Rejected because: While powerful, introduces unnecessary complexity for cart state which is better handled by Zustand and React Query combination.
Licensing and Bundle Size Impact Assessment
Bundle Size Budget:
- Total third-party dependencies: ~40KB gzipped (under 50KB target for cart feature)
- Critical path dependencies: ~25KB gzipped (core cart functionality)
License Compliance:
- All libraries are MIT or BSD licensed, suitable for commercial applications
- No proprietary dependencies that would restrict deployment
Tree-shaking Strategy:
- All libraries properly support tree-shaking
- Only necessary modules imported (e.g.,
date-fns/formatvs full library) - No unused components or features included in bundle
Bundle Analysis Strategy:
- Regular bundle analysis using Webpack Bundle Analyzer
- Critical path dependencies prioritized for performance optimization
- Lazy loading strategy implemented where appropriate (e.g., form validation libraries)
15. Open Questions, Risks & Trade-offs
Decisions that need product/engineering alignment
- Cart persistence strategy: Should carts be persisted per user session, browser session, or across devices? This impacts storage strategy (localStorage vs. server-side) and user experience expectations.
- Cart expiration policy: How long should carts persist before expiring? Should they expire after X hours of inactivity, or only when user logs out?
- Cross-device sync: Should cart items be synced across devices? This introduces complexity in state management and synchronization logic.
- Guest vs. authenticated user cart handling: How should we handle guest users who add items to cart and then sign up? Should the cart merge automatically?
Known trade-offs made in this design
- Simplified cart item structure: Prioritized minimal implementation over rich product metadata to reduce complexity and maintainability overhead.
- Single cart per user: Chose a single cart model over multiple cart types (e.g., wishlist, saved for later) to reduce implementation complexity and API surface area.
- Client-side cart persistence: Used localStorage for simplicity but this creates limitations in multi-tab scenarios and device synchronization.
- Basic optimistic updates: Implemented simple optimistic updates for item quantity changes but not for cart removal or item addition to keep implementation focused.
Risks and mitigations
- Race conditions in cart updates: Concurrent cart modifications from multiple tabs or devices could cause data inconsistencies. Mitigation: Implement server-side validation and optimistic update conflict resolution.
- Performance degradation with large carts: Large cart collections could impact UI performance. Mitigation: Implement virtualization for cart item lists and lazy loading of product details.
- Storage limits: localStorage has 5MB limit which could be exceeded with large cart contents. Mitigation: Implement cart size monitoring and warn users before hitting limits.
- Network failure handling: Cart updates may fail due to network issues. Mitigation: Implement retry logic with exponential backoff and local queue for failed operations.
Technical debt acknowledged
- Limited cart validation: Current implementation assumes all cart items are valid and available. Future work should include server-side validation for item availability, pricing, and inventory.
- No cart sharing or gift functionality: The design doesn't support sharing carts or gift wrapping features which may be needed in future iterations.
- Basic error handling: Error states are minimal and don't provide detailed user guidance for common failure scenarios like out-of-stock items.
- No cart analytics integration: The design doesn't include tracking of cart abandonment or conversion rates which would be valuable for business metrics.
- Limited product metadata: Cart items only store basic product information, not full product details needed for rich cart experiences.
- No cart rules engine: The design doesn't support complex cart rules like discounts, free shipping thresholds, or promotional pricing logic.