114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useCallback } from "react";
|
|
|
|
// Extend Window interface for gtag
|
|
declare global {
|
|
interface Window {
|
|
gtag?: (command: string, targetId: string, config: any) => void;
|
|
}
|
|
}
|
|
|
|
interface PerformanceMetrics {
|
|
FCP: number | null;
|
|
LCP: number | null;
|
|
FID: number | null;
|
|
CLS: number | null;
|
|
TTFB: number | null;
|
|
}
|
|
|
|
export const usePerformance = () => {
|
|
const reportMetric = useCallback((name: string, value: number) => {
|
|
// Send to analytics service
|
|
if (typeof window !== "undefined" && window.gtag) {
|
|
window.gtag("event", name, {
|
|
value: Math.round(name === "CLS" ? value * 1000 : value),
|
|
metric_id: name,
|
|
metric_value: value,
|
|
metric_delta: 0,
|
|
});
|
|
}
|
|
|
|
// Log to console in development
|
|
if (process.env.NODE_ENV === "development") {
|
|
console.log(`Performance Metric - ${name}:`, value);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
|
|
// First Contentful Paint
|
|
const fcpObserver = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
const fcp = entries[entries.length - 1];
|
|
if (fcp) {
|
|
reportMetric("FCP", fcp.startTime);
|
|
}
|
|
});
|
|
fcpObserver.observe({ entryTypes: ["paint"] });
|
|
|
|
// Largest Contentful Paint
|
|
const lcpObserver = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
const lcp = entries[entries.length - 1];
|
|
if (lcp) {
|
|
reportMetric("LCP", lcp.startTime);
|
|
}
|
|
});
|
|
lcpObserver.observe({ entryTypes: ["largest-contentful-paint"] });
|
|
|
|
// First Input Delay
|
|
const fidObserver = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
entries.forEach((entry) => {
|
|
const firstInputEntry = entry as PerformanceEventTiming;
|
|
if (firstInputEntry.processingStart) {
|
|
const fid = firstInputEntry.processingStart - firstInputEntry.startTime;
|
|
reportMetric("FID", fid);
|
|
}
|
|
});
|
|
});
|
|
fidObserver.observe({ entryTypes: ["first-input"] });
|
|
|
|
// Cumulative Layout Shift
|
|
let clsValue = 0;
|
|
const clsObserver = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
entries.forEach((entry: any) => {
|
|
if (!entry.hadRecentInput) {
|
|
clsValue += entry.value;
|
|
}
|
|
});
|
|
});
|
|
clsObserver.observe({ entryTypes: ["layout-shift"] });
|
|
|
|
// Time to First Byte
|
|
const navigationEntry = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming;
|
|
if (navigationEntry) {
|
|
const ttfb = navigationEntry.responseStart - navigationEntry.requestStart;
|
|
reportMetric("TTFB", ttfb);
|
|
}
|
|
|
|
// Report CLS on page unload
|
|
const handleBeforeUnload = () => {
|
|
if (clsValue > 0) {
|
|
reportMetric("CLS", clsValue);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
|
|
return () => {
|
|
fcpObserver.disconnect();
|
|
lcpObserver.disconnect();
|
|
fidObserver.disconnect();
|
|
clsObserver.disconnect();
|
|
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
};
|
|
}, [reportMetric]);
|
|
|
|
return {
|
|
reportMetric,
|
|
};
|
|
};
|