# Dashboard Performance Optimization ## Table of Contents - [Loading Strategies](#loading-strategies) - [Rendering Optimization](#rendering-optimization) - [Data Management](#data-management) - [Memory Management](#memory-management) - [Network Optimization](#network-optimization) - [Monitoring & Profiling](#monitoring--profiling) ## Loading Strategies ### Progressive Widget Loading ```tsx function ProgressiveDashboard({ widgets }) { const [loadPhase, setLoadPhase] = useState(0); const [loadedWidgets, setLoadedWidgets] = useState>(new Set()); useEffect(() => { const loadingStrategy = [ // Phase 1: Critical above-the-fold widgets widgets.filter(w => w.priority === 'critical'), // Phase 2: Visible widgets widgets.filter(w => w.priority === 'high'), // Phase 3: Below-the-fold widgets widgets.filter(w => w.priority === 'normal'), // Phase 4: Optional widgets widgets.filter(w => w.priority === 'low') ]; loadingStrategy.forEach((phase, index) => { setTimeout(() => { setLoadPhase(index + 1); phase.forEach(widget => { setLoadedWidgets(prev => new Set(prev).add(widget.id)); }); }, index * 200); // Stagger loading }); }, [widgets]); return (
{widgets.map(widget => (
{loadedWidgets.has(widget.id) ? ( ) : ( )}
))}
); } ``` ### Intersection Observer Loading ```tsx function LazyWidget({ widget }) { const ref = useRef(null); const [isVisible, setIsVisible] = useState(false); const [hasLoaded, setHasLoaded] = useState(false); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true); if (!hasLoaded) { setHasLoaded(true); } } else { setIsVisible(false); } }, { threshold: 0.1, rootMargin: '50px' // Start loading 50px before visible } ); if (ref.current) { observer.observe(ref.current); } return () => { if (ref.current) { observer.unobserve(ref.current); } }; }, [hasLoaded]); return (
{hasLoaded ? ( ) : ( )}
); } ``` ### Priority-Based Loading Queue ```tsx class WidgetLoader { private queue: PriorityQueue; private loading = new Map>(); private maxConcurrent = 4; private currentLoading = 0; constructor() { this.queue = new PriorityQueue((a, b) => b.priority - a.priority); } async loadWidget(widget: Widget): Promise { // Check if already loading if (this.loading.has(widget.id)) { return this.loading.get(widget.id); } // Add to queue this.queue.enqueue(widget); return this.processQueue(); } private async processQueue() { while (this.currentLoading < this.maxConcurrent && !this.queue.isEmpty()) { const widget = this.queue.dequeue(); if (!widget) break; this.currentLoading++; const loadPromise = this.fetchWidgetData(widget) .finally(() => { this.currentLoading--; this.loading.delete(widget.id); this.processQueue(); // Process next in queue }); this.loading.set(widget.id, loadPromise); } } private async fetchWidgetData(widget: Widget) { const response = await fetch(`/api/widget/${widget.id}/data`); return response.json(); } } // Usage const widgetLoader = new WidgetLoader(); function PriorityLoadedWidget({ widget }) { const [data, setData] = useState(null); useEffect(() => { widgetLoader.loadWidget(widget).then(setData); }, [widget]); return data ? : ; } ``` ## Rendering Optimization ### React.memo for Widget Optimization ```tsx const OptimizedWidget = React.memo( ({ id, data, filters }) => { console.log(`Rendering widget ${id}`); return (
); }, (prevProps, nextProps) => { // Custom comparison function // Only re-render if data actually changed return ( prevProps.id === nextProps.id && JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data) && JSON.stringify(prevProps.filters) === JSON.stringify(nextProps.filters) ); } ); ``` ### Virtualization for Large Widget Lists ```tsx import { VariableSizeGrid } from 'react-window'; function VirtualizedDashboard({ widgets, columns = 4 }) { const getItemSize = (index: number) => { const widget = widgets[index]; return widget.height || 300; // Default height }; const Cell = ({ columnIndex, rowIndex, style }) => { const index = rowIndex * columns + columnIndex; if (index >= widgets.length) return null; return (
); }; return ( 300} // Fixed column width height={window.innerHeight} rowCount={Math.ceil(widgets.length / columns)} rowHeight={getItemSize} width={window.innerWidth} overscanRowCount={2} // Render 2 extra rows for smooth scrolling > {Cell} ); } ``` ### Batched State Updates ```tsx function useBatchedUpdates() { const [updates, setUpdates] = useState([]); const pendingUpdates = useRef([]); const rafId = useRef(); const scheduleUpdate = useCallback((update: Update) => { pendingUpdates.current.push(update); // Cancel previous frame if (rafId.current) { cancelAnimationFrame(rafId.current); } // Schedule batch update rafId.current = requestAnimationFrame(() => { setUpdates(current => [...current, ...pendingUpdates.current]); pendingUpdates.current = []; }); }, []); useEffect(() => { return () => { if (rafId.current) { cancelAnimationFrame(rafId.current); } }; }, []); return { updates, scheduleUpdate }; } ``` ## Data Management ### Efficient Data Structures ```tsx class DashboardDataStore { private widgetData = new Map(); private subscriptions = new Map>(); private cache = new LRUCache(100); // Max 100 entries setWidgetData(widgetId: string, data: any) { // Store in map this.widgetData.set(widgetId, data); // Update cache this.cache.set(widgetId, { data, timestamp: Date.now() }); // Notify subscribers this.notifySubscribers(widgetId, data); } getWidgetData(widgetId: string): any { // Try cache first const cached = this.cache.get(widgetId); if (cached && Date.now() - cached.timestamp < 60000) { return cached.data; } // Return from store return this.widgetData.get(widgetId); } subscribe(widgetId: string, callback: Function) { if (!this.subscriptions.has(widgetId)) { this.subscriptions.set(widgetId, new Set()); } this.subscriptions.get(widgetId)!.add(callback); // Return unsubscribe function return () => { const subs = this.subscriptions.get(widgetId); if (subs) { subs.delete(callback); } }; } private notifySubscribers(widgetId: string, data: any) { const subs = this.subscriptions.get(widgetId); if (subs) { subs.forEach(callback => callback(data)); } } clearCache() { this.cache.clear(); } clearWidget(widgetId: string) { this.widgetData.delete(widgetId); this.cache.delete(widgetId); this.subscriptions.delete(widgetId); } } ``` ### Data Aggregation and Transformation ```tsx function useAggregatedData(widgets: Widget[]) { const [aggregated, setAggregated] = useState({}); useEffect(() => { const worker = new Worker('/workers/data-aggregator.js'); worker.postMessage({ type: 'AGGREGATE', widgets: widgets.map(w => ({ id: w.id, type: w.type })) }); worker.onmessage = (event) => { if (event.data.type === 'AGGREGATED') { setAggregated(event.data.result); } }; return () => worker.terminate(); }, [widgets]); return aggregated; } // Web Worker: data-aggregator.js self.onmessage = function(event) { if (event.data.type === 'AGGREGATE') { const result = {}; event.data.widgets.forEach(widget => { // Perform heavy computations result[widget.id] = performAggregation(widget); }); self.postMessage({ type: 'AGGREGATED', result }); } }; ``` ## Memory Management ### Widget Cleanup ```tsx function Widget({ id, data }) { useEffect(() => { // Setup const charts = initializeCharts(data); const subscriptions = setupSubscriptions(id); // Cleanup return () => { // Dispose of chart instances charts.forEach(chart => chart.destroy()); // Cancel subscriptions subscriptions.forEach(sub => sub.unsubscribe()); // Clear timers clearInterval(refreshTimer); // Clear cached data widgetCache.delete(id); }; }, [id]); return
...
; } ``` ### Memory Leak Prevention ```tsx function useSafeAsync() { const mounted = useRef(true); const abortControllers = useRef>(new Set()); useEffect(() => { return () => { mounted.current = false; // Abort all pending requests abortControllers.current.forEach(controller => controller.abort()); }; }, []); const safeAsync = useCallback(async (asyncFunc: () => Promise) => { const controller = new AbortController(); abortControllers.current.add(controller); try { const result = await asyncFunc(); if (mounted.current) { return result; } } catch (error) { if (error.name !== 'AbortError') { throw error; } } finally { abortControllers.current.delete(controller); } }, []); return safeAsync; } ``` ### Resource Pooling ```tsx class ChartPool { private available: Chart[] = []; private inUse = new Map(); private maxSize = 20; acquire(widgetId: string, config: ChartConfig): Chart { let chart = this.available.pop(); if (!chart) { chart = new Chart(config); } else { chart.update(config); } this.inUse.set(widgetId, chart); return chart; } release(widgetId: string) { const chart = this.inUse.get(widgetId); if (chart) { this.inUse.delete(widgetId); if (this.available.length < this.maxSize) { chart.clear(); this.available.push(chart); } else { chart.destroy(); } } } clear() { this.inUse.forEach(chart => chart.destroy()); this.available.forEach(chart => chart.destroy()); this.inUse.clear(); this.available = []; } } const chartPool = new ChartPool(); function PooledChartWidget({ id, config }) { useEffect(() => { const chart = chartPool.acquire(id, config); return () => { chartPool.release(id); }; }, [id, config]); return
; } ``` ## Network Optimization ### Request Batching ```tsx class RequestBatcher { private batch: Map> = new Map(); private timer: NodeJS.Timeout | null = null; private delay = 50; // Batch window in ms async fetch(endpoint: string, params: any): Promise { const key = `${endpoint}:${JSON.stringify(params)}`; // Check if request already in batch if (this.batch.has(key)) { return this.batch.get(key); } // Create promise for this request const promise = new Promise((resolve, reject) => { // Add to batch this.addToBatch(key, { endpoint, params, resolve, reject }); }); this.batch.set(key, promise); return promise; } private addToBatch(key: string, request: BatchRequest) { if (!this.timer) { this.timer = setTimeout(() => this.executeBatch(), this.delay); } } private async executeBatch() { const requests = Array.from(this.batch.entries()); this.batch.clear(); this.timer = null; try { // Send all requests in single call const response = await fetch('/api/batch', { method: 'POST', body: JSON.stringify(requests.map(([key, _]) => key)) }); const results = await response.json(); // Resolve individual promises requests.forEach(([key, promise], index) => { promise.resolve(results[index]); }); } catch (error) { requests.forEach(([key, promise]) => { promise.reject(error); }); } } } ``` ### Response Compression ```tsx function useCompressedData(endpoint: string) { const [data, setData] = useState(null); useEffect(() => { fetch(endpoint, { headers: { 'Accept-Encoding': 'gzip, deflate, br' } }) .then(response => { // Check if response is compressed const encoding = response.headers.get('content-encoding'); console.log(`Response compressed with: ${encoding}`); return response.json(); }) .then(setData); }, [endpoint]); return data; } ``` ### Prefetching Strategies ```tsx function usePrefetch() { const prefetchCache = useRef(new Map()); const prefetch = useCallback(async (widgets: Widget[]) => { // Identify widgets likely to be needed const toPrefetch = widgets.filter(w => w.priority === 'high' && !prefetchCache.current.has(w.id) ); // Prefetch in background toPrefetch.forEach(async widget => { const data = await fetch(`/api/widget/${widget.id}/data`, { priority: 'low' // Low priority fetch }); prefetchCache.current.set(widget.id, await data.json()); }); }, []); const getPrefetched = useCallback((widgetId: string) => { const data = prefetchCache.current.get(widgetId); if (data) { prefetchCache.current.delete(widgetId); // Use once } return data; }, []); return { prefetch, getPrefetched }; } ``` ## Monitoring & Profiling ### Performance Metrics Collection ```tsx class DashboardMetrics { private metrics = { widgetLoadTimes: new Map(), renderTimes: new Map(), dataFetchTimes: new Map(), errors: [] }; startTimer(category: string, id: string) { const key = `${category}:${id}`; performance.mark(`${key}:start`); } endTimer(category: string, id: string) { const key = `${category}:${id}`; performance.mark(`${key}:end`); performance.measure( key, `${key}:start`, `${key}:end` ); const measure = performance.getEntriesByName(key)[0]; this.metrics[category].set(id, measure.duration); } getReport() { const avgLoadTime = this.calculateAverage(this.metrics.widgetLoadTimes); const avgRenderTime = this.calculateAverage(this.metrics.renderTimes); const avgFetchTime = this.calculateAverage(this.metrics.dataFetchTimes); return { averageLoadTime: avgLoadTime, averageRenderTime: avgRenderTime, averageFetchTime: avgFetchTime, totalErrors: this.metrics.errors.length, slowestWidget: this.findSlowest(this.metrics.widgetLoadTimes), performance: this.calculatePerformanceScore() }; } private calculateAverage(map: Map): number { const values = Array.from(map.values()); return values.reduce((a, b) => a + b, 0) / values.length; } private findSlowest(map: Map): string { let slowest = ''; let maxTime = 0; map.forEach((time, id) => { if (time > maxTime) { maxTime = time; slowest = id; } }); return slowest; } private calculatePerformanceScore(): number { const avgLoad = this.calculateAverage(this.metrics.widgetLoadTimes); if (avgLoad < 100) return 100; if (avgLoad < 300) return 90; if (avgLoad < 500) return 70; if (avgLoad < 1000) return 50; return 30; } } // Usage const metrics = new DashboardMetrics(); function MonitoredWidget({ id, ...props }) { useEffect(() => { metrics.startTimer('widgetLoadTimes', id); return () => { metrics.endTimer('widgetLoadTimes', id); }; }, [id]); return ; } ``` ### Real User Monitoring (RUM) ```tsx function useRUM() { useEffect(() => { // Core Web Vitals if ('PerformanceObserver' in window) { // Largest Contentful Paint const lcpObserver = new PerformanceObserver((entries) => { const lastEntry = entries.getEntries().pop(); console.log('LCP:', lastEntry?.startTime); // Send to analytics analytics.track('dashboard_lcp', { value: lastEntry?.startTime, url: window.location.href }); }); lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); // First Input Delay const fidObserver = new PerformanceObserver((entries) => { entries.getEntries().forEach(entry => { console.log('FID:', entry.processingStart - entry.startTime); }); }); fidObserver.observe({ entryTypes: ['first-input'] }); // Cumulative Layout Shift let clsValue = 0; const clsObserver = new PerformanceObserver((entries) => { entries.getEntries().forEach(entry => { if (!entry.hadRecentInput) { clsValue += entry.value; console.log('CLS:', clsValue); } }); }); clsObserver.observe({ entryTypes: ['layout-shift'] }); return () => { lcpObserver.disconnect(); fidObserver.disconnect(); clsObserver.disconnect(); }; } }, []); } ```