React Performance Monitoring and Optimization
1. React Profiler API and Performance Metrics
| Feature | API | Metrics Captured | Use Case |
|---|---|---|---|
| Profiler Component | <Profiler id="..." onRender={...}> |
Render time, phase, actual duration | Component-level performance tracking |
| onRender Callback | onRender(id, phase, actualDuration, ...) |
All render metrics per component tree | Collect performance data programmatically |
| Interaction Tracing | DevTools Profiler with interactions | User interactions causing renders | Debug performance bottlenecks |
| Commit Phase Timing | baseDuration vs actualDuration |
Optimization impact measurement | Measure memo/callback effectiveness |
| Custom Metrics | Performance API integration | Custom timing, user timing marks | Business-specific metrics |
| Production Profiling | React with profiling build | Real user performance data | Production performance monitoring |
Example: React Profiler API usage
import { Profiler, ProfilerOnRenderCallback } from 'react';
// Profiler callback
const onRenderCallback: ProfilerOnRenderCallback = (
id, // Profiler id
phase, // "mount" or "update"
actualDuration, // Time spent rendering
baseDuration, // Estimated time without memoization
startTime, // When React began rendering
commitTime, // When React committed the update
interactions // Set of interactions for this update
) => {
console.log(`Profiler [${id}] - ${phase}`);
console.log(`Actual duration: ${actualDuration}ms`);
console.log(`Base duration: ${baseDuration}ms`);
console.log(`Time saved: ${baseDuration - actualDuration}ms`);
// Send to analytics
if (actualDuration > 16) { // More than one frame
sendToAnalytics({
component: id,
phase,
duration: actualDuration,
timestamp: commitTime,
});
}
};
// Wrap component tree with Profiler
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Header />
<Profiler id="Navigation" onRender={onRenderCallback}>
<Navigation />
</Profiler>
<Profiler id="Content" onRender={onRenderCallback}>
<MainContent />
</Profiler>
<Footer />
</Profiler>
);
}
// Custom performance tracking hook
function usePerformanceTracking(componentName: string) {
useEffect(() => {
const startMark = `${componentName}-start`;
const endMark = `${componentName}-end`;
const measureName = `${componentName}-render`;
performance.mark(startMark);
return () => {
performance.mark(endMark);
performance.measure(measureName, startMark, endMark);
const measure = performance.getEntriesByName(measureName)[0];
console.log(`${componentName} render time: ${measure.duration}ms`);
// Clean up
performance.clearMarks(startMark);
performance.clearMarks(endMark);
performance.clearMeasures(measureName);
};
});
}
// Usage in component
function ExpensiveComponent() {
usePerformanceTracking('ExpensiveComponent');
// Component logic
return <div>...</div>;
}
Example: Production performance monitoring
// Performance monitoring service
class PerformanceMonitor {
private metrics: Map<string, number[]> = new Map();
recordMetric(name: string, duration: number) {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(duration);
}
getStats(name: string) {
const durations = this.metrics.get(name) || [];
if (durations.length === 0) return null;
const sorted = [...durations].sort((a, b) => a - b);
return {
count: durations.length,
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
median: sorted[Math.floor(sorted.length / 2)],
p95: sorted[Math.floor(sorted.length * 0.95)],
p99: sorted[Math.floor(sorted.length * 0.99)],
min: sorted[0],
max: sorted[sorted.length - 1],
};
}
flush() {
const stats: Record<string, any> = {};
this.metrics.forEach((_, name) => {
stats[name] = this.getStats(name);
});
// Send to backend
fetch('/api/performance', {
method: 'POST',
body: JSON.stringify(stats),
});
this.metrics.clear();
}
}
const monitor = new PerformanceMonitor();
// Global profiler callback
const globalProfilerCallback: ProfilerOnRenderCallback = (
id,
phase,
actualDuration
) => {
monitor.recordMetric(`${id}-${phase}`, actualDuration);
};
// Flush metrics periodically
setInterval(() => {
monitor.flush();
}, 60000); // Every minute
// Web Vitals integration
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics({ name, delta, id }: any) {
fetch('/api/vitals', {
method: 'POST',
body: JSON.stringify({ name, value: delta, id }),
});
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
2. Bundle Analysis and Code Splitting Strategies
| Strategy | Implementation | Bundle Impact | Best For |
|---|---|---|---|
| Route-based Splitting | React.lazy(() => import('./Route')) |
Split by page/route | Multi-page applications |
| Component-based Splitting | Lazy load heavy components | Split by feature/component | Large components, modals, charts |
| Library Splitting | Dynamic imports for large libs | Vendor bundle optimization | Heavy libraries (PDF, charts, maps) |
| Webpack Bundle Analyzer | webpack-bundle-analyzer |
Visualize bundle composition | Identify optimization opportunities |
| Tree Shaking | ES6 modules, side-effect-free | Remove unused exports | Reduce bundle size automatically |
| Prefetching | import(/* webpackPrefetch: true */) |
Load during idle time | Improve perceived performance |
Example: Advanced code splitting patterns
// Route-based code splitting
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// Component-based splitting with prefetch
function ProductList() {
const [showModal, setShowModal] = useState(false);
// Lazy load modal component
const ProductModal = lazy(() => import('./ProductModal'));
// Prefetch on hover
const handleMouseEnter = () => {
import('./ProductModal'); // Prefetch
};
return (
<div>
<button
onClick={() => setShowModal(true)}
onMouseEnter={handleMouseEnter}
>
View Details
</button>
{showModal && (
<Suspense fallback={<Spinner />}>
<ProductModal onClose={() => setShowModal(false)} />
</Suspense>
)}
</div>
);
}
// Library splitting - heavy chart library
const Chart = lazy(() => import('recharts').then(module => ({
default: module.LineChart
})));
function Analytics() {
return (
<Suspense fallback={<ChartSkeleton />}>
<Chart data={data} />
</Suspense>
);
}
// Dynamic import with error handling
function DynamicComponent() {
const [Component, setComponent] = useState<React.ComponentType | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
import('./HeavyComponent')
.then(module => setComponent(() => module.default))
.catch(err => setError(err));
}, []);
if (error) return <ErrorMessage error={error} />;
if (!Component) return <Loading />;
return <Component />;
}
// Webpack magic comments for optimization
const AdminPanel = lazy(() =>
import(
/* webpackChunkName: "admin" */
/* webpackPrefetch: true */
'./pages/AdminPanel'
)
);
const ReportGenerator = lazy(() =>
import(
/* webpackChunkName: "reports" */
/* webpackPreload: true */
'./components/ReportGenerator'
)
);
Example: Bundle analysis and optimization
// package.json scripts for bundle analysis
{
"scripts": {
"analyze": "webpack-bundle-analyzer build/bundle-stats.json",
"build:analyze": "npm run build -- --stats && npm run analyze"
}
}
// Webpack configuration for bundle optimization
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vendor bundle
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
// Common components
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
// React core
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20,
},
},
},
runtimeChunk: 'single', // Extract runtime into separate chunk
},
};
// Analyze imports and suggest optimizations
import { lazy } from 'react';
// ❌ Bad: Import entire library
import _ from 'lodash';
import moment from 'moment';
// ✅ Good: Import only what you need
import debounce from 'lodash/debounce';
import dayjs from 'dayjs'; // Lighter alternative
// ❌ Bad: Import all icons
import * as Icons from 'react-icons/fa';
// ✅ Good: Import specific icons
import { FaUser, FaHome } from 'react-icons/fa';
// Tree shaking configuration in package.json
{
"sideEffects": [
"*.css",
"*.scss"
]
}
// Check bundle size with size-limit
// size-limit.config.js
module.exports = [
{
name: 'Main bundle',
path: 'build/static/js/*.js',
limit: '200 KB',
},
{
name: 'Vendor bundle',
path: 'build/static/js/vendors.*.js',
limit: '150 KB',
},
];
3. Memory Leak Detection and Prevention
| Leak Source | Detection Method | Prevention | Tools |
|---|---|---|---|
| Event Listeners | Memory snapshots, heap profiling | Remove in cleanup function | Chrome DevTools Memory Profiler |
| Timers/Intervals | Check running timers count | Clear in useEffect cleanup | React DevTools Profiler |
| Subscriptions | Monitor active subscriptions | Unsubscribe in cleanup | Custom logging |
| DOM References | Detached DOM tree analysis | Clear refs, remove listeners | Chrome Memory Profiler |
| State Updates | Warning: setState on unmounted | Cancel async operations | React strict mode warnings |
| Closures | Heap snapshot comparison | Avoid large closure scopes | Memory timeline recording |
Example: Memory leak prevention patterns
// ✅ Proper event listener cleanup
function WindowResizeComponent() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
// Cleanup: Remove listener
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Width: {width}</div>;
}
// ✅ Timer cleanup
function CountdownTimer() {
const [count, setCount] = useState(60);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(c => c - 1);
}, 1000);
// Cleanup: Clear interval
return () => {
clearInterval(intervalId);
};
}, []);
return <div>{count}s remaining</div>;
}
// ✅ Abort controller for fetch requests
function DataFetcher({ id }: { id: string }) {
const [data, setData] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
try {
const response = await fetch(`/api/data/${id}`, {
signal: abortController.signal,
});
const json = await response.json();
setData(json);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
}
}
fetchData();
// Cleanup: Abort pending request
return () => {
abortController.abort();
};
}, [id]);
return <div>{JSON.stringify(data)}</div>;
}
// ✅ WebSocket cleanup
function WebSocketComponent() {
const [messages, setMessages] = useState<string[]>([]);
useEffect(() => {
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};
// Cleanup: Close connection
return () => {
ws.close();
};
}, []);
return <ul>{messages.map((msg, i) => <li key={i}>{msg}</li>)}</ul>;
}
// ✅ Observable/RxJS subscription cleanup
import { interval } from 'rxjs';
function ObservableComponent() {
const [tick, setTick] = useState(0);
useEffect(() => {
const subscription = interval(1000).subscribe(val => {
setTick(val);
});
// Cleanup: Unsubscribe
return () => {
subscription.unsubscribe();
};
}, []);
return <div>Tick: {tick}</div>;
}
Example: Memory leak detection utilities
// Custom hook to detect memory leaks
function useMemoryLeakDetector(componentName: string) {
useEffect(() => {
const mountTime = Date.now();
console.log(`[${componentName}] Mounted`);
return () => {
const unmountTime = Date.now();
const lifetime = unmountTime - mountTime;
console.log(`[${componentName}] Unmounted after ${lifetime}ms`);
// Check for detached listeners
if (process.env.NODE_ENV === 'development') {
setTimeout(() => {
// This will warn if state updates after unmount
console.log(`[${componentName}] Cleanup verification`);
}, 100);
}
};
}, [componentName]);
}
// Safe async state updates
function useSafeState<T>(initialState: T) {
const [state, setState] = useState(initialState);
const isMountedRef = useRef(true);
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
const setSafeState = useCallback((value: T | ((prev: T) => T)) => {
if (isMountedRef.current) {
setState(value);
}
}, []);
return [state, setSafeState] as const;
}
// Usage
function AsyncComponent() {
const [data, setData] = useSafeState(null);
useEffect(() => {
fetchData().then(result => {
setData(result); // Safe even if unmounted
});
}, [setData]);
return <div>{JSON.stringify(data)}</div>;
}
// Memory profiling helper
class MemoryProfiler {
private snapshots: number[] = [];
takeSnapshot() {
if (performance.memory) {
this.snapshots.push(performance.memory.usedJSHeapSize);
console.log(`Heap size: ${(performance.memory.usedJSHeapSize / 1048576).toFixed(2)} MB`);
}
}
analyze() {
if (this.snapshots.length < 2) return;
const growth = this.snapshots[this.snapshots.length - 1] - this.snapshots[0];
const avgGrowth = growth / this.snapshots.length;
console.log(`Total growth: ${(growth / 1048576).toFixed(2)} MB`);
console.log(`Average growth per snapshot: ${(avgGrowth / 1048576).toFixed(2)} MB`);
}
}
const profiler = new MemoryProfiler();
// Take snapshots periodically
setInterval(() => profiler.takeSnapshot(), 5000);
4. Re-render Analysis and Optimization
| Technique | Implementation | Impact | When to Use |
|---|---|---|---|
| React.memo | Wrap component with memo | Skip re-render if props unchanged | Pure components with expensive renders |
| useMemo | Memoize expensive calculations | Cache computed values | Heavy computations, derived data |
| useCallback | Memoize function references | Stable function identity | Callbacks passed to memo'd children |
| Context Splitting | Separate contexts by update frequency | Reduce context re-render scope | Large context with mixed data |
| Component Splitting | Break into smaller components | Isolate re-renders | Large components with mixed state |
| Virtualization | Render only visible items | Massive performance gain for lists | Long lists, tables, grids |
Example: Re-render optimization patterns
// ✅ React.memo with custom comparison
interface ItemProps {
item: { id: string; name: string; price: number };
onSelect: (id: string) => void;
}
const ListItem = memo(({ item, onSelect }: ItemProps) => {
console.log(`Rendering item: ${item.id}`);
return (
<div onClick={() => onSelect(item.id)}>
{item.name} - ${item.price}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison: only re-render if item changed
return prevProps.item.id === nextProps.item.id &&
prevProps.item.name === nextProps.item.name &&
prevProps.item.price === nextProps.item.price;
});
// ✅ useCallback for stable function reference
function ProductList() {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [items, setItems] = useState(products);
// Memoize callback to prevent ListItem re-renders
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);
return (
<div>
{items.map(item => (
<ListItem key={item.id} item={item} onSelect={handleSelect} />
))}
</div>
);
}
// ✅ useMemo for expensive calculations
function DataTable({ data }: { data: DataPoint[] }) {
// Only recalculate when data changes
const stats = useMemo(() => {
console.log('Calculating stats...');
return {
total: data.reduce((sum, d) => sum + d.value, 0),
average: data.reduce((sum, d) => sum + d.value, 0) / data.length,
max: Math.max(...data.map(d => d.value)),
min: Math.min(...data.map(d => d.value)),
};
}, [data]);
return (
<div>
<div>Total: {stats.total}</div>
<div>Average: {stats.average}</div>
<div>Max: {stats.max}</div>
<div>Min: {stats.min}</div>
</div>
);
}
// ✅ Context splitting to reduce re-renders
// Bad: Single context causes all consumers to re-render
const AppContext = createContext({
user: null,
theme: 'light',
settings: {},
notifications: [],
});
// Good: Split into separate contexts
const UserContext = createContext(null);
const ThemeContext = createContext('light');
const SettingsContext = createContext({});
const NotificationsContext = createContext([]);
// Components only re-render when their specific context changes
function UserProfile() {
const user = useContext(UserContext); // Only re-renders on user change
return <div>{user?.name}</div>;
}
function ThemeSwitcher() {
const theme = useContext(ThemeContext); // Only re-renders on theme change
return <button>{theme}</button>;
}
Example: Advanced re-render debugging
// Custom hook to track re-renders
function useRenderCount(componentName: string) {
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
console.log(`[${componentName}] Render count: ${renderCount.current}`);
});
return renderCount.current;
}
// Hook to track prop changes
function useWhyDidYouUpdate(name: string, props: Record<string, any>) {
const previousProps = useRef<Record<string, any>>();
useEffect(() => {
if (previousProps.current) {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changedProps: Record<string, any> = {};
allKeys.forEach(key => {
if (previousProps.current![key] !== props[key]) {
changedProps[key] = {
from: previousProps.current![key],
to: props[key],
};
}
});
if (Object.keys(changedProps).length > 0) {
console.log('[why-did-you-update]', name, changedProps);
}
}
previousProps.current = props;
});
}
// Usage in component
function ExpensiveComponent({ data, config, onUpdate }: Props) {
const renderCount = useRenderCount('ExpensiveComponent');
useWhyDidYouUpdate('ExpensiveComponent', { data, config, onUpdate });
return <div>Rendered {renderCount} times</div>;
}
// Component splitting for optimization
// ❌ Bad: Entire form re-renders on any input change
function BadForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
});
return (
<div>
<input
value={formData.name}
onChange={e => setFormData({...formData, name: e.target.value})}
/>
<input
value={formData.email}
onChange={e => setFormData({...formData, email: e.target.value})}
/>
<ExpensivePreview data={formData} />
</div>
);
}
// ✅ Good: Split into controlled components
const FormInput = memo(({ value, onChange, name }: any) => {
console.log(`Rendering input: ${name}`);
return <input value={value} onChange={onChange} />;
});
function GoodForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const formData = useMemo(() => ({ name, email }), [name, email]);
return (
<div>
<FormInput name="name" value={name} onChange={e => setName(e.target.value)} />
<FormInput name="email" value={email} onChange={e => setEmail(e.target.value)} />
<ExpensivePreview data={formData} />
</div>
);
}
5. Core Web Vitals and React Performance
| Metric | Target | React Impact | Optimization Strategy |
|---|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Initial render performance | Code splitting, SSR, image optimization |
| FID (First Input Delay) | < 100ms | JavaScript execution time | Reduce bundle size, defer non-critical JS |
| CLS (Cumulative Layout Shift) | < 0.1 | Dynamic content insertion | Reserve space, avoid layout shifts |
| FCP (First Contentful Paint) | < 1.8s | Time to first render | Minimize initial bundle, critical CSS |
| TTFB (Time to First Byte) | < 600ms | Server response time | SSR optimization, CDN, caching |
| TBT (Total Blocking Time) | < 200ms | Long tasks blocking main thread | Code splitting, Web Workers |
Example: Core Web Vitals optimization
// LCP optimization - prioritize hero image
function HeroSection() {
return (
<section>
<img
src="/hero-large.jpg"
alt="Hero"
// Prioritize loading
loading="eager"
fetchpriority="high"
// Prevent layout shift
width={1200}
height={600}
/>
</section>
);
}
// FID optimization - defer non-critical code
function App() {
const [analyticsLoaded, setAnalyticsLoaded] = useState(false);
useEffect(() => {
// Load analytics after initial render
requestIdleCallback(() => {
import('./analytics').then(module => {
module.initAnalytics();
setAnalyticsLoaded(true);
});
});
}, []);
return <MainApp />;
}
// CLS optimization - prevent layout shifts
function ImageWithPlaceholder({ src, alt }: { src: string; alt: string }) {
const [loaded, setLoaded] = useState(false);
return (
<div style={{ position: 'relative', width: 300, height: 200 }}>
{/* Placeholder reserves space */}
{!loaded && (
<div
style={{
position: 'absolute',
inset: 0,
backgroundColor: '#f0f0f0'
}}
/>
)}
<img
src={src}
alt={alt}
width={300}
height={200}
onLoad={() => setLoaded(true)}
style={{ display: loaded ? 'block' : 'none' }}
/>
</div>
);
}
// Measure Core Web Vitals
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
function reportWebVitals() {
const sendToAnalytics = ({ name, value, id }: any) => {
// Send to analytics endpoint
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
metric: name,
value: Math.round(name === 'CLS' ? value * 1000 : value),
id,
timestamp: Date.now(),
}),
keepalive: true,
});
};
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}
// Initialize in app
useEffect(() => {
reportWebVitals();
}, []);
Example: React performance best practices
// Optimize initial load with React.lazy
const HeavyChart = lazy(() =>
import(/* webpackChunkName: "chart" */ './HeavyChart')
);
function Dashboard() {
return (
<div>
<Header /> {/* Render immediately */}
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart /> {/* Load after initial render */}
</Suspense>
</div>
);
}
// Prevent CLS with skeleton screens
function SkeletonCard() {
return (
<div
style={{
width: '300px',
height: '200px',
backgroundColor: '#f0f0f0',
borderRadius: '8px',
animation: 'pulse 1.5s infinite',
}}
/>
);
}
function ProductCard({ id }: { id: string }) {
const { data, loading } = useFetch<Product>(`/api/products/${id}`);
if (loading) return <SkeletonCard />; // Same dimensions as actual card
return (
<div style={{ width: '300px', height: '200px' }}>
<img src={data.image} alt={data.name} />
<h3>{data.name}</h3>
</div>
);
}
// Optimize TBT with progressive hydration
function ProgressiveApp() {
const [hydrated, setHydrated] = useState({
header: false,
content: false,
footer: false,
});
useEffect(() => {
// Hydrate header first
setHydrated(prev => ({ ...prev, header: true }));
requestIdleCallback(() => {
// Hydrate content when idle
setHydrated(prev => ({ ...prev, content: true }));
requestIdleCallback(() => {
// Hydrate footer last
setHydrated(prev => ({ ...prev, footer: true }));
});
});
}, []);
return (
<div>
{hydrated.header ? <Header /> : <HeaderSSR />}
{hydrated.content ? <Content /> : <ContentSSR />}
{hydrated.footer ? <Footer /> : <FooterSSR />}
</div>
);
}
6. Production Performance Monitoring Tools
| Tool | Features | Use Case | Integration |
|---|---|---|---|
| Sentry | Error tracking, performance monitoring, tracing | Production error tracking, performance issues | React SDK, automatic error boundaries |
| New Relic | APM, browser monitoring, distributed tracing | Full-stack performance monitoring | Browser agent, React instrumentation |
| Datadog RUM | Real user monitoring, session replay, metrics | User experience analytics | Browser SDK, React integration |
| LogRocket | Session replay, performance monitoring, logs | Debug production issues with replays | React SDK, Redux integration |
| Lighthouse CI | Automated performance testing, CI/CD integration | Performance regression prevention | GitHub Actions, GitLab CI |
| Web Vitals Library | Measure Core Web Vitals in production | Track real user metrics | NPM package, analytics integration |
Example: Production monitoring setup
// Sentry integration
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true,
}),
],
// Performance monitoring
tracesSampleRate: 0.1, // 10% of transactions
// Session replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
// Environment
environment: process.env.NODE_ENV,
// Release tracking
release: process.env.REACT_APP_VERSION,
// Custom tags
beforeSend(event, hint) {
// Add custom context
event.tags = {
...event.tags,
'user.role': getCurrentUserRole(),
};
return event;
},
});
// Wrap app with Sentry error boundary
function App() {
return (
<Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
<Router />
</Sentry.ErrorBoundary>
);
}
// Track custom performance metrics
function trackCustomMetric(name: string, value: number) {
Sentry.metrics.distribution(name, value, {
unit: 'millisecond',
tags: { page: window.location.pathname },
});
}
// Component performance tracking
function ExpensiveComponent() {
useEffect(() => {
const startTime = performance.now();
return () => {
const duration = performance.now() - startTime;
trackCustomMetric('ExpensiveComponent.renderTime', duration);
};
});
return <div>...</div>;
}
Example: Multi-tool monitoring setup
// LogRocket setup
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';
LogRocket.init('YOUR_APP_ID', {
release: process.env.REACT_APP_VERSION,
network: {
requestSanitizer: request => {
// Sanitize sensitive data
if (request.headers['Authorization']) {
request.headers['Authorization'] = '[REDACTED]';
}
return request;
},
},
});
setupLogRocketReact(LogRocket);
// Connect to Sentry
LogRocket.getSessionURL(sessionURL => {
Sentry.configureScope(scope => {
scope.setExtra('sessionURL', sessionURL);
});
});
// New Relic Browser Agent
// Add to index.html or initialize programmatically
<script src="https://js-agent.newrelic.com/nr-loader.js"></script>
<script>
window.NREUM.init = {
applicationID: 'YOUR_APP_ID',
licenseKey: 'YOUR_LICENSE_KEY',
// Additional config
};
</script>
// Datadog RUM
import { datadogRum } from '@datadog/browser-rum';
datadogRum.init({
applicationId: 'YOUR_APP_ID',
clientToken: 'YOUR_CLIENT_TOKEN',
site: 'datadoghq.com',
service: 'react-app',
env: process.env.NODE_ENV,
version: process.env.REACT_APP_VERSION,
sessionSampleRate: 100,
sessionReplaySampleRate: 20,
trackUserInteractions: true,
trackResources: true,
trackLongTasks: true,
defaultPrivacyLevel: 'mask-user-input',
});
// Start session replay
datadogRum.startSessionReplayRecording();
// Custom action tracking
function trackUserAction(name: string, context?: Record<string, any>) {
datadogRum.addAction(name, context);
LogRocket.track(name, context);
Sentry.addBreadcrumb({
message: name,
data: context,
});
}
// Web Vitals to all platforms
import { onCLS, onFID, onLCP } from 'web-vitals';
function sendToAllPlatforms(metric: any) {
// Send to Sentry
Sentry.metrics.distribution(metric.name, metric.value);
// Send to Datadog
datadogRum.addTiming(metric.name, metric.value);
// Send to custom analytics
fetch('/api/vitals', {
method: 'POST',
body: JSON.stringify(metric),
keepalive: true,
});
}
onCLS(sendToAllPlatforms);
onFID(sendToAllPlatforms);
onLCP(sendToAllPlatforms);
// Lighthouse CI configuration
// lighthouserc.js
module.exports = {
ci: {
collect: {
startServerCommand: 'npm run serve',
url: ['http://localhost:3000'],
numberOfRuns: 3,
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
Performance Monitoring Best Practices: Use React Profiler API for component-level insights,
implement code splitting at route and component levels, detect and prevent memory leaks with proper cleanup,
optimize re-renders with memo/useMemo/useCallback, monitor Core Web Vitals in production, integrate multiple
monitoring tools for comprehensive coverage, set up automated performance testing in CI/CD, track custom
business metrics, sanitize sensitive data in monitoring tools, establish performance budgets.