⚡ Task 10.2: Performance Optimization - Sub-Second Responsiveness
Optimize frontend performance to ensure instant responsiveness and smooth 60fps animations.
📝 Description
Implement performance optimizations across the frontend to achieve professional-grade responsiveness. This includes code splitting, lazy loading components, memoization, virtualizing long lists, optimizing images, reducing bundle size, and ensuring all interactions feel instant. The goal is butter-smooth performance that makes judges say "wow, this is fast."
🎯 Acceptance Criteria
🛠️ Implementation
Code Splitting and Lazy Loading
Update src/App.js:
import React, { Suspense, lazy } from 'react';
import { WebSocketProvider } from './services/websocket';
import LoadingSpinner from './components/Shared/LoadingSpinner';
import './App.css';
// Lazy load heavy components
const Dashboard = lazy(() => import('./components/Dashboard'));
function App() {
return (
<WebSocketProvider url={process.env.REACT_APP_API_URL?.replace('/api', '')}>
<div className="App">
<header className="app-header">
<h1>🚨 RapidResponse AI</h1>
<p className="subtitle">Emergency Response Intelligence System</p>
<div className="disclaimer">
⚠️ SIMULATION MODE - For Demonstration Only
</div>
</header>
<Suspense fallback={
<div className="app-loading">
<LoadingSpinner size="large" message="Loading Emergency Operations Center..." />
</div>
}>
<Dashboard />
</Suspense>
</div>
</WebSocketProvider>
);
}
export default App;
React.memo for Components
Optimize heavy components with memoization:
Update src/components/Map/MapView.js:
import React, { useEffect, useRef, useState, memo } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import DangerZoneLayer from './DangerZoneLayer';
import EvacuationRoutes from './EvacuationRoutes';
import Markers from './Markers';
import './MapView.css';
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN;
const MapView = memo(function MapView({ disaster, plan }) {
// ... existing implementation ...
return (
<div className="map-view">
{/* ... existing JSX ... */}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison function
return (
prevProps.disaster?.disaster_id === nextProps.disaster?.disaster_id &&
prevProps.plan?.generated_at === nextProps.plan?.generated_at
);
});
export default MapView;
useMemo for Expensive Calculations
Update src/components/EmergencyPlan/PopulationImpact.js:
import React, { useMemo } from 'react';
import './PopulationImpact.css';
function PopulationImpact({ populationImpact, affectedAreas }) {
// Memoize expensive calculations
const languageData = useMemo(() => {
if (!populationImpact?.languages) return [];
const totalLangSpeakers = Object.values(populationImpact.languages)
.reduce((sum, count) => sum + count, 0);
return Object.entries(populationImpact.languages)
.map(([lang, count]) => ({
language: lang,
count: count,
percentage: totalLangSpeakers > 0
? (count / totalLangSpeakers * 100).toFixed(1)
: 0,
}))
.sort((a, b) => b.count - a.count);
}, [populationImpact?.languages]);
const vulnerablePercentage = useMemo(() => {
if (!populationImpact) return 0;
const { elderly = 0, children = 0, disabled = 0 } = populationImpact.vulnerable_population || {};
const totalVulnerable = elderly + children + disabled;
const total = populationImpact.total_affected || 0;
return total > 0 ? ((totalVulnerable / total) * 100).toFixed(1) : 0;
}, [populationImpact]);
// ... rest of component
}
export default React.memo(PopulationImpact);
useCallback for Event Handlers
Update src/hooks/useDisaster.js:
import { useState, useCallback, useEffect, useMemo } from 'react';
import { disasterAPI } from '../services/api';
import useWebSocket from './useWebSocket';
function useDisaster() {
const [disaster, setDisaster] = useState(null);
const [plan, setPlan] = useState(null);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [statusMessage, setStatusMessage] = useState('');
const { connected, on, subscribeToDisaster } = useWebSocket();
// Memoize status values
const status = useMemo(() => ({
isProcessing: loading,
hasActivePlan: !!plan,
hasError: !!error,
}), [loading, plan, error]);
const triggerDisaster = useCallback(async (disasterData) => {
// ... existing implementation (already uses useCallback)
}, [subscribeToDisaster]);
const clearDisaster = useCallback(() => {
// ... existing implementation (already uses useCallback)
}, []);
return {
disaster,
plan,
loading,
progress,
error,
statusMessage,
triggerDisaster,
clearDisaster,
...status,
};
}
export default useDisaster;
Image Optimization
Create utility for lazy image loading:
src/utils/lazyImage.js:
import { useEffect, useRef, useState } from 'react';
export function useLazyImage(src) {
const [imageSrc, setImageSrc] = useState(null);
const [imageRef, setImageRef] = useState(null);
useEffect(() => {
let observer;
let didCancel = false;
if (imageRef && imageSrc !== src) {
if (IntersectionObserver) {
observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (
!didCancel &&
(entry.intersectionRatio > 0 || entry.isIntersecting)
) {
setImageSrc(src);
observer.unobserve(imageRef);
}
});
},
{
threshold: 0.01,
rootMargin: '75%',
}
);
observer.observe(imageRef);
} else {
// Fallback for browsers without IntersectionObserver
setImageSrc(src);
}
}
return () => {
didCancel = true;
if (observer && observer.unobserve && imageRef) {
observer.unobserve(imageRef);
}
};
}, [src, imageSrc, imageRef]);
return [setImageRef, imageSrc];
}
Bundle Size Optimization
Update package.json with tree-shaking optimized imports:
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"build": "GENERATE_SOURCEMAP=false react-scripts build"
},
"devDependencies": {
"source-map-explorer": "^2.5.3"
}
}
Analyze bundle:
npm run build
npm run analyze
Debounce Expensive Operations
Create src/utils/debounce.js:
export function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Usage example for search or filter operations
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
Virtual Scrolling for Long Lists
If you have long lists (like many facilities), use virtual scrolling:
Example usage in src/components/Map/Markers.js:
import { FixedSizeList } from 'react-window';
// For rendering many markers in a list view (if you add this feature)
function MarkersList({ markers }) {
const Row = ({ index, style }) => (
<div style={style} className="marker-row">
{markers[index].name}
</div>
);
return (
<FixedSizeList
height={400}
itemCount={markers.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
Optimize Re-renders with React DevTools
Add React DevTools Profiler in development:
Update src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
if (process.env.NODE_ENV === 'development') {
// Enable React DevTools profiler
root.render(
<React.StrictMode>
<React.Profiler id="App" onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}>
<App />
</React.Profiler>
</React.StrictMode>
);
} else {
root.render(<App />);
}
Service Worker for Caching (Optional)
Enable service worker for faster repeat loads:
Update src/index.js:
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
// Register service worker
serviceWorkerRegistration.register({
onUpdate: registration => {
console.log('New version available! Please refresh.');
},
onSuccess: registration => {
console.log('Content cached for offline use.');
},
});
Create src/serviceWorkerRegistration.js:
// Standard service worker registration
// Copy from Create React App template or implement basic caching
export function register(config) {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
navigator.serviceWorker.register(swUrl)
.then(registration => {
if (config && config.onSuccess) {
config.onSuccess(registration);
}
})
.catch(error => {
console.error('Service worker registration failed:', error);
});
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
🧪 Performance Testing
Run Lighthouse Audit
# Build production version
npm run build
# Serve it
npx serve -s build
# Open Chrome DevTools > Lighthouse
# Run audit for Performance, Accessibility, Best Practices, SEO
# Target: All scores > 90
Check Bundle Size
npm run build
# Check sizes
ls -lh build/static/js/*.js
ls -lh build/static/css/*.css
# Should see:
# - Main JS: < 200KB gzipped
# - Vendor JS: < 300KB gzipped
# - CSS: < 50KB gzipped
Test Memory Leaks
// In DevTools Console, run this test:
let memoryBefore = performance.memory.usedJSHeapSize;
console.log('Memory before:', memoryBefore);
// Trigger disaster 5 times, wait for completion each time
// Then run:
let memoryAfter = performance.memory.usedJSHeapSize;
console.log('Memory after:', memoryAfter);
console.log('Increase:', ((memoryAfter - memoryBefore) / 1024 / 1024).toFixed(2), 'MB');
// Memory increase should be < 10MB
Test Frame Rate
// Add FPS counter (development only)
let lastTime = performance.now();
let frames = 0;
function measureFPS() {
frames++;
const now = performance.now();
if (now >= lastTime + 1000) {
const fps = Math.round((frames * 1000) / (now - lastTime));
console.log('FPS:', fps);
frames = 0;
lastTime = now;
}
requestAnimationFrame(measureFPS);
}
measureFPS();
// Should consistently show 60 FPS during animations
📋 Performance Checklist
⏱️ Estimated Time
75 minutes
🔗 Related Documentation
React Performance: https://react.dev/learn/render-and-commit
Web Vitals: https://web.dev/vitals/
⚡ Task 10.2: Performance Optimization - Sub-Second Responsiveness
Optimize frontend performance to ensure instant responsiveness and smooth 60fps animations.
📝 Description
Implement performance optimizations across the frontend to achieve professional-grade responsiveness. This includes code splitting, lazy loading components, memoization, virtualizing long lists, optimizing images, reducing bundle size, and ensuring all interactions feel instant. The goal is butter-smooth performance that makes judges say "wow, this is fast."
🎯 Acceptance Criteria
🛠️ Implementation
Code Splitting and Lazy Loading
Update
src/App.js:React.memo for Components
Optimize heavy components with memoization:
Update
src/components/Map/MapView.js:useMemo for Expensive Calculations
Update
src/components/EmergencyPlan/PopulationImpact.js:useCallback for Event Handlers
Update
src/hooks/useDisaster.js:Image Optimization
Create utility for lazy image loading:
src/utils/lazyImage.js:Bundle Size Optimization
Update
package.jsonwith tree-shaking optimized imports:{ "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", "build": "GENERATE_SOURCEMAP=false react-scripts build" }, "devDependencies": { "source-map-explorer": "^2.5.3" } }Analyze bundle:
Debounce Expensive Operations
Create
src/utils/debounce.js:Virtual Scrolling for Long Lists
If you have long lists (like many facilities), use virtual scrolling:
Example usage in
src/components/Map/Markers.js:Optimize Re-renders with React DevTools
Add React DevTools Profiler in development:
Update
src/index.js:Service Worker for Caching (Optional)
Enable service worker for faster repeat loads:
Update
src/index.js:Create
src/serviceWorkerRegistration.js:🧪 Performance Testing
Run Lighthouse Audit
Check Bundle Size
Test Memory Leaks
Test Frame Rate
📋 Performance Checklist
⏱️ Estimated Time
75 minutes
🔗 Related Documentation
React Performance: https://react.dev/learn/render-and-commit
Web Vitals: https://web.dev/vitals/