Performance Monitoring
Performance monitoring is crucial for understanding how your Refract application behaves in the real world. It helps you identify bottlenecks, track user experience, and make data-driven decisions about optimizations.
Think of it as having a health monitor for your application - it tells you when things are running smoothly and alerts you when performance degrades, so you can fix issues before they affect your users.
Why Monitor Performance?
Your application might work perfectly on your development machine, but real users have different devices, network conditions, and usage patterns. Performance monitoring helps you:
- Catch issues early - Identify problems before they impact many users
- Understand user experience - See how your app performs on different devices
- Make informed decisions - Use real data to guide optimization efforts
- Track improvements - Measure the impact of performance optimizations
Without monitoring, you're flying blind - you won't know if your application is fast or slow for real users.
Built-in Performance Tracking
Refract provides built-in performance monitoring through the application configuration:
import { createApp } from 'refract';
const app = createApp(App, {
// Enable performance monitoring
performance: {
enabled: true,
sampleRate: 0.1, // Monitor 10% of interactions
thresholds: {
componentRender: 16, // Warn if component takes >16ms to render
effectExecution: 5, // Warn if effect takes >5ms
stateUpdate: 1 // Warn if state update takes >1ms
}
},
// Performance event handler
onPerformanceEntry: (entry) => {
// Send to your analytics service
analytics.track('performance', {
type: entry.type,
duration: entry.duration,
componentName: entry.componentName,
timestamp: entry.timestamp
});
}
});
Core Web Vitals Integration
Track Core Web Vitals to understand user experience:
- Setup
- Component Tracking
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
const trackWebVitals = (metric) => {
// Send to your analytics service
analytics.track('web-vital', {
name: metric.name,
value: metric.value,
id: metric.id,
delta: metric.delta
});
};
// Track all Core Web Vitals
getCLS(trackWebVitals);
getFID(trackWebVitals);
getFCP(trackWebVitals);
getLCP(trackWebVitals);
getTTFB(trackWebVitals);
// Initialize Refract app
const app = createApp(App, {
onMount: () => {
// Track app initialization time
performance.mark('refract-app-ready');
performance.measure('refract-init', 'navigationStart', 'refract-app-ready');
}
});
const PerformanceTracker = createComponent(({ lens }) => {
const renderCount = lens.useRefraction(0);
const renderTimes = lens.useRefraction([]);
lens.useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const renderTime = endTime - startTime;
renderCount.set(count => count + 1);
renderTimes.set(times => [...times.slice(-9), renderTime]);
// Track slow renders
if (renderTime > 16) {
analytics.track('slow-render', {
component: 'PerformanceTracker',
renderTime,
renderCount: renderCount.value
});
}
};
});
const averageRenderTime = renderTimes.value.length > 0
? renderTimes.value.reduce((a, b) => a + b, 0) / renderTimes.value.length
: 0;
return (
<div className="performance-info">
<p>Renders: {renderCount.value}</p>
<p>Avg Render Time: {averageRenderTime.toFixed(2)}ms</p>
<p>Last 10 Renders: {renderTimes.value.map(t => t.toFixed(1)).join(', ')}ms</p>
</div>
);
});
Memory Usage Monitoring
Track memory usage to prevent memory leaks:
const useMemoryMonitor = (lens) => {
const memoryInfo = lens.useRefraction(null);
lens.useEffect(() => {
const updateMemoryInfo = () => {
if ('memory' in performance) {
memoryInfo.set({
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
timestamp: Date.now()
});
}
};
// Update immediately
updateMemoryInfo();
// Update every 30 seconds
const interval = setInterval(updateMemoryInfo, 30000);
return () => clearInterval(interval);
}, []);
return memoryInfo.value;
};
const MemoryMonitor = createComponent(({ lens }) => {
const memory = useMemoryMonitor(lens);
lens.useEffect(() => {
if (!memory) return;
const usagePercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
// Alert if memory usage is high
if (usagePercent > 80) {
analytics.track('high-memory-usage', {
usagePercent,
usedMB: Math.round(memory.usedJSHeapSize / 1024 / 1024),
totalMB: Math.round(memory.totalJSHeapSize / 1024 / 1024)
});
}
}, [memory]);
if (!memory) return <div>Memory info not available</div>;
const usedMB = Math.round(memory.usedJSHeapSize / 1024 / 1024);
const totalMB = Math.round(memory.totalJSHeapSize / 1024 / 1024);
const limitMB = Math.round(memory.jsHeapSizeLimit / 1024 / 1024);
return (
<div className="memory-monitor">
<h3>Memory Usage</h3>
<p>Used: {usedMB}MB / {totalMB}MB</p>
<p>Limit: {limitMB}MB</p>
<div
className="memory-bar"
style={{
width: '200px',
height: '20px',
backgroundColor: '#f0f0f0',
borderRadius: '10px',
overflow: 'hidden'
}}
>
<div
style={{
width: `${(usedMB / limitMB) * 100}%`,
height: '100%',
backgroundColor: usedMB / limitMB > 0.8 ? '#ff4444' : '#44ff44',
transition: 'width 0.3s ease'
}}
/>
</div>
</div>
);
});
User Interaction Tracking
Monitor user interactions and their performance impact:
const useInteractionTracking = (lens) => {
lens.useEffect(() => {
const trackInteraction = (event) => {
const startTime = performance.now();
// Use requestIdleCallback to measure after interaction is processed
requestIdleCallback(() => {
const endTime = performance.now();
const duration = endTime - startTime;
analytics.track('user-interaction', {
type: event.type,
target: event.target.tagName,
duration,
timestamp: startTime
});
// Track slow interactions
if (duration > 100) {
analytics.track('slow-interaction', {
type: event.type,
duration,
target: event.target.tagName
});
}
});
};
// Track various interaction types
const events = ['click', 'input', 'scroll', 'keydown'];
events.forEach(eventType => {
document.addEventListener(eventType, trackInteraction, { passive: true });
});
return () => {
events.forEach(eventType => {
document.removeEventListener(eventType, trackInteraction);
});
};
}, []);
};
const App = createComponent(({ lens }) => {
useInteractionTracking(lens);
return (
<div>
{/* Your app content */}
</div>
);
});
Bundle Size Monitoring
Track bundle size and loading performance:
// Add to your build process
const bundleAnalyzer = {
onBuildComplete: (stats) => {
const bundleSize = stats.assets
.filter(asset => asset.name.endsWith('.js'))
.reduce((total, asset) => total + asset.size, 0);
// Track bundle size changes
analytics.track('bundle-size', {
size: bundleSize,
gzipSize: stats.gzipSize,
chunks: stats.chunks.length,
buildTime: stats.time
});
// Alert if bundle size increases significantly
if (bundleSize > 500000) { // 500KB threshold
console.warn(`Bundle size is large: ${Math.round(bundleSize / 1024)}KB`);
}
}
};
// Runtime bundle tracking
const trackResourceTiming = () => {
const resources = performance.getEntriesByType('resource');
resources
.filter(resource => resource.name.includes('.js'))
.forEach(resource => {
analytics.track('resource-timing', {
name: resource.name,
size: resource.transferSize,
loadTime: resource.responseEnd - resource.requestStart,
cacheHit: resource.transferSize === 0
});
});
};
// Call after app loads
window.addEventListener('load', trackResourceTiming);
Error Boundary with Performance Impact
Track how errors affect performance:
const ErrorBoundary = createComponent(({ lens, children }) => {
const error = lens.useRefraction(null);
const errorCount = lens.useRefraction(0);
const performanceImpact = lens.useRefraction(null);
lens.useEffect(() => {
const handleError = (event) => {
const errorTime = performance.now();
error.set({
message: event.error?.message || 'Unknown error',
stack: event.error?.stack,
timestamp: errorTime
});
errorCount.set(count => count + 1);
// Measure performance impact
requestIdleCallback(() => {
const recoveryTime = performance.now() - errorTime;
performanceImpact.set(recoveryTime);
analytics.track('error-performance-impact', {
errorMessage: event.error?.message,
recoveryTime,
errorCount: errorCount.value
});
});
};
window.addEventListener('error', handleError);
window.addEventListener('unhandledrejection', handleError);
return () => {
window.removeEventListener('error', handleError);
window.removeEventListener('unhandledrejection', handleError);
};
}, []);
if (error.value) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<p>{error.value.message}</p>
{performanceImpact.value && (
<p>Recovery time: {performanceImpact.value.toFixed(2)}ms</p>
)}
<button onClick={() => {
error.set(null);
performanceImpact.set(null);
}}>
Try Again
</button>
</div>
);
}
return children;
});
Performance Dashboard
Create a real-time performance dashboard:
const PerformanceDashboard = createComponent(({ lens }) => {
const metrics = lens.useRefraction({
renderTime: 0,
memoryUsage: 0,
errorCount: 0,
interactionLatency: 0
});
lens.useEffect(() => {
const updateMetrics = () => {
// Collect current metrics
const currentMetrics = {
renderTime: performance.now() % 100, // Simulated
memoryUsage: performance.memory?.usedJSHeapSize || 0,
errorCount: window.errorCount || 0,
interactionLatency: window.lastInteractionTime || 0
};
metrics.set(currentMetrics);
};
const interval = setInterval(updateMetrics, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="performance-dashboard">
<h2>Performance Dashboard</h2>
<div className="metrics-grid">
<div className="metric">
<h3>Render Time</h3>
<span className={metrics.value.renderTime > 16 ? 'warning' : 'good'}>
{metrics.value.renderTime.toFixed(2)}ms
</span>
</div>
<div className="metric">
<h3>Memory Usage</h3>
<span>
{Math.round(metrics.value.memoryUsage / 1024 / 1024)}MB
</span>
</div>
<div className="metric">
<h3>Error Count</h3>
<span className={metrics.value.errorCount > 0 ? 'error' : 'good'}>
{metrics.value.errorCount}
</span>
</div>
<div className="metric">
<h3>Interaction Latency</h3>
<span className={metrics.value.interactionLatency > 100 ? 'warning' : 'good'}>
{metrics.value.interactionLatency.toFixed(2)}ms
</span>
</div>
</div>
</div>
);
});
Best Practices
- Monitor continuously: Set up automated performance monitoring in production
- Set meaningful thresholds: Define performance budgets for your application
- Track user impact: Focus on metrics that affect user experience
- Use sampling: Don't track every interaction to avoid performance overhead
- Correlate with business metrics: Connect performance data to user behavior
- Set up alerts: Get notified when performance degrades
- Regular audits: Periodically review and optimize based on monitoring data
Related Topics
- CI/CD Pipeline - Automated performance testing
- Scaling Applications - Performance at scale