Spaces:
Configuration error
Configuration error
| "use client" | |
| import { useEffect } from "react" | |
| /** | |
| * Reports Core Web Vitals (LCP, FID, CLS, FCP, TTFB) to console in development | |
| * and can be extended to send to analytics in production. | |
| * | |
| * Uses the web-vitals library pattern via PerformanceObserver. | |
| */ | |
| export function WebVitals() { | |
| useEffect(() => { | |
| if (typeof window === "undefined") return | |
| // Only report in development or if analytics endpoint is configured | |
| const shouldReport = process.env.NODE_ENV === "development" || !!process.env.NEXT_PUBLIC_ANALYTICS_URL | |
| if (!shouldReport) return | |
| // Use Performance Observer for CLS, LCP, FID | |
| try { | |
| // Largest Contentful Paint | |
| const lcpObserver = new PerformanceObserver((list) => { | |
| const entries = list.getEntries() | |
| const lastEntry = entries[entries.length - 1] | |
| if (lastEntry) { | |
| reportMetric("LCP", lastEntry.startTime) | |
| } | |
| }) | |
| lcpObserver.observe({ type: "largest-contentful-paint", buffered: true }) | |
| // First Input Delay | |
| const fidObserver = new PerformanceObserver((list) => { | |
| const entries = list.getEntries() | |
| entries.forEach((entry) => { | |
| const fidEntry = entry as PerformanceEventTiming | |
| reportMetric("FID", fidEntry.processingStart - fidEntry.startTime) | |
| }) | |
| }) | |
| fidObserver.observe({ type: "first-input", buffered: true }) | |
| // Cumulative Layout Shift | |
| let clsValue = 0 | |
| const clsObserver = new PerformanceObserver((list) => { | |
| const entries = list.getEntries() | |
| entries.forEach((entry) => { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| if (!(entry as any).hadRecentInput) { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| clsValue += (entry as any).value | |
| } | |
| }) | |
| reportMetric("CLS", clsValue) | |
| }) | |
| clsObserver.observe({ type: "layout-shift", buffered: true }) | |
| return () => { | |
| lcpObserver.disconnect() | |
| fidObserver.disconnect() | |
| clsObserver.disconnect() | |
| } | |
| } catch { | |
| // PerformanceObserver not supported | |
| } | |
| }, []) | |
| return null | |
| } | |
| function reportMetric(name: string, value: number) { | |
| if (process.env.NODE_ENV === "development") { | |
| const color = getMetricColor(name, value) | |
| console.log( | |
| `%c[Web Vital] ${name}: ${value.toFixed(2)}ms`, | |
| `color: ${color}; font-weight: bold;` | |
| ) | |
| } | |
| // Send to analytics endpoint if configured | |
| const analyticsUrl = process.env.NEXT_PUBLIC_ANALYTICS_URL | |
| if (analyticsUrl) { | |
| const body = JSON.stringify({ name, value, page: window.location.pathname }) | |
| // Use sendBeacon for reliability during page unload | |
| if (navigator.sendBeacon) { | |
| navigator.sendBeacon(analyticsUrl, body) | |
| } else { | |
| fetch(analyticsUrl, { method: "POST", body, keepalive: true }).catch(() => {}) | |
| } | |
| } | |
| } | |
| function getMetricColor(name: string, value: number): string { | |
| switch (name) { | |
| case "LCP": | |
| return value <= 2500 ? "green" : value <= 4000 ? "orange" : "red" | |
| case "FID": | |
| return value <= 100 ? "green" : value <= 300 ? "orange" : "red" | |
| case "CLS": | |
| return value <= 0.1 ? "green" : value <= 0.25 ? "orange" : "red" | |
| default: | |
| return "blue" | |
| } | |
| } | |