export class PerformanceMonitor {
private metrics: Map<string, PerformanceMetric[]> = new Map();
private observers: PerformanceObserver[] = [];
// Monitor Core Web Vitals
// Monitor React performance
this.monitorReactPerformance();
// Send metrics every 10 seconds
setInterval(() => this.sendMetrics(), 10000);
private observeWebVitals() {
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.recordMetric('lcp', lastEntry.startTime);
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
const fidObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.recordMetric('fid', entry.processingStart - entry.startTime);
fidObserver.observe({ entryTypes: ['first-input'] });
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
this.recordMetric('cls', clsValue);
clsObserver.observe({ entryTypes: ['layout-shift'] });
this.observers.push(lcpObserver, fidObserver, clsObserver);
private monitorReactPerformance() {
if (window.React && window.React.Profiler) {
// Wrap React.Profiler to capture all renders
const originalProfiler = window.React.Profiler;
window.React.Profiler = (props) => {
const onRender = (id, phase, actualDuration, baseDuration) => {
this.recordMetric(`react-render-${id}`, actualDuration, {
timestamp: performance.now()
if (actualDuration > 16) { // 60fps threshold
console.warn(`Slow render detected: ${id} took ${actualDuration}ms`);
props.onRender?.(id, phase, actualDuration, baseDuration);
return originalProfiler({ ...props, onRender });
private interceptFetch() {
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = performance.now();
const url = args[0].toString();
const response = await originalFetch(...args);
const duration = performance.now() - startTime;
this.recordMetric('api-call', duration, {
method: args[1]?.method || 'GET'
const duration = performance.now() - startTime;
this.recordMetric('api-error', duration, {
private monitorMemory() {
if (!performance.memory) return;
const memoryInfo = performance.memory;
this.recordMetric('memory', memoryInfo.usedJSHeapSize, {
totalJSHeapSize: memoryInfo.totalJSHeapSize,
jsHeapSizeLimit: memoryInfo.jsHeapSizeLimit
// Alert on memory spikes
const usage = memoryInfo.usedJSHeapSize / memoryInfo.jsHeapSizeLimit;
console.error('Memory usage critical:', `${(usage * 100).toFixed(1)}%`);
private recordMetric(name: string, value: number, metadata?: any) {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
this.metrics.get(name)!.push({
private async sendMetrics() {
const metricsToSend = {};
for (const [name, values] of this.metrics.entries()) {
metricsToSend[name] = values;
this.metrics.set(name, []); // Clear sent metrics
if (Object.keys(metricsToSend).length > 0) {
await fetch('/api/metrics', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metricsToSend)
console.error('Failed to send metrics:', error);