/** * Web Vitals 采集(A11y + 性能) * * 仲裁:所有前端应用必须采集 Web Vitals * 端点:NEXT_PUBLIC_WEB_VITALS_ENDPOINT(默认 /api/admin/web-vitals) */ import { onLCP, onCLS, onFCP, onINP, onTTFB } from "web-vitals"; type Metric = { name: string; value: number; rating: string; id: string }; const ENDPOINT = process.env.NEXT_PUBLIC_WEB_VITALS_ENDPOINT ?? "/api/admin/web-vitals"; function sendMetric(metric: Metric): void { if (typeof navigator === "undefined") return; const body = JSON.stringify({ name: metric.name, value: metric.value, rating: metric.rating, id: metric.id, page: window.location.pathname, ts: Date.now(), }); // 使用 sendBeacon 避免阻塞卸载 if (navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, body); } else { void fetch(ENDPOINT, { method: "POST", body, keepalive: true }).catch( () => { // 忽略上报失败 }, ); } } export function initWebVitals(): void { if (typeof window === "undefined") return; onLCP((m) => sendMetric(m as unknown as Metric)); onCLS((m) => sendMetric(m as unknown as Metric)); onFCP((m) => sendMetric(m as unknown as Metric)); onINP((m) => sendMetric(m as unknown as Metric)); onTTFB((m) => sendMetric(m as unknown as Metric)); }