'use client'; import { useEffect } from 'react'; import { trackWebVitals, trackPerformance } from './analytics'; // Web Vitals types interface Metric { name: string; value: number; delta: number; id: string; } // Simple Web Vitals implementation (since we don't want to add external dependencies) const getCLS = (onPerfEntry: (metric: Metric) => void) => { if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') return null; try { let clsValue = 0; let sessionValue = 0; let sessionEntries: PerformanceEntry[] = []; const observer = new PerformanceObserver((list) => { try { for (const entry of list.getEntries()) { if (!(entry as PerformanceEntry & { hadRecentInput?: boolean }).hadRecentInput) { const firstSessionEntry = sessionEntries[0]; const lastSessionEntry = sessionEntries[sessionEntries.length - 1]; if (sessionValue && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000) { sessionValue += (entry as PerformanceEntry & { value?: number }).value || 0; sessionEntries.push(entry); } else { sessionValue = (entry as PerformanceEntry & { value?: number }).value || 0; sessionEntries = [entry]; } if (sessionValue > clsValue) { clsValue = sessionValue; onPerfEntry({ name: 'CLS', value: clsValue, delta: clsValue, id: `cls-${Date.now()}`, }); } } } } catch (error) { // Silently fail - CLS tracking is not critical if (process.env.NODE_ENV === 'development') { console.warn('CLS tracking error:', error); } } }); observer.observe({ type: 'layout-shift', buffered: true }); return observer; } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('CLS observer initialization failed:', error); } return null; } }; const getFID = (onPerfEntry: (metric: Metric) => void) => { if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') return null; try { const observer = new PerformanceObserver((list) => { try { for (const entry of list.getEntries()) { const processingStart = (entry as PerformanceEntry & { processingStart?: number }).processingStart; if (processingStart !== undefined) { onPerfEntry({ name: 'FID', value: processingStart - entry.startTime, delta: processingStart - entry.startTime, id: `fid-${Date.now()}`, }); } } } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('FID tracking error:', error); } } }); observer.observe({ type: 'first-input', buffered: true }); return observer; } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('FID observer initialization failed:', error); } return null; } }; const getFCP = (onPerfEntry: (metric: Metric) => void) => { if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') return null; try { const observer = new PerformanceObserver((list) => { try { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { onPerfEntry({ name: 'FCP', value: entry.startTime, delta: entry.startTime, id: `fcp-${Date.now()}`, }); } } } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('FCP tracking error:', error); } } }); observer.observe({ type: 'paint', buffered: true }); return observer; } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('FCP observer initialization failed:', error); } return null; } }; const getLCP = (onPerfEntry: (metric: Metric) => void) => { if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') return null; try { const observer = new PerformanceObserver((list) => { try { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; if (lastEntry) { onPerfEntry({ name: 'LCP', value: lastEntry.startTime, delta: lastEntry.startTime, id: `lcp-${Date.now()}`, }); } } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('LCP tracking error:', error); } } }); observer.observe({ type: 'largest-contentful-paint', buffered: true }); return observer; } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('LCP observer initialization failed:', error); } return null; } }; const getTTFB = (onPerfEntry: (metric: Metric) => void) => { if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') return null; try { const observer = new PerformanceObserver((list) => { try { for (const entry of list.getEntries()) { if (entry.entryType === 'navigation') { const navEntry = entry as PerformanceNavigationTiming; if (navEntry.responseStart && navEntry.fetchStart) { onPerfEntry({ name: 'TTFB', value: navEntry.responseStart - navEntry.fetchStart, delta: navEntry.responseStart - navEntry.fetchStart, id: `ttfb-${Date.now()}`, }); } } } } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('TTFB tracking error:', error); } } }); observer.observe({ type: 'navigation', buffered: true }); return observer; } catch (error) { if (process.env.NODE_ENV === 'development') { console.warn('TTFB observer initialization failed:', error); } return null; } }; // Custom hook for Web Vitals tracking export const useWebVitals = () => { useEffect(() => { if (typeof window === 'undefined') return; // Wrap everything in try-catch to prevent errors from breaking the app try { const safeNow = () => { if (typeof performance !== "undefined" && typeof performance.now === "function") { return performance.now(); } return Date.now(); }; // Store web vitals for batch sending const webVitals: Record = {}; const path = window.location.pathname; const projectMatch = path.match(/\/projects\/([^\/]+)/); const projectId = projectMatch ? projectMatch[1] : null; const observers: PerformanceObserver[] = []; const sendWebVitals = async () => { if (Object.keys(webVitals).length >= 3) { // Wait for at least FCP, LCP, CLS try { await fetch('/api/analytics/track', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'performance', projectId: projectId, page: path, performance: { fcp: webVitals.FCP || 0, lcp: webVitals.LCP || 0, cls: webVitals.CLS || 0, fid: webVitals.FID || 0, ttfb: webVitals.TTFB || 0, loadTime: safeNow() } }) }); } catch (error) { // Silently fail if (process.env.NODE_ENV === 'development') { console.error('Error sending web vitals:', error); } } } }; // Track Core Web Vitals const clsObserver = getCLS((metric) => { webVitals.CLS = metric.value; trackWebVitals({ ...metric, name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB', url: window.location.pathname, }); sendWebVitals(); }); if (clsObserver) observers.push(clsObserver); const fidObserver = getFID((metric) => { webVitals.FID = metric.value; trackWebVitals({ ...metric, name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB', url: window.location.pathname, }); sendWebVitals(); }); if (fidObserver) observers.push(fidObserver); const fcpObserver = getFCP((metric) => { webVitals.FCP = metric.value; trackWebVitals({ ...metric, name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB', url: window.location.pathname, }); sendWebVitals(); }); if (fcpObserver) observers.push(fcpObserver); const lcpObserver = getLCP((metric) => { webVitals.LCP = metric.value; trackWebVitals({ ...metric, name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB', url: window.location.pathname, }); sendWebVitals(); }); if (lcpObserver) observers.push(lcpObserver); const ttfbObserver = getTTFB((metric) => { webVitals.TTFB = metric.value; trackWebVitals({ ...metric, name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB', url: window.location.pathname, }); sendWebVitals(); }); if (ttfbObserver) observers.push(ttfbObserver); // Track page load performance const handleLoad = () => { setTimeout(() => { trackPerformance({ name: 'page-load-complete', value: safeNow(), url: window.location.pathname, timestamp: Date.now(), userAgent: navigator.userAgent, }); }, 0); }; if (document.readyState === 'complete') { handleLoad(); } else { window.addEventListener('load', handleLoad); } return () => { // Cleanup all observers observers.forEach(observer => { try { observer.disconnect(); } catch { // Silently fail } }); try { window.removeEventListener('load', handleLoad); } catch { // Silently fail } }; } catch (error) { // If Web Vitals initialization fails, don't break the app if (process.env.NODE_ENV === 'development') { console.warn('Web Vitals initialization failed:', error); } // Return empty cleanup function return () => {}; } }, []); };