auto-claude: subtask-2-3 - Add web-vitals reporting to layout for Core Web Vitals monitoring

- Create WebVitals client component in src/components/analytics/
- Track LCP, INP, CLS, FCP, and TTFB metrics
- Log metrics to console in development mode
- Send metrics to Google Analytics when available
- Add WebVitals component to root layout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 00:19:42 +01:00
co-authored by Claude Opus 4.5
parent a6d0e28990
commit ab669a4dc9
3 changed files with 70 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
'use client';
import { useEffect } from 'react';
import { onCLS, onINP, onLCP, onFCP, onTTFB, type Metric } from 'web-vitals';
type WebVitalsReportHandler = (metric: Metric) => void;
interface WebVitalsProps {
/**
* Optional custom report handler. If not provided, metrics are logged to console in development.
*/
onReport?: WebVitalsReportHandler;
}
/**
* Reports Core Web Vitals metrics.
*
* Metrics tracked:
* - LCP (Largest Contentful Paint): measures loading performance
* - INP (Interaction to Next Paint): measures interactivity
* - CLS (Cumulative Layout Shift): measures visual stability
* - FCP (First Contentful Paint): measures time to first content
* - TTFB (Time to First Byte): measures server response time
*/
export function WebVitals({ onReport }: WebVitalsProps) {
useEffect(() => {
const handleMetric: WebVitalsReportHandler = (metric) => {
if (onReport) {
onReport(metric);
} else if (process.env.NODE_ENV === 'development') {
// Log metrics in development for debugging
const { name, value, rating, id } = metric;
// eslint-disable-next-line no-console
console.log(`[Web Vitals] ${name}: ${value.toFixed(2)} (${rating}) [${id}]`);
}
// Send to Google Analytics if available
if (typeof window !== 'undefined' && 'gtag' in window) {
const gtag = window.gtag as (
command: string,
eventName: string,
params: Record<string, unknown>
) => void;
gtag('event', metric.name, {
event_category: 'Web Vitals',
event_label: metric.id,
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
non_interaction: true,
});
}
};
// Register all Core Web Vitals observers
// Each function returns an unsubscribe function, but web-vitals v5 doesn't require cleanup
// as it uses PerformanceObserver internally which is garbage collected
onCLS(handleMetric);
onINP(handleMetric);
onLCP(handleMetric);
onFCP(handleMetric);
onTTFB(handleMetric);
}, [onReport]);
// This component doesn't render anything
return null;
}
+1
View File
@@ -0,0 +1 @@
export { WebVitals } from './WebVitals';