System Design for Frontend Engineers
System Design for Frontend EngineersFebruary 20, 2024

System Design for Frontend Engineers

Learn system design concepts specifically for frontend engineers. This comprehensive guide covers CDN strategy, caching mechanisms, micro-frontend architecture, performance monitoring, and scalable component design patterns that are essential for building large-scale frontend systems.

Table of Contents

  1. Introduction to Frontend System Design
  2. Content Delivery Networks (CDN)
  3. Caching Strategies
  4. Micro-Frontend Architecture
  5. Performance Monitoring
  6. Scalable Component Design
  7. State Management at Scale
  8. API Design and Integration
  9. Security Considerations
  10. Monitoring and Observability

Introduction to Frontend System Design {#introduction}

Frontend system design involves architecting client-side applications that can handle millions of users, provide excellent performance, and remain maintainable as they scale. Unlike backend system design, frontend design focuses on user experience, performance optimization, and efficient resource utilization.

Key Principles

  • Performance First: Every design decision should consider its impact on user experience
  • Scalability: Architecture should support growing user base and feature complexity
  • Maintainability: Code should be organized for easy updates and debugging
  • Reliability: System should gracefully handle failures and provide fallbacks
  • Security: Protect user data and prevent common frontend vulnerabilities

Common Frontend System Design Challenges

  • Bundle Size: Managing JavaScript bundle size as applications grow
  • Performance: Maintaining fast load times and smooth interactions
  • Caching: Efficient caching strategies for static and dynamic content
  • SEO: Search engine optimization for single-page applications
  • Cross-browser Compatibility: Ensuring consistent experience across browsers
  • Mobile Performance: Optimizing for mobile devices and networks

Content Delivery Networks (CDN) {#cdn}

CDNs are crucial for delivering frontend assets efficiently to users worldwide.

CDN Strategy

// CDN Configuration Example
const CDN_CONFIG = {
  production: {
    staticAssets: 'https://cdn.example.com/static/',
    images: 'https://images.cdn.example.com/',
    videos: 'https://video.cdn.example.com/',
    api: 'https://api.example.com/',
  },
  staging: {
    staticAssets: 'https://staging-cdn.example.com/static/',
    images: 'https://staging-images.cdn.example.com/',
    videos: 'https://staging-video.cdn.example.com/',
    api: 'https://staging-api.example.com/',
  },
  development: {
    staticAssets: '/static/',
    images: '/images/',
    videos: '/videos/',
    api: 'http://localhost:3001/',
  },
};

export const getCDNUrl = (assetType, path) => {
  const env = process.env.NODE_ENV || 'development';
  const baseUrl = CDN_CONFIG[env][assetType];
  return `${baseUrl}${path}`;
};

// Usage
const imageUrl = getCDNUrl('images', 'user-avatar.jpg');
const scriptUrl = getCDNUrl('staticAssets', 'js/main.bundle.js');

Image Optimization Strategy

// Responsive Image Component with CDN
import React from 'react';

const OptimizedImage = ({ 
  src, 
  alt, 
  sizes = '100vw',
  loading = 'lazy',
  ...props 
}) => {
  const generateSrcSet = (baseSrc) => {
    const formats = ['webp', 'jpg'];
    const sizes = [320, 640, 960, 1280, 1920];
    
    return formats.map(format => 
      sizes.map(size => 
        `${getCDNUrl('images', `${baseSrc}?w=${size}&f=${format}`)} ${size}w`
      ).join(', ')
    );
  };

  return (
    <picture>
      <source 
        srcSet={generateSrcSet(src)[0]} 
        type="image/webp" 
        sizes={sizes}
      />
      <img
        src={getCDNUrl('images', src)}
        srcSet={generateSrcSet(src)[1]}
        alt={alt}
        loading={loading}
        sizes={sizes}
        {...props}
      />
    </picture>
  );
};

export default OptimizedImage;

CDN Cache Control

// Cache Control Headers Configuration
const CACHE_STRATEGIES = {
  staticAssets: {
    'Cache-Control': 'public, max-age=31536000, immutable', // 1 year
    'ETag': true,
  },
  dynamicContent: {
    'Cache-Control': 'public, max-age=300, must-revalidate', // 5 minutes
    'ETag': true,
  },
  userSpecific: {
    'Cache-Control': 'private, max-age=0, no-cache',
    'ETag': false,
  },
  api: {
    'Cache-Control': 'public, max-age=60, stale-while-revalidate=300',
    'ETag': true,
  },
};

// Webpack configuration for cache busting
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
    publicPath: getCDNUrl('staticAssets', ''),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          filename: 'vendor.[contenthash].js',
          chunks: 'all',
        },
      },
    },
  },
};

Caching Strategies {#caching}

Effective caching is essential for frontend performance.

Browser Cache Strategy

// Service Worker for Advanced Caching
class CacheManager {
  constructor() {
    this.CACHE_NAME = 'app-cache-v1';
    this.API_CACHE = 'api-cache-v1';
    this.IMAGE_CACHE = 'image-cache-v1';
  }

  async install() {
    const cache = await caches.open(this.CACHE_NAME);
    const staticAssets = [
      '/',
      '/static/css/main.css',
      '/static/js/main.js',
      '/manifest.json',
    ];
    
    return cache.addAll(staticAssets);
  }

  async fetch(request) {
    const url = new URL(request.url);

    // Cache strategy for different resource types
    if (this.isStaticAsset(url)) {
      return this.cacheFirst(request);
    }
    
    if (this.isAPIRequest(url)) {
      return this.networkFirst(request);
    }
    
    if (this.isImageRequest(url)) {
      return this.staleWhileRevalidate(request);
    }

    return fetch(request);
  }

  async cacheFirst(request) {
    const cache = await caches.open(this.CACHE_NAME);
    const cachedResponse = await cache.match(request);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    const networkResponse = await fetch(request);
    cache.put(request, networkResponse.clone());
    return networkResponse;
  }

  async networkFirst(request) {
    const cache = await caches.open(this.API_CACHE);
    
    try {
      const networkResponse = await fetch(request);
      cache.put(request, networkResponse.clone());
      return networkResponse;
    } catch (error) {
      const cachedResponse = await cache.match(request);
      return cachedResponse || new Response('Offline', { status: 503 });
    }
  }

  async staleWhileRevalidate(request) {
    const cache = await caches.open(this.IMAGE_CACHE);
    const cachedResponse = await cache.match(request);
    
    const fetchPromise = fetch(request).then(networkResponse => {
      cache.put(request, networkResponse.clone());
      return networkResponse;
    });
    
    return cachedResponse || fetchPromise;
  }

  isStaticAsset(url) {
    return /\.(js|css|woff2?|png|jpg|svg)$/.test(url.pathname);
  }

  isAPIRequest(url) {
    return url.pathname.startsWith('/api/');
  }

  isImageRequest(url) {
    return /\.(png|jpg|jpeg|webp|gif|svg)$/.test(url.pathname);
  }
}

// Register service worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js');
  });
}

Application-Level Caching

// React Query for Server State Caching
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 10 * 60 * 1000, // 10 minutes
      retry: 3,
      refetchOnWindowFocus: false,
    },
  },
});

// Cache with different strategies
const useCachedData = (key, fetcher, options = {}) => {
  return useQuery(key, fetcher, {
    staleTime: options.staleTime || 5 * 60 * 1000,
    cacheTime: options.cacheTime || 10 * 60 * 1000,
    ...options,
  });
};

// Memory cache for computed values
class MemoryCache {
  constructor(maxSize = 100) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  get(key) {
    if (this.cache.has(key)) {
      const value = this.cache.get(key);
      // Move to end (LRU)
      this.cache.delete(key);
      this.cache.set(key, value);
      return value;
    }
    return null;
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      // Remove oldest entry
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }

  clear() {
    this.cache.clear();
  }
}

const memoCache = new MemoryCache(50);

export const memoize = (fn, keyGenerator = (...args) => JSON.stringify(args)) => {
  return (...args) => {
    const key = keyGenerator(...args);
    const cached = memoCache.get(key);
    
    if (cached !== null) {
      return cached;
    }
    
    const result = fn(...args);
    memoCache.set(key, result);
    return result;
  };
};

Micro-Frontend Architecture {#micro-frontend}

Micro-frontends enable teams to develop and deploy frontend applications independently.

Module Federation Setup

// webpack.config.js for Host Application
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        userManagement: 'userManagement@http://localhost:3001/remoteEntry.js',
        dashboard: 'dashboard@http://localhost:3002/remoteEntry.js',
        analytics: 'analytics@http://localhost:3003/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
        'react-router-dom': { singleton: true },
      },
    }),
  ],
};

// webpack.config.js for Micro-frontend
module.exports = {
  mode: 'development',
  devServer: {
    port: 3001,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'userManagement',
      filename: 'remoteEntry.js',
      exposes: {
        './UserManagement': './src/UserManagement',
        './UserProfile': './src/UserProfile',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

Micro-Frontend Communication

// Event Bus for Cross-Micro-Frontend Communication
class EventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);

    // Return unsubscribe function
    return () => {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    };
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }

  clear() {
    this.events = {};
  }
}

// Global event bus instance
window.eventBus = window.eventBus || new EventBus();

// React Hook for Event Bus
import { useEffect, useCallback } from 'react';

export const useEventBus = (event, handler) => {
  useEffect(() => {
    if (!event || !handler) return;

    const unsubscribe = window.eventBus.subscribe(event, handler);
    return unsubscribe;
  }, [event, handler]);

  const emit = useCallback((eventName, data) => {
    window.eventBus.emit(eventName, data);
  }, []);

  return { emit };
};

// Usage in components
const UserProfile = () => {
  const { emit } = useEventBus('user-updated', (userData) => {
    console.log('User updated:', userData);
  });

  const handleUserUpdate = (user) => {
    emit('user-updated', user);
  };

  return (
    <div>
      {/* Component content */}
    </div>
  );
};

Shared State Management

// Shared Store for Micro-Frontends
class SharedStore {
  constructor() {
    this.store = {};
    this.subscribers = {};
  }

  getState(key) {
    return this.store[key];
  }

  setState(key, value) {
    this.store[key] = value;
    this.notifySubscribers(key, value);
  }

  subscribe(key, callback) {
    if (!this.subscribers[key]) {
      this.subscribers[key] = [];
    }
    this.subscribers[key].push(callback);

    return () => {
      this.subscribers[key] = this.subscribers[key].filter(cb => cb !== callback);
    };
  }

  notifySubscribers(key, value) {
    if (this.subscribers[key]) {
      this.subscribers[key].forEach(callback => callback(value));
    }
  }
}

window.sharedStore = window.sharedStore || new SharedStore();

// React Hook for Shared Store
export const useSharedState = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const stored = window.sharedStore.getState(key);
    return stored !== undefined ? stored : initialValue;
  });

  useEffect(() => {
    const unsubscribe = window.sharedStore.subscribe(key, setValue);
    return unsubscribe;
  }, [key]);

  const setSharedValue = useCallback((newValue) => {
    window.sharedStore.setState(key, newValue);
  }, [key]);

  return [value, setSharedValue];
};

Performance Monitoring {#performance-monitoring}

Comprehensive performance monitoring is crucial for maintaining user experience.

Core Web Vitals Monitoring

// Web Vitals Monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

class PerformanceMonitor {
  constructor(options = {}) {
    this.apiEndpoint = options.apiEndpoint || '/api/metrics';
    this.sampling = options.sampling || 0.1; // 10% sampling
    this.bufferSize = options.bufferSize || 10;
    this.buffer = [];
    
    this.init();
  }

  init() {
    if (Math.random() > this.sampling) return;

    // Core Web Vitals
    getCLS(this.handleMetric.bind(this));
    getFID(this.handleMetric.bind(this));
    getFCP(this.handleMetric.bind(this));
    getLCP(this.handleMetric.bind(this));
    getTTFB(this.handleMetric.bind(this));

    // Custom metrics
    this.monitorReactPerformance();
    this.monitorResourceTiming();
    this.monitorJSErrors();
  }

  handleMetric(metric) {
    const data = {
      name: metric.name,
      value: metric.value,
      rating: metric.rating,
      delta: metric.delta,
      id: metric.id,
      url: window.location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent,
      connection: navigator.connection?.effectiveType,
    };

    this.buffer.push(data);

    if (this.buffer.length >= this.bufferSize) {
      this.flush();
    }
  }

  monitorReactPerformance() {
    // React DevTools Profiler API
    if (window.React && window.React.Profiler) {
      const originalProfiler = window.React.Profiler;
      
      window.React.Profiler = (props) => {
        return originalProfiler({
          ...props,
          onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
            if (props.onRender) {
              props.onRender(id, phase, actualDuration, baseDuration, startTime, commitTime);
            }

            this.handleMetric({
              name: 'react-render',
              value: actualDuration,
              id: id,
              extra: { phase, baseDuration, startTime, commitTime },
            });
          },
        });
      };
    }
  }

  monitorResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'resource') {
          this.handleMetric({
            name: 'resource-timing',
            value: entry.duration,
            id: entry.name,
            extra: {
              size: entry.transferSize,
              type: entry.initiatorType,
              cached: entry.transferSize === 0,
            },
          });
        }
      });
    });

    observer.observe({ entryTypes: ['resource'] });
  }

  monitorJSErrors() {
    window.addEventListener('error', (event) => {
      this.handleMetric({
        name: 'js-error',
        value: 1,
        id: event.filename,
        extra: {
          message: event.message,
          lineno: event.lineno,
          colno: event.colno,
          stack: event.error?.stack,
        },
      });
    });

    window.addEventListener('unhandledrejection', (event) => {
      this.handleMetric({
        name: 'unhandled-promise-rejection',
        value: 1,
        id: 'promise-rejection',
        extra: {
          reason: event.reason,
        },
      });
    });
  }

  flush() {
    if (this.buffer.length === 0) return;

    const data = [...this.buffer];
    this.buffer = [];

    // Send to analytics service
    fetch(this.apiEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    }).catch(console.error);
  }
}

// Initialize performance monitoring
const performanceMonitor = new PerformanceMonitor({
  apiEndpoint: '/api/metrics',
  sampling: process.env.NODE_ENV === 'production' ? 0.1 : 1,
});

React Performance Monitoring

// React Performance Monitor Component
import React, { Profiler, useState, useEffect } from 'react';

const PerformanceProfiler = ({ id, children, onRender }) => {
  const [renderMetrics, setRenderMetrics] = useState([]);

  const handleRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
    const metric = {
      id,
      phase,
      actualDuration,
      baseDuration,
      startTime,
      commitTime,
      timestamp: Date.now(),
    };

    setRenderMetrics(prev => [...prev.slice(-19), metric]);

    if (onRender) {
      onRender(metric);
    }

    // Log slow renders
    if (actualDuration > 16) { // Slower than 60fps
      console.warn(`Slow render detected in ${id}:`, metric);
    }
  };

  return (
    <Profiler id={id} onRender={handleRender}>
      {children}
    </Profiler>
  );
};

// Usage
const App = () => {
  return (
    <PerformanceProfiler id="App">
      <Header />
      <PerformanceProfiler id="MainContent">
        <MainContent />
      </PerformanceProfiler>
      <Footer />
    </PerformanceProfiler>
  );
};

Scalable Component Design {#component-design}

Design components that can scale with your application.

Component Architecture Patterns

// Compound Component Pattern
const Tabs = ({ children, defaultTab = 0 }) => {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <div className="tabs">
      {React.Children.map(children, (child, index) => {
        if (React.isValidElement(child)) {
          return React.cloneElement(child, {
            isActive: index === activeTab,
            onActivate: () => setActiveTab(index),
            index,
          });
        }
        return child;
      })}
    </div>
  );
};

const TabList = ({ children }) => (
  <div className="tab-list" role="tablist">
    {children}
  </div>
);

const Tab = ({ children, isActive, onActivate, index }) => (
  <button
    className={`tab ${isActive ? 'tab--active' : ''}`}
    onClick={onActivate}
    role="tab"
    aria-selected={isActive}
    id={`tab-${index}`}
    aria-controls={`panel-${index}`}
  >
    {children}
  </button>
);

const TabPanels = ({ children }) => (
  <div className="tab-panels">
    {children}
  </div>
);

const TabPanel = ({ children, isActive, index }) => (
  <div
    className={`tab-panel ${isActive ? 'tab-panel--active' : ''}`}
    role="tabpanel"
    id={`panel-${index}`}
    aria-labelledby={`tab-${index}`}
    hidden={!isActive}
  >
    {children}
  </div>
);

// Attach components as properties
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panels = TabPanels;
Tabs.Panel = TabPanel;

// Usage
<Tabs defaultTab={0}>
  <Tabs.List>
    <Tabs.Tab>Tab 1</Tabs.Tab>
    <Tabs.Tab>Tab 2</Tabs.Tab>
    <Tabs.Tab>Tab 3</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panels>
    <Tabs.Panel>Content 1</Tabs.Panel>
    <Tabs.Panel>Content 2</Tabs.Panel>
    <Tabs.Panel>Content 3</Tabs.Panel>
  </Tabs.Panels>
</Tabs>

Design System Architecture

// Design System Token Management
const designTokens = {
  colors: {
    primary: {
      50: '#eff6ff',
      100: '#dbeafe',
      500: '#3b82f6',
      900: '#1e3a8a',
    },
    semantic: {
      success: '#10b981',
      warning: '#f59e0b',
      error: '#ef4444',
      info: '#3b82f6',
    },
  },
  spacing: {
    xs: '0.25rem',
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
    xl: '2rem',
  },
  typography: {
    fontFamily: {
      sans: ['Inter', 'sans-serif'],
      mono: ['Monaco', 'monospace'],
    },
    fontSize: {
      xs: ['0.75rem', '1rem'],
      sm: ['0.875rem', '1.25rem'],
      base: ['1rem', '1.5rem'],
      lg: ['1.125rem', '1.75rem'],
    },
  },
  breakpoints: {
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
  },
};

// Theme Provider
import React, { createContext, useContext } from 'react';

const ThemeContext = createContext(designTokens);

export const ThemeProvider = ({ theme = designTokens, children }) => (
  <ThemeContext.Provider value={theme}>
    {children}
  </ThemeContext.Provider>
);

export const useTheme = () => {
  const theme = useContext(ThemeContext);
  if (!theme) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return theme;
};

// Styled Component Factory
export const styled = (component) => (styles) => {
  return React.forwardRef((props, ref) => {
    const theme = useTheme();
    const computedStyles = typeof styles === 'function' 
      ? styles({ theme, ...props }) 
      : styles;

    return React.createElement(component, {
      ...props,
      ref,
      style: { ...computedStyles, ...props.style },
    });
  });
};

// Usage
const StyledButton = styled('button')(({ theme, variant = 'primary' }) => ({
  padding: `${theme.spacing.sm} ${theme.spacing.md}`,
  backgroundColor: theme.colors.primary[500],
  color: 'white',
  border: 'none',
  borderRadius: '0.375rem',
  fontFamily: theme.typography.fontFamily.sans.join(', '),
  fontSize: theme.typography.fontSize.sm[0],
  lineHeight: theme.typography.fontSize.sm[1],
  cursor: 'pointer',
  transition: 'all 0.2s ease',
  
  '&:hover': {
    backgroundColor: theme.colors.primary[600],
  },
  
  '&:disabled': {
    opacity: 0.5,
    cursor: 'not-allowed',
  },
}));

State Management at Scale {#state-management}

Managing state in large applications requires careful architecture.

Modular State Management

// Redux Toolkit with Feature Slices
import { configureStore, createSlice } from '@reduxjs/toolkit';

// User slice
const userSlice = createSlice({
  name: 'user',
  initialState: {
    profile: null,
    preferences: {},
    loading: false,
    error: null,
  },
  reducers: {
    setUser: (state, action) => {
      state.profile = action.payload;
    },
    updatePreferences: (state, action) => {
      state.preferences = { ...state.preferences, ...action.payload };
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    },
    setError: (state, action) => {
      state.error = action.payload;
    },
  },
});

// Notification slice
const notificationSlice = createSlice({
  name: 'notifications',
  initialState: {
    items: [],
    unreadCount: 0,
  },
  reducers: {
    addNotification: (state, action) => {
      state.items.push(action.payload);
      state.unreadCount += 1;
    },
    removeNotification: (state, action) => {
      state.items = state.items.filter(item => item.id !== action.payload);
    },
    markAsRead: (state, action) => {
      const notification = state.items.find(item => item.id === action.payload);
      if (notification && !notification.read) {
        notification.read = true;
        state.unreadCount = Math.max(0, state.unreadCount - 1);
      }
    },
  },
});

// Store configuration
const store = configureStore({
  reducer: {
    user: userSlice.reducer,
    notifications: notificationSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST'],
      },
    }),
});

// Feature-based hooks
export const useUser = () => {
  const user = useSelector(state => state.user);
  const dispatch = useDispatch();

  return {
    ...user,
    setUser: (userData) => dispatch(userSlice.actions.setUser(userData)),
    updatePreferences: (prefs) => dispatch(userSlice.actions.updatePreferences(prefs)),
    setLoading: (loading) => dispatch(userSlice.actions.setLoading(loading)),
    setError: (error) => dispatch(userSlice.actions.setError(error)),
  };
};

export const useNotifications = () => {
  const notifications = useSelector(state => state.notifications);
  const dispatch = useDispatch();

  return {
    ...notifications,
    addNotification: (notification) => 
      dispatch(notificationSlice.actions.addNotification(notification)),
    removeNotification: (id) => 
      dispatch(notificationSlice.actions.removeNotification(id)),
    markAsRead: (id) => 
      dispatch(notificationSlice.actions.markAsRead(id)),
  };
};

Security Considerations {#security}

Frontend security is crucial for protecting user data and preventing attacks.

Content Security Policy

// CSP Configuration
const CSP_DIRECTIVES = {
  'default-src': ["'self'"],
  'script-src': [
    "'self'",
    "'unsafe-inline'", // Only for development
    'https://cdn.jsdelivr.net',
    'https://unpkg.com',
  ],
  'style-src': [
    "'self'",
    "'unsafe-inline'",
    'https://fonts.googleapis.com',
  ],
  'img-src': [
    "'self'",
    'data:',
    'https:',
  ],
  'font-src': [
    "'self'",
    'https://fonts.gstatic.com',
  ],
  'connect-src': [
    "'self'",
    'https://api.example.com',
    'wss://websocket.example.com',
  ],
  'frame-ancestors': ["'none'"],
  'base-uri': ["'self'"],
  'form-action': ["'self'"],
};

const generateCSP = (directives) => {
  return Object.entries(directives)
    .map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
    .join('; ');
};

// Set CSP header
const cspHeader = generateCSP(CSP_DIRECTIVES);
document.querySelector('meta[http-equiv="Content-Security-Policy"]')
  ?.setAttribute('content', cspHeader);

Input Sanitization

// XSS Prevention
class SecurityUtils {
  static sanitizeHTML(html) {
    const div = document.createElement('div');
    div.textContent = html;
    return div.innerHTML;
  }

  static sanitizeURL(url) {
    try {
      const parsedUrl = new URL(url);
      const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
      
      if (!allowedProtocols.includes(parsedUrl.protocol)) {
        return '#';
      }
      
      return parsedUrl.href;
    } catch {
      return '#';
    }
  }

  static validateInput(input, type) {
    const validators = {
      email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      phone: /^\+?[\d\s\-\(\)]{10,}$/,
      url: /^https?:\/\/.+$/,
      alphanumeric: /^[a-zA-Z0-9]+$/,
    };

    return validators[type]?.test(input) || false;
  }

  static encodeForHTML(str) {
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
  }
}

// Safe component for rendering user content
const SafeHTML = ({ content, allowedTags = [] }) => {
  const sanitized = useMemo(() => {
    // Use a library like DOMPurify for production
    return SecurityUtils.sanitizeHTML(content);
  }, [content]);

  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
};

Monitoring and Observability {#monitoring}

Comprehensive monitoring ensures system reliability and performance.

Error Tracking and Logging

// Error Tracking Service
class ErrorTracker {
  constructor(config) {
    this.apiEndpoint = config.apiEndpoint;
    this.environment = config.environment;
    this.userId = config.userId;
    this.sessionId = this.generateSessionId();
    
    this.setupGlobalErrorHandlers();
  }

  setupGlobalErrorHandlers() {
    window.addEventListener('error', this.handleError.bind(this));
    window.addEventListener('unhandledrejection', this.handlePromiseRejection.bind(this));
  }

  handleError(event) {
    this.track({
      type: 'javascript-error',
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      stack: event.error?.stack,
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: new Date().toISOString(),
    });
  }

  handlePromiseRejection(event) {
    this.track({
      type: 'promise-rejection',
      reason: event.reason,
      url: window.location.href,
      timestamp: new Date().toISOString(),
    });
  }

  track(error) {
    const errorData = {
      ...error,
      environment: this.environment,
      userId: this.userId,
      sessionId: this.sessionId,
      breadcrumbs: this.getBreadcrumbs(),
      context: this.getContext(),
    };

    // Send to error tracking service
    fetch(this.apiEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorData),
    }).catch(console.error);
  }

  getBreadcrumbs() {
    // Return array of user actions leading to error
    return window.breadcrumbs || [];
  }

  getContext() {
    return {
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight,
      },
      connection: navigator.connection?.effectiveType,
      memory: navigator.deviceMemory,
      cores: navigator.hardwareConcurrency,
    };
  }

  generateSessionId() {
    return 'session_' + Math.random().toString(36).substr(2, 9);
  }
}

// Initialize error tracking
const errorTracker = new ErrorTracker({
  apiEndpoint: '/api/errors',
  environment: process.env.NODE_ENV,
  userId: getCurrentUserId(),
});

Performance Metrics Dashboard

// Real-time Performance Dashboard
import React, { useState, useEffect } from 'react';

const PerformanceDashboard = () => {
  const [metrics, setMetrics] = useState({
    lcp: null,
    fid: null,
    cls: null,
    ttfb: null,
    bundleSize: null,
    errorRate: null,
  });

  useEffect(() => {
    const fetchMetrics = async () => {
      try {
        const response = await fetch('/api/metrics/dashboard');
        const data = await response.json();
        setMetrics(data);
      } catch (error) {
        console.error('Failed to fetch metrics:', error);
      }
    };

    fetchMetrics();
    const interval = setInterval(fetchMetrics, 30000); // Update every 30s

    return () => clearInterval(interval);
  }, []);

  const getScoreColor = (value, thresholds) => {
    if (value <= thresholds.good) return '#10b981';
    if (value <= thresholds.fair) return '#f59e0b';
    return '#ef4444';
  };

  return (
    <div className="performance-dashboard">
      <h2>Performance Metrics</h2>
      
      <div className="metrics-grid">
        <MetricCard
          title="Largest Contentful Paint"
          value={metrics.lcp}
          unit="ms"
          thresholds={{ good: 2500, fair: 4000 }}
          description="Time to render the largest content element"
        />
        
        <MetricCard
          title="First Input Delay"
          value={metrics.fid}
          unit="ms"
          thresholds={{ good: 100, fair: 300 }}
          description="Time from first user interaction to browser response"
        />
        
        <MetricCard
          title="Cumulative Layout Shift"
          value={metrics.cls}
          unit=""
          thresholds={{ good: 0.1, fair: 0.25 }}
          description="Amount of unexpected layout shift"
        />
        
        <MetricCard
          title="Time to First Byte"
          value={metrics.ttfb}
          unit="ms"
          thresholds={{ good: 800, fair: 1800 }}
          description="Time from navigation to first byte received"
        />
      </div>
    </div>
  );
};

const MetricCard = ({ title, value, unit, thresholds, description }) => {
  const color = value ? getScoreColor(value, thresholds) : '#6b7280';

  return (
    <div className="metric-card">
      <h3>{title}</h3>
      <div className="metric-value" style={{ color }}>
        {value ? `${value}${unit}` : 'Loading...'}
      </div>
      <p className="metric-description">{description}</p>
    </div>
  );
};

Conclusion

Frontend system design requires a holistic approach that considers performance, scalability, maintainability, and user experience. Key takeaways:

  1. CDN Strategy: Implement efficient content delivery and caching strategies
  2. Performance Monitoring: Continuously monitor Core Web Vitals and user experience metrics
  3. Micro-Frontend Architecture: Consider micro-frontends for large, multi-team applications
  4. Security: Implement robust security measures to protect user data
  5. Observability: Maintain comprehensive monitoring and error tracking

By following these principles and implementing these patterns, you can build frontend systems that scale effectively while maintaining excellent user experience and developer productivity.