You can't optimize what you don't measure! Track Core Web Vitals to measure real user experience, integrate analytics to understand user behavior, monitor errors to catch issues early, and use performance data to guide optimization. Master monitoring and build data-driven, high-performance applications that delight users!
Core Web Vitals
The Three Core Web Vitals
1. LCP (Largest Contentful Paint)
Measures: Loading performance
What it tracks: Time until largest content element is visible
Good: < 2.5 seconds
Needs improvement: 2.5 - 4 seconds
Poor: > 4 seconds
2. INP (Interaction to Next Paint)
Measures: Interactivity and responsiveness
What it tracks: Time from user interaction to visual response
Good: < 200ms
Needs improvement: 200 - 500ms
Poor: > 500ms
Note: INP replaced FID in March 2024
3. CLS (Cumulative Layout Shift)
Measures: Visual stability
What it tracks: Unexpected layout shifts during page load
Good: < 0.1
Needs improvement: 0.1 - 0.25
Poor: > 0.25
Why Core Web Vitals Matter
- SEO Impact: Google uses Web Vitals as ranking signals
- User Experience: Direct correlation with user satisfaction
- Conversion Rates: Better vitals = higher conversions
- Competitive Advantage: Stand out with better performance
Tracking Web Vitals in Next.js
useReportWebVitals Hook
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals((metric) => {
// Log to console (development)
if (process.env.NODE_ENV === 'development') {
console.log(metric);
}
// Send to analytics (production)
if (process.env.NODE_ENV === 'production') {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
});
// Use sendBeacon for reliability
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/analytics', body);
} else {
fetch('/api/analytics', {
body,
method: 'POST',
keepalive: true,
});
}
}
});
return null;
}
// Add to layout:
// <WebVitals />
// ✅ Tracks all Core Web Vitals
// ✅ Captures real user metrics
// ✅ Sends to analytics endpointWeb Vitals API Route
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const metric = await request.json();
// Log metric
console.log('Web Vital:', metric);
// Send to analytics service (Google Analytics, custom service, etc.)
await sendToAnalytics(metric);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Analytics error:', error);
return NextResponse.json(
{ success: false },
{ status: 500 }
);
}
}
async function sendToAnalytics(metric: any) {
// Example: Send to Google Analytics
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_rating: metric.rating,
metric_delta: metric.delta,
});
}
// Or send to custom analytics service
// await fetch('https://analytics.example.com/metrics', {
// method: 'POST',
// body: JSON.stringify(metric),
// });
}
// ✅ Receives Web Vitals data
// ✅ Processes metrics
// ✅ Forwards to analytics serviceVercel Analytics Integration
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
{/* Vercel Analytics */}
<Analytics />
{/* Vercel Speed Insights */}
<SpeedInsights />
</body>
</html>
);
}
// Install packages:
// npm install @vercel/analytics @vercel/speed-insights
// ✅ Automatic Web Vitals tracking
// ✅ Real user monitoring
// ✅ Performance insights dashboard
// ✅ No configuration neededGoogle Analytics Integration
Google Analytics 4 Setup
'use client';
import Script from 'next/script';
export function GoogleAnalytics({ GA_MEASUREMENT_ID }: { GA_MEASUREMENT_ID: string }) {
return (
<>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_MEASUREMENT_ID}', {
page_path: window.location.pathname,
});
`}
</Script>
</>
);
}
// Usage in layout:
// <GoogleAnalytics GA_MEASUREMENT_ID={process.env.NEXT_PUBLIC_GA_ID!} />
// ✅ Next.js Script component for optimization
// ✅ afterInteractive strategy (doesn't block page)
// ✅ Page view trackingTrack Custom Events
// Declare gtag function
declare global {
interface Window {
gtag: (
command: string,
...args: any[]
) => void;
}
}
// Track page views
export const trackPageView = (url: string) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', process.env.NEXT_PUBLIC_GA_ID!, {
page_path: url,
});
}
};
// Track custom events
export const trackEvent = (
action: string,
category: string,
label?: string,
value?: number
) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
}
};
// Track conversions
export const trackConversion = (conversionId: string) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'conversion', {
send_to: conversionId,
});
}
};
// Usage:
// trackEvent('click', 'Button', 'Sign Up');
// trackConversion('AW-CONVERSION-ID');
// ✅ Type-safe event tracking
// ✅ Reusable utilities
// ✅ Custom event supportTrack Navigation in App Router
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { trackPageView } from '@/lib/analytics';
export function PageViewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (pathname) {
const url = pathname + (searchParams?.toString() ? `?${searchParams}` : '');
trackPageView(url);
}
}, [pathname, searchParams]);
return null;
}
// Add to layout:
// <PageViewTracker />
// ✅ Tracks all page navigations
// ✅ Works with App Router
// ✅ Includes search paramsError Monitoring
Sentry Integration
# Install Sentry
npm install @sentry/nextjs
# Initialize Sentry
npx @sentry/wizard@latest -i nextjs
# This creates:
# - sentry.client.config.js
# - sentry.server.config.js
# - sentry.edge.config.js
# - next.config.js updates
# ✅ Automatic error tracking
# ✅ Performance monitoring
# ✅ Release trackingSentry Configuration
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Performance monitoring
tracesSampleRate: 1.0,
// Session replay
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
// Environment
environment: process.env.NODE_ENV,
// Integrations
integrations: [
new Sentry.BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true,
}),
],
});
// ✅ Client-side error tracking
// ✅ Performance traces
// ✅ Session replaysCustom Error Handling
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log error to Sentry
Sentry.captureException(error, {
tags: {
error_boundary: 'app',
},
contexts: {
error: {
digest: error.digest,
},
},
});
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
// ✅ Captures errors in error boundary
// ✅ Sends to Sentry with context
// ✅ User can retryManual Error Logging
import * as Sentry from '@sentry/nextjs';
// Log errors
export function logError(error: Error, context?: Record<string, any>) {
console.error('Error:', error);
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(error, {
extra: context,
});
}
}
// Log messages
export function logMessage(
message: string,
level: 'info' | 'warning' | 'error' = 'info'
) {
console.log(`[${level.toUpperCase()}]`, message);
if (process.env.NODE_ENV === 'production') {
Sentry.captureMessage(message, level);
}
}
// Set user context
export function setUser(user: { id: string; email: string }) {
if (process.env.NODE_ENV === 'production') {
Sentry.setUser(user);
}
}
// Usage:
// logError(new Error('Something failed'), { userId: '123' });
// logMessage('User signed up', 'info');
// setUser({ id: '123', email: 'user@example.com' });
// ✅ Centralized error logging
// ✅ User context tracking
// ✅ Production-only in SentryAnalytics and Monitoring Structure
Files for performance tracking
Select a file or folder to see details
Custom Performance Metrics
Performance API
// Measure custom timings
export function measureTiming(name: string, startMark: string, endMark: string) {
try {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name)[0];
console.log(`${name}: ${measure.duration.toFixed(2)}ms`);
// Send to analytics
sendMetric(name, measure.duration);
return measure.duration;
} catch (error) {
console.error('Performance measurement error:', error);
return 0;
}
}
// Mark start of operation
export function markStart(name: string) {
performance.mark(`${name}-start`);
}
// Mark end of operation
export function markEnd(name: string) {
performance.mark(`${name}-end`);
return measureTiming(name, `${name}-start`, `${name}-end`);
}
// Usage:
// markStart('data-fetch');
// await fetchData();
// markEnd('data-fetch'); // Logs: "data-fetch: 234.56ms"
// ✅ Custom timing measurements
// ✅ Track specific operations
// ✅ Send to analyticsComponent Performance
'use client';
import { useEffect } from 'react';
import { markStart, markEnd } from '@/lib/performance';
export function ExpensiveComponent() {
useEffect(() => {
markStart('expensive-component-render');
// Component mounted
return () => {
// Component unmounting
markEnd('expensive-component-render');
};
}, []);
return <div>Expensive Component</div>;
}
// ✅ Track component render time
// ✅ Identify slow components
// ✅ Optimize based on dataAPI Performance Tracking
async function fetchWithMetrics<T>(
url: string,
options?: RequestInit
): Promise<T> {
const startTime = performance.now();
try {
const response = await fetch(url, options);
const data = await response.json();
const duration = performance.now() - startTime;
// Log API call metrics
console.log(`API call to ${url}: ${duration.toFixed(2)}ms`);
// Send to analytics
sendMetric('api-call', duration, {
url,
status: response.status,
});
return data;
} catch (error) {
const duration = performance.now() - startTime;
// Log failed API call
console.error(`API call failed to ${url}: ${duration.toFixed(2)}ms`, error);
sendMetric('api-call-error', duration, {
url,
error: (error as Error).message,
});
throw error;
}
}
// ✅ Track all API calls
// ✅ Measure response times
// ✅ Log errors with timingLighthouse CI
Lighthouse CI Configuration
module.exports = {
ci: {
collect: {
// Run Lighthouse on these URLs
url: [
'http://localhost:3000/',
'http://localhost:3000/about',
'http://localhost:3000/blog',
],
// Number of runs per URL
numberOfRuns: 3,
},
assert: {
// Assertions for performance budgets
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
},
},
upload: {
// Upload to Lighthouse CI server (optional)
target: 'temporary-public-storage',
},
},
};
// ✅ Automated performance testing
// ✅ Performance budgets
// ✅ CI/CD integrationGitHub Actions with Lighthouse CI
name: Lighthouse CI
on:
pull_request:
branches: [main]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Start server
run: npm start &
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
# ✅ Automatic performance checks on PRs
# ✅ Prevents performance regressions
# ✅ Comments on PRs with resultsOptimization Strategies Based on Metrics
Improving LCP (Loading)
Strategies:
- Optimize images (use Next.js Image component)
- Preload critical assets
- Use CDN for static assets
- Implement proper caching
- Minimize server response time
- Remove render-blocking resources
Improving INP (Interactivity)
Strategies:
- Code split large JavaScript bundles
- Use dynamic imports for heavy components
- Defer non-critical JavaScript
- Optimize event handlers
- Use Web Workers for heavy computations
- Reduce main thread work
Improving CLS (Visual Stability)
Strategies:
- Set width and height on images and videos
- Reserve space for dynamic content
- Avoid inserting content above existing content
- Use CSS transform animations instead of layout properties
- Preload fonts to avoid FOUT
- Set explicit dimensions on ad slots
Production Monitoring Checklist
✅ Essential Monitoring
- Web Vitals: Track LCP, INP, CLS
- Analytics: Google Analytics or alternative
- Error Tracking: Sentry or similar service
- Performance: Custom metrics for critical operations
- Uptime: Monitor server availability
- Alerts: Set up notifications for critical issues
- Logs: Centralized logging for debugging
- Budgets: Performance budgets in CI/CD
Key Takeaways
- Core Web Vitals - LCP, INP, CLS measure user experience
- useReportWebVitals - track Web Vitals in Next.js
- Analytics integration - Google Analytics, Vercel Analytics
- Error monitoring - Sentry for production error tracking
- Custom metrics - Performance API for specific operations
- Lighthouse CI - automated performance testing
- Real user monitoring - measure actual user experiences
- Data-driven optimization - optimize based on metrics
🎉 Congratulations!
You've completed the complete Next.js 15 tutorial series! You've mastered:
What You've Learned:
- ✅ Routing Fundamentals - App Router, dynamic routes, layouts
- ✅ Server & Client Components - composition patterns, when to use each
- ✅ Data Fetching - SSG, SSR, ISR, caching strategies
- ✅ Navigation & Links - prefetching, programmatic navigation
- ✅ Styling - CSS Modules, Tailwind, CSS-in-JS
- ✅ Images & Media - optimization, responsive images
- ✅ Forms & Mutations - Server Actions, validation, optimistic updates
- ✅ Metadata & SEO - dynamic metadata, Open Graph, sitemaps
- ✅ API Routes - REST APIs, error handling, authentication
- ✅ Middleware - request interception, authentication
- ✅ Environment Variables - configuration, security
- ✅ Streaming & Suspense - progressive rendering, loading states
- ✅ Error Handling - not-found, error boundaries
- ✅ Rendering Strategies - static, dynamic, ISR
- ✅ Static Generation - generateStaticParams
- ✅ Build Optimization - bundle analysis, code splitting
- ✅ Deployment - Vercel, AWS, Docker, self-hosting
- ✅ Monitoring & Analytics - Web Vitals, error tracking
You're now ready to build production-ready, high-performance, and scalable Next.js applications! 🚀
🎓 Next Steps
Continue your Next.js journey by building real projects! Apply what you've learned: build a blog with MDX, an e-commerce site, a SaaS application, or contribute to open-source Next.js projects. The best way to solidify your knowledge is to build!