// Performance monitoring utilities for Core Web Vitals
export interface PerformanceMetrics {
lcp: number;
fid: number;
cls: number;
fcp: number;
ttfb: number;
}
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: Partial<PerformanceMetrics> = {};
private observers: PerformanceObserver[] = [];
static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
private constructor() {
this.initializeObservers();
}
private initializeObservers(): void {
// Largest Contentful Paint (LCP)
if ('PerformanceObserver' in window) {
try {
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.startTime;
console.log('LCP:', this.metrics.lcp);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
this.observers.push(lcpObserver);
} catch (e) {
console.warn('LCP observer not supported');
}
// First Input Delay (FID)
try {
const fidObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry: any) => {
this.metrics.fid = entry.processingStart - entry.startTime;
console.log('FID:', this.metrics.fid);
});
});
fidObserver.observe({ entryTypes: ['first-input'] });
this.observers.push(fidObserver);
} catch (e) {
console.warn('FID observer not supported');
}
// Cumulative Layout Shift (CLS)
try {
let clsValue = 0;
const clsObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry: any) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
this.metrics.cls = clsValue;
console.log('CLS:', this.metrics.cls);
}
});
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
this.observers.push(clsObserver);
} catch (e) {
console.warn('CLS observer not supported');
}
// First Contentful Paint (FCP)
try {
const fcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.fcp = lastEntry.startTime;
console.log('FCP:', this.metrics.fcp);
});
fcpObserver.observe({ entryTypes: ['paint'] });
this.observers.push(fcpObserver);
} catch (e) {
console.warn('FCP observer not supported');
}
}
}
getMetrics(): Partial<PerformanceMetrics> {
return this.metrics;
}
logMetrics(): void {
console.log('Performance Metrics:', this.metrics);
}
disconnect(): void {
this.observers.forEach(observer => observer.disconnect());
}
}
// Utility functions for performance optimization
export const debounce = <T extends (...args: any[]) => any>(
func: T,
wait: number
): ((...args: Parameters<T>) => void) => {
let timeout: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};
export const throttle = <T extends (...args: any[]) => any>(
func: T,
limit: number
): ((...args: Parameters<T>) => void) => {
let inThrottle: boolean;
return (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
// Lazy loading utility
export const lazyLoadImage = (src: string, placeholder?: string): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = () => resolve(img);
img.onerror = reject;
if (placeholder) {
img.src = placeholder;
}
});
};
// Intersection Observer for lazy loading
export const createIntersectionObserver = (
callback: IntersectionObserverCallback,
options?: IntersectionObserverInit
): IntersectionObserver => {
const defaultOptions: IntersectionObserverInit = {
root: null,
rootMargin: '50px',
threshold: 0.1,
...options
};
return new IntersectionObserver(callback, defaultOptions);
};
// Memory usage monitoring
export const getMemoryUsage = (): { used: number; total: number } | null => {
if ('memory' in performance) {
const memory = (performance as any).memory;
return {
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize
};
}
return null;
};
// Web Vitals reporting
export const reportWebVitals = (metrics: Partial<PerformanceMetrics>): void => {
// Send to analytics service (if gtag is available)
if (typeof (window as any).gtag !== 'undefined') {
(window as any).gtag('event', 'web_vitals', {
event_category: 'Web Vitals',
event_label: 'LCP',
value: Math.round(metrics.lcp || 0),
custom_map: {
lcp: 'dimension1',
fid: 'dimension2',
cls: 'dimension3'
}
});
}
// Log for debugging
console.log('Web Vitals Report:', metrics);
};