All files / src/lib web-vitals.ts

97.11% Statements 101/104
82.35% Branches 14/17
100% Functions 4/4
97.11% Lines 101/104

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 1051x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 34x 34x 34x 34x 34x 34x 34x 34x 34x 9x 9x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 1x 4x 1x 3x 2x 2x 4x 4x 4x       4x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 12x 12x 2x 2x 2x 2x  
/**
 * Web Vitals Tracking
 *
 * Tracks Core Web Vitals metrics (LCP, FID, CLS) and reports them
 * for performance monitoring and optimization.
 */
 
import { logger } from "@/lib/logging";
 
// Metric thresholds (in ms for LCP/FID, unitless for CLS)
const THRESHOLDS = {
  LCP: { good: 2500, needsImprovement: 4000 },
  FID: { good: 100, needsImprovement: 300 },
  CLS: { good: 0.1, needsImprovement: 0.25 },
  FCP: { good: 1800, needsImprovement: 3000 },
  TTFB: { good: 800, needsImprovement: 1800 },
  INP: { good: 200, needsImprovement: 500 }};
 
export type MetricName = keyof typeof THRESHOLDS;
 
export interface WebVitalMetric {
  name: MetricName;
  value: number;
  rating: "good" | "needs-improvement" | "poor";
  delta: number;
  id: string;
}
 
/**
 * Get rating for a metric value
 */
export function getRating(
  name: MetricName,
  value: number
): "good" | "needs-improvement" | "poor" {
  const threshold = THRESHOLDS[name];
  if (!threshold) return "good";
 
  if (value <= threshold.good) return "good";
  if (value <= threshold.needsImprovement) return "needs-improvement";
  return "poor";
}
 
/**
 * Report Web Vitals metric
 * In production, this would send to an analytics service
 */
export function reportWebVital(metric: WebVitalMetric): void {
  const { name, value, rating, delta, id } = metric;
 
  // Log warning for poor metrics
  if (rating === "poor") {
    logger.warn(`Poor ${name}: ${value.toFixed(2)} (delta: ${delta.toFixed(2)})`, { category: "WEB_VITALS", id });
  } else if (rating === "needs-improvement") {
    logger.info(`${name} needs improvement: ${value.toFixed(2)}`, { category: "WEB_VITALS", id });
  } else {
    logger.debug(`Good ${name}: ${value.toFixed(2)}`, { category: "WEB_VITALS", id });
  }
 
  // In production, send to analytics service
  if (process.env.NODE_ENV === "production" && typeof window !== "undefined") {
    // Example: Send to analytics endpoint
    // sendToAnalytics({ name, value, rating, delta, id, url: window.location.href });
  }
}
 
/**
 * Web Vitals handler for Next.js
 * Use with: export { reportWebVitals } from '@/lib/web-vitals';
 */
export function onReportWebVitals(metric: {
  id: string;
  name: string;
  value: number;
  delta: number;
}): void {
  const name = metric.name as MetricName;
 
  if (!(name in THRESHOLDS)) {
    return;
  }
 
  const rating = getRating(name, metric.value);
 
  reportWebVital({
    name,
    value: metric.value,
    rating,
    delta: metric.delta,
    id: metric.id});
}
 
/**
 * Get Web Vitals summary for diagnostics
 */
export function getWebVitalsSummary(): Record<MetricName, { threshold: typeof THRESHOLDS[MetricName] }> {
  return Object.entries(THRESHOLDS).reduce(
    (acc, [name, threshold]) => {
      acc[name as MetricName] = { threshold };
      return acc;
    },
    {} as Record<MetricName, { threshold: typeof THRESHOLDS[MetricName] }>
  );
}