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:
@@ -6,6 +6,7 @@ import { locales, localeMetadata, seoKeywords, type Locale } from '@/i18n/config
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Header, Footer, GlobalBackground } from '@/components/layout';
|
import { Header, Footer, GlobalBackground } from '@/components/layout';
|
||||||
import { PersonJsonLd, ProfessionalServiceJsonLd, OrganizationJsonLd, WebSiteJsonLd } from '@/components/seo';
|
import { PersonJsonLd, ProfessionalServiceJsonLd, OrganizationJsonLd, WebSiteJsonLd } from '@/components/seo';
|
||||||
|
import { WebVitals } from '@/components/analytics';
|
||||||
import '../globals.css';
|
import '../globals.css';
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
@@ -123,6 +124,8 @@ export default async function LocaleLayout({ children, params }: Props) {
|
|||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
|
{/* Core Web Vitals monitoring */}
|
||||||
|
<WebVitals />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { WebVitals } from './WebVitals';
|
||||||
Reference in New Issue
Block a user