interface PerformanceMetrics { lcp: number | null; fid: number | null; cls: number | null; fcp: number | null; ttfb: number | null; pageLoad: number | null; } interface ApiTiming { name: string; duration: number; timestamp: number; } interface ComponentTiming { name: string; duration: number; timestamp: number; } interface LayoutShift extends PerformanceEntry { value: number; hadRecentInput: boolean; } class PerformanceService { private metrics: PerformanceMetrics = { lcp: null, fid: null, cls: 0, fcp: null, ttfb: null, pageLoad: null, }; private apiTimings: ApiTiming[] = []; private componentTimings: ComponentTiming[] = []; constructor() { if (typeof window !== 'undefined' && typeof PerformanceObserver !== 'undefined') { this.observeLCP(); this.observeFID(); this.observeCLS(); this.observeFCP(); } } private observeFCP() { new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (entry.name === 'first-contentful-paint') { this.metrics.fcp = entry.startTime; } } }).observe({ type: 'paint', buffered: true }); } private observeLCP() { new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); if (entries.length > 0) { this.metrics.lcp = entries[entries.length - 1].startTime; } }).observe({ type: 'largest-contentful-paint', buffered: true }); } private observeFID() { new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { this.metrics.fid = (entry as PerformanceEventTiming).processingStart - entry.startTime; } }).observe({ type: 'first-input', buffered: true }); } private observeCLS() { new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (!(entry as LayoutShift).hadRecentInput) { this.metrics.cls += (entry as LayoutShift).value; } } }).observe({ type: 'layout-shift', buffered: true }); } public getMetrics(): PerformanceMetrics { if (typeof performance !== 'undefined') { const navEntry = performance.getEntriesByType('navigation')[0] as | PerformanceNavigationTiming | undefined; if (navEntry) { this.metrics.ttfb = navEntry.responseStart - navEntry.requestStart; this.metrics.pageLoad = navEntry.loadEventEnd - navEntry.startTime; } } return this.metrics; } public recordApiTiming(name: string, duration: number) { this.apiTimings.unshift({ name, duration, timestamp: Date.now() }); if (this.apiTimings.length > 10) { this.apiTimings.pop(); } } public getApiTimings(): ApiTiming[] { return this.apiTimings; } public recordComponentTiming(name: string, duration: number) { this.componentTimings.unshift({ name, duration, timestamp: Date.now() }); if (this.componentTimings.length > 10) { this.componentTimings.pop(); } } public getComponentTimings(): ComponentTiming[] { return this.componentTimings; } } export const performanceService = new PerformanceService();