In production Next.js applications, performance monitoring and error tracking are key to maintaining high quality of delivered services. In this module, we will learn about tools and techniques that will allow us to effectively monitor the application, detect problems before users do, and optimize performance on an ongoing basis.
Monitoring is the process of continuously observing an application in order to:
Sentry is one of the most popular tools for error monitoring in JavaScript and Next.js applications.
1npm install @sentry/nextjs
2# or
3yarn add @sentry/nextjsCreate the configuration files:
1// sentry.client.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6
7 // Environment settings
8 environment: process.env.NODE_ENV,
9
10 // Sample rate for performance transactions
11 tracesSampleRate: 1.0,
12
13 // Enable Replay for user sessions
14 replaysSessionSampleRate: 0.1, // 10% of sessions
15 replaysOnErrorSampleRate: 1.0, // 100% of sessions with errors
16
17 // Integrations
18 integrations: [
19 new Sentry.Replay({
20 maskAllText: false,
21 blockAllMedia: false,
22 }),
23 ],
24
25 // Filtering errors
26 beforeSend(event) {
27 // Ignore certain types of errors
28 if (event.exception) {
29 const error = event.exception.values[0];
30 if (error.type === 'ChunkLoadError') {
31 return null; // Ignore chunk loading errors
32 }
33 }
34 return event;
35 },
36});1// sentry.server.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6 environment: process.env.NODE_ENV,
7 tracesSampleRate: 1.0,
8
9 // Additional options for the server
10 debug: false,
11
12 // Integrations specific to Node.js
13 integrations: [
14 new Sentry.Integrations.Http({ tracing: true }),
15 ],
16});1// sentry.edge.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6 environment: process.env.NODE_ENV,
7 tracesSampleRate: 1.0,
8 debug: false,
9});1// next.config.js
2const { withSentryConfig } = require('@sentry/nextjs');
3
4const nextConfig = {
5 // Your standard Next.js configuration
6};
7
8const sentryWebpackPluginOptions = {
9 // Additional configuration options for the Sentry webpack plugin
10 silent: true, // Silence Sentry logs during the build
11 org: process.env.SENTRY_ORG,
12 project: process.env.SENTRY_PROJECT,
13};
14
15module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions);1// lib/sentry.ts
2import * as Sentry from '@sentry/nextjs';
3
4// Function to add user context
5export function setUserContext(user: { id: string; email: string; name?: string }) {
6 Sentry.setUser({
7 id: user.id,
8 email: user.email,
9 username: user.name,
10 });
11}
12
13// Function to add tags
14export function addTags(tags: Record<string, string>) {
15 Sentry.setTags(tags);
16}
17
18// Function for manual error reporting
19export function captureException(error: Error, context?: Record<string, any>) {
20 Sentry.withScope((scope) => {
21 if (context) {
22 scope.setContext('additional_info', context);
23 }
24 Sentry.captureException(error);
25 });
26}
27
28// Function for performance tracking
29export function startTransaction(name: string, op: string) {
30 return Sentry.startTransaction({ name, op });
31}1// components/ErrorBoundary.tsx
2'use client';
3
4import React from 'react';
5import * as Sentry from '@sentry/nextjs';
6
7interface ErrorBoundaryState {
8 hasError: boolean;
9 eventId?: string;
10}
11
12class ErrorBoundary extends React.Component<
13 React.PropsWithChildren<{}>,
14 ErrorBoundaryState
15> {
16 constructor(props: React.PropsWithChildren<{}>) {
17 super(props);
18 this.state = { hasError: false };
19 }
20
21 static getDerivedStateFromError(_: Error): ErrorBoundaryState {
22 return { hasError: true };
23 }
24
25 componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
26 const eventId = Sentry.captureException(error, {
27 contexts: {
28 react: {
29 componentStack: errorInfo.componentStack,
30 },
31 },
32 });
33
34 this.setState({ eventId });
35 }
36
37 render() {
38 if (this.state.hasError) {
39 return (
40 <div className="error-fallback">
41 <h2>Oops! Something went wrong</h2>
42 <p>An unexpected error occurred. Our team has been notified.</p>
43 <details style={{ whiteSpace: 'pre-wrap' }}>
44 Error ID: {this.state.eventId}
45 </details>
46 <button onClick={() => window.location.reload()}>
47 Refresh the page
48 </button>
49 </div>
50 );
51 }
52
53 return this.props.children;
54 }
55}
56
57export default ErrorBoundary;Core Web Vitals are key performance metrics that Google uses to evaluate the user experience:
1// lib/web-vitals.ts
2import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
3import * as Sentry from '@sentry/nextjs';
4
5interface WebVitalMetric {
6 name: string;
7 value: number;
8 id: string;
9 delta: number;
10}
11
12// Function for reporting metrics
13function sendToAnalytics(metric: WebVitalMetric) {
14 // Send to Google Analytics
15 if (typeof window !== 'undefined' && window.gtag) {
16 window.gtag('event', metric.name, {
17 event_category: 'Web Vitals',
18 event_label: metric.id,
19 value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
20 non_interaction: true,
21 });
22 }
23
24 // Send to Sentry
25 Sentry.addBreadcrumb({
26 category: 'web-vital',
27 message: `${metric.name}: ${metric.value}`,
28 level: 'info',
29 data: {
30 name: metric.name,
31 value: metric.value,
32 id: metric.id,
33 delta: metric.delta,
34 },
35 });
36
37 // In case of poor results, send as event to Sentry
38 const thresholds = {
39 LCP: 2500,
40 FID: 100,
41 CLS: 0.1,
42 };
43
44 if (metric.value > thresholds[metric.name as keyof typeof thresholds]) {
45 Sentry.captureMessage(`Poor ${metric.name}: ${metric.value}`, 'warning');
46 }
47}
48
49// Initialize Web Vitals measurements
50export function initWebVitals() {
51 getCLS(sendToAnalytics);
52 getFID(sendToAnalytics);
53 getFCP(sendToAnalytics);
54 getLCP(sendToAnalytics);
55 getTTFB(sendToAnalytics);
56}1// components/WebVitalsReporter.tsx
2'use client';
3
4import { useEffect } from 'react';
5import { useReportWebVitals } from 'next/web-vitals';
6
7export default function WebVitalsReporter() {
8 useReportWebVitals((metric) => {
9 // Send to various analytics systems
10 switch (metric.name) {
11 case 'FCP':
12 case 'LCP':
13 case 'CLS':
14 case 'FID':
15 case 'TTFB':
16 // Send to your own API
17 fetch('/api/analytics/web-vitals', {
18 method: 'POST',
19 headers: { 'Content-Type': 'application/json' },
20 body: JSON.stringify(metric),
21 }).catch(console.error);
22 break;
23 default:
24 break;
25 }
26 });
27
28 return null;
29}1// app/api/analytics/web-vitals/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { prisma } from '@/lib/prisma'; // Let's assume you use Prisma
4
5export async function POST(request: NextRequest) {
6 try {
7 const metric = await request.json();
8
9 // Basic validation
10 if (!metric.name || !metric.value) {
11 return NextResponse.json({ error: 'Invalid metric data' }, { status: 400 });
12 }
13
14 // Save to the database
15 await prisma.webVitalMetric.create({
16 data: {
17 name: metric.name,
18 value: metric.value,
19 id: metric.id,
20 delta: metric.delta,
21 url: request.headers.get('referer') || '',
22 userAgent: request.headers.get('user-agent') || '',
23 timestamp: new Date(),
24 },
25 });
26
27 return NextResponse.json({ success: true });
28 } catch (error) {
29 console.error('Error saving web vital metric:', error);
30 return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
31 }
32}New Relic offers a comprehensive solution for performance monitoring of Next.js applications.
1npm install newrelic1// newrelic.js (in the project root)
2'use strict';
3
4exports.config = {
5 app_name: ['Next.js App'],
6 license_key: process.env.NEW_RELIC_LICENSE_KEY,
7
8 // Logging configuration
9 logging: {
10 level: 'info',
11 },
12
13 // Enable distributed tracing
14 distributed_tracing: {
15 enabled: true,
16 },
17
18 // Browser monitoring
19 browser_monitoring: {
20 enable: true,
21 },
22
23 // Configuration specific to Next.js
24 allow_all_headers: true,
25 attributes: {
26 exclude: [
27 'request.headers.cookie',
28 'request.headers.authorization',
29 'request.headers.x-*',
30 ],
31 },
32};1// next.config.js
2if (process.env.NODE_ENV === 'production') {
3 require('./newrelic');
4}
5
6const nextConfig = {
7 // Your Next.js configuration
8};
9
10module.exports = nextConfig;1// lib/monitoring.ts
2import newrelic from 'newrelic';
3
4// Function to track custom metrics
5export function recordCustomMetric(name: string, value: number) {
6 if (process.env.NODE_ENV === 'production') {
7 newrelic.recordMetric(name, value);
8 }
9}
10
11// Function to track business events
12export function recordCustomEvent(eventType: string, attributes: Record<string, any>) {
13 if (process.env.NODE_ENV === 'production') {
14 newrelic.recordCustomEvent(eventType, attributes);
15 }
16}
17
18// Wrapper for API calls with automatic tracking
19export async function instrumentedApiCall<T>(
20 name: string,
21 apiCall: () => Promise<T>
22): Promise<T> {
23 const startTime = Date.now();
24
25 try {
26 const result = await apiCall();
27
28 // Save success metrics
29 recordCustomMetric(`Custom/API/${name}/Duration`, Date.now() - startTime);
30 recordCustomEvent('APICall', {
31 name,
32 status: 'success',
33 duration: Date.now() - startTime,
34 });
35
36 return result;
37 } catch (error) {
38 // Save error metrics
39 recordCustomMetric(`Custom/API/${name}/Error`, 1);
40 recordCustomEvent('APICall', {
41 name,
42 status: 'error',
43 duration: Date.now() - startTime,
44 error: error.message,
45 });
46
47 throw error;
48 }
49}In Quantum Metropolis 2150, every user interaction is monitored by advanced analytics systems. Vercel Analytics is a native monitoring solution for Next.js applications, designed with user privacy and GDPR compliance in mind. Unlike Google Analytics, Vercel Analytics runs directly in the Vercel Edge Network, which ensures minimal latency and accurate measurements.
Before you decide on an analytics tool, it's worth understanding the key differences:
Vercel Analytics:
Google Analytics 4:
Real User Monitoring is a technique for collecting data about real user experiences, not synthetic tests. Vercel Analytics automatically tracks all key performance metrics.
1npm install @vercel/analytics
2# or
3yarn add @vercel/analytics
4# or
5pnpm add @vercel/analytics1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3
4export default function RootLayout({
5 children
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <html lang="pl">
11 <body>
12 {children}
13 <Analytics />
14 </body>
15 </html>
16 );
17}That's all! Vercel Analytics will automatically start tracking:
Core Web Vitals are key web performance metrics that Google uses as ranking signals. Vercel Analytics automatically tracks all three metrics:
1. Largest Contentful Paint (LCP)
2. First Input Delay (FID) / Interaction to Next Paint (INP)
3. Cumulative Layout Shift (CLS)
You can programmatically access Web Vitals metrics:
1// app/web-vitals.tsx
2'use client';
3
4import { useReportWebVitals } from 'next/web-vitals';
5
6export function WebVitals() {
7 useReportWebVitals((metric) => {
8 console.log(metric);
9
10 // You can send metrics to your own endpoint
11 if (metric.label === 'web-vital') {
12 fetch('/api/analytics/web-vitals', {
13 method: 'POST',
14 body: JSON.stringify(metric),
15 headers: {
16 'Content-Type': 'application/json'
17 }
18 });
19 }
20 });
21
22 return null;
23}1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { WebVitals } from './web-vitals';
4
5export default function RootLayout({ children }: { children: React.ReactNode }) {
6 return (
7 <html lang="pl">
8 <body>
9 {children}
10 <Analytics />
11 <WebVitals />
12 </body>
13 </html>
14 );
15}Vercel Analytics allows tracking custom events for specific user actions:
1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function NewsletterForm() {
6 const handleSubscribe = async (email: string) => {
7 try {
8 await subscribeToNewsletter(email);
9
10 // Track successful subscription
11 track('Newsletter Subscribe', {
12 email_domain: email.split('@')[1],
13 source: 'homepage'
14 });
15
16 } catch (error) {
17 // Track error
18 track('Newsletter Subscribe Error', {
19 error: error.message
20 });
21 }
22 };
23
24 return (
25 <form onSubmit={(e) => {
26 e.preventDefault();
27 const email = e.target.email.value;
28 handleSubscribe(email);
29 }}>
30 <input type="email" name="email" required />
31 <button type="submit">Subscribe</button>
32 </form>
33 );
34}1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function ProductPage({ product }: { product: Product }) {
6 // Track product view
7 useEffect(() => {
8 track('Product View', {
9 product_id: product.id,
10 product_name: product.name,
11 product_category: product.category,
12 product_price: product.price
13 });
14 }, [product]);
15
16 // Track add to cart
17 const handleAddToCart = () => {
18 track('Add to Cart', {
19 product_id: product.id,
20 quantity: 1,
21 value: product.price
22 });
23
24 addToCart(product);
25 };
26
27 // Track checkout start
28 const handleCheckout = () => {
29 track('Begin Checkout', {
30 value: cartTotal,
31 items: cartItems.length
32 });
33
34 router.push('/checkout');
35 };
36
37 return (
38 <div>
39 <h1>{product.name}</h1>
40 <p>{product.price} PLN</p>
41 <button onClick={handleAddToCart}>Add to cart</button>
42 <button onClick={handleCheckout}>Buy now</button>
43 </div>
44 );
45}Speed Insights is an Analytics extension that provides detailed information about application performance for real users.
1npm install @vercel/speed-insights1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { SpeedInsights } from '@vercel/speed-insights/next';
4
5export default function RootLayout({ children }: { children: React.ReactNode }) {
6 return (
7 <html lang="pl">
8 <body>
9 {children}
10 <Analytics />
11 <SpeedInsights />
12 </body>
13 </html>
14 );
15}Performance Score
Field Data (RUM)
Lab Data
Opportunities
You can combine Vercel Analytics with other tools for a fuller picture:
1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { SpeedInsights } from '@vercel/speed-insights/next';
4import { GoogleAnalytics } from '@next/third-parties/google';
5
6export default function RootLayout({ children }: { children: React.ReactNode }) {
7 return (
8 <html lang="pl">
9 <body>
10 {children}
11
12 {/* Vercel Analytics - for Core Web Vitals and basic metrics */}
13 <Analytics />
14 <SpeedInsights />
15
16 {/* Google Analytics - for advanced segmentation */}
17 <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />
18 </body>
19 </html>
20 );
21}1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3
4export default function RootLayout({ children }: { children: React.ReactNode }) {
5 return (
6 <html lang="pl">
7 <body>
8 {children}
9
10 {/* Only in production */}
11 {process.env.NODE_ENV === 'production' && <Analytics />}
12 </body>
13 </html>
14 );
15}1// lib/analytics.ts
2import { track } from '@vercel/analytics';
3
4export const analytics = {
5 // User events
6 user: {
7 signup: (method: string) => track('User Signup', { method }),
8 login: (method: string) => track('User Login', { method }),
9 logout: () => track('User Logout')
10 },
11
12 // E-commerce events
13 ecommerce: {
14 viewProduct: (productId: string) =>
15 track('Product View', { product_id: productId }),
16 addToCart: (productId: string, value: number) =>
17 track('Add to Cart', { product_id: productId, value }),
18 purchase: (orderId: string, value: number) =>
19 track('Purchase', { order_id: orderId, value })
20 },
21
22 // Engagement events
23 engagement: {
24 shareContent: (contentType: string, method: string) =>
25 track('Share', { content_type: contentType, method }),
26 search: (query: string) =>
27 track('Search', { search_term: query }),
28 playVideo: (videoId: string) =>
29 track('Video Play', { video_id: videoId })
30 }
31};1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function CheckoutFlow() {
6 // Step 1: View cart
7 useEffect(() => {
8 track('Checkout Step 1 - Cart', {
9 items: cart.length,
10 value: cartTotal
11 });
12 }, []);
13
14 // Step 2: Shipping info
15 const handleShippingSubmit = () => {
16 track('Checkout Step 2 - Shipping', {
17 value: cartTotal
18 });
19 };
20
21 // Step 3: Payment
22 const handlePaymentSubmit = () => {
23 track('Checkout Step 3 - Payment', {
24 value: cartTotal,
25 payment_method: selectedPaymentMethod
26 });
27 };
28
29 // Step 4: Confirmation
30 const handleOrderComplete = (orderId: string) => {
31 track('Purchase Complete', {
32 order_id: orderId,
33 value: cartTotal,
34 items: cart.length
35 });
36 };
37
38 return <CheckoutSteps />;
39}The Vercel Analytics Dashboard offers:
Overview
Audiences
Real-time
Web Vitals
Custom Events
Free tier:
Pro tier ($10/member/month):
Enterprise:
You can dynamically control feature flags based on analytics:
1// lib/feature-flags.ts
2import { get } from '@vercel/edge-config';
3import { track } from '@vercel/analytics';
4
5export async function checkFeatureFlag(flagName: string): Promise<boolean> {
6 const isEnabled = await get(flagName);
7
8 // Track feature flag usage
9 if (isEnabled) {
10 track('Feature Flag Enabled', { flag: flagName });
11 }
12
13 return isEnabled || false;
14}In Quantum Metropolis 2150, where every millisecond matters, Vercel Analytics provides quantum precision in Next.js application monitoring:
Key advantages:
When to use Vercel Analytics:
When to consider alternatives:
Vercel Analytics is the ideal solution for most Next.js applications, offering an excellent balance between simplicity, performance, and functionality. Combined with Speed Insights, it creates a complete real-time application performance monitoring system!
For more advanced monitoring, you can configure your own stack with Prometheus and Grafana.
1// lib/metrics.ts
2import client from 'prom-client';
3
4// Metrics registry
5const register = new client.Registry();
6
7// HTTP metrics
8const httpRequestDuration = new client.Histogram({
9 name: 'http_request_duration_seconds',
10 help: 'Duration of HTTP requests in seconds',
11 labelNames: ['method', 'route', 'status_code'],
12 buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
13});
14
15const httpRequestTotal = new client.Counter({
16 name: 'http_requests_total',
17 help: 'Total number of HTTP requests',
18 labelNames: ['method', 'route', 'status_code'],
19});
20
21// Business metrics
22const userRegistrations = new client.Counter({
23 name: 'user_registrations_total',
24 help: 'Total number of user registrations',
25});
26
27const ordersTotal = new client.Counter({
28 name: 'orders_total',
29 help: 'Total number of orders',
30 labelNames: ['status'],
31});
32
33// Register metrics
34register.registerMetric(httpRequestDuration);
35register.registerMetric(httpRequestTotal);
36register.registerMetric(userRegistrations);
37register.registerMetric(ordersTotal);
38
39// Add default Node.js metrics
40client.collectDefaultMetrics({ register });
41
42export {
43 register,
44 httpRequestDuration,
45 httpRequestTotal,
46 userRegistrations,
47 orderTotal
48};1// middleware.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { httpRequestDuration, httpRequestTotal } from '@/lib/metrics';
4
5export function middleware(request: NextRequest) {
6 const start = Date.now();
7
8 // Continue processing the request
9 const response = NextResponse.next();
10
11 // Save metrics after processing the request
12 response.headers.set('x-response-time', (Date.now() - start).toString());
13
14 // Async metric saving (does not block the response)
15 setImmediate(() => {
16 const duration = (Date.now() - start) / 1000;
17 const labels = {
18 method: request.method,
19 route: request.nextUrl.pathname,
20 status_code: response.status.toString(),
21 };
22
23 httpRequestDuration.observe(labels, duration);
24 httpRequestTotal.inc(labels);
25 });
26
27 return response;
28}
29
30export const config = {
31 matcher: [
32 '/((?!api|_next/static|_next/image|favicon.ico).*)',
33 ],
34};1// app/api/metrics/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { register } from '@/lib/metrics';
4
5export async function GET(request: NextRequest) {
6 const metrics = await register.metrics();
7
8 return new NextResponse(metrics, {
9 headers: {
10 'Content-Type': register.contentType,
11 },
12 });
13}An important aspect of monitoring is automatic notification of problems.
1// lib/alerts.ts
2import { IncomingWebhook } from '@slack/webhook';
3
4const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);
5
6export async function sendSlackAlert(message: string, severity: 'info' | 'warning' | 'error') {
7 const colors = {
8 info: '#36a64f',
9 warning: '#ffcc00',
10 error: '#ff0000',
11 };
12
13 try {
14 await webhook.send({
15 attachments: [
16 {
17 color: colors[severity],
18 fields: [
19 {
20 title: 'Alert',
21 value: message,
22 short: false,
23 },
24 {
25 title: 'Time',
26 value: new Date().toISOString(),
27 short: true,
28 },
29 {
30 title: 'Environment',
31 value: process.env.NODE_ENV,
32 short: true,
33 },
34 ],
35 },
36 ],
37 });
38 } catch (error) {
39 console.error('Failed to send Slack alert:', error);
40 }
41}
42
43// Function for checking thresholds and sending alerts
44export function checkPerformanceThresholds(metrics: {
45 lcp: number;
46 fid: number;
47 cls: number;
48}) {
49 if (metrics.lcp > 2500) {
50 sendSlackAlert(`High LCP detected: ${metrics.lcp}ms`, 'warning');
51 }
52
53 if (metrics.fid > 100) {
54 sendSlackAlert(`High FID detected: ${metrics.fid}ms`, 'warning');
55 }
56
57 if (metrics.cls > 0.1) {
58 sendSlackAlert(`High CLS detected: ${metrics.cls}`, 'warning');
59 }
60}1// lib/uptime-monitor.ts
2import { sendSlackAlert } from './alerts';
3
4interface HealthCheckResult {
5 service: string;
6 status: 'healthy' | 'unhealthy';
7 responseTime: number;
8 timestamp: Date;
9}
10
11export async function checkServiceHealth(url: string, service: string): Promise<HealthCheckResult> {
12 const start = Date.now();
13
14 try {
15 const response = await fetch(url, {
16 method: 'GET',
17 timeout: 10000, // 10 second timeout
18 });
19
20 const responseTime = Date.now() - start;
21
22 if (response.ok) {
23 return {
24 service,
25 status: 'healthy',
26 responseTime,
27 timestamp: new Date(),
28 };
29 } else {
30 await sendSlackAlert(`Service ${service} returned ${response.status}`, 'error');
31 return {
32 service,
33 status: 'unhealthy',
34 responseTime,
35 timestamp: new Date(),
36 };
37 }
38 } catch (error) {
39 await sendSlackAlert(`Service ${service} is unreachable: ${error.message}`, 'error');
40 return {
41 service,
42 status: 'unhealthy',
43 responseTime: Date.now() - start,
44 timestamp: new Date(),
45 };
46 }
47}
48
49// Function for running health checks
50export async function runHealthChecks() {
51 const services = [
52 { name: 'Main App', url: 'https://your-app.com/api/health' },
53 { name: 'Database', url: 'https://your-app.com/api/health/db' },
54 { name: 'External API', url: 'https://api.external-service.com/health' },
55 ];
56
57 const results = await Promise.allSettled(
58 services.map(service => checkServiceHealth(service.url, service.name))
59 );
60
61 results.forEach((result, index) => {
62 if (result.status === 'fulfilled') {
63 console.log(`Health check for ${services[index].name}:`, result.value);
64 } else {
65 console.error(`Health check failed for ${services[index].name}:`, result.reason);
66 }
67 });
68}1// app/admin/monitoring/page.tsx
2import { getWebVitalMetrics, getErrorMetrics } from '@/lib/analytics';
3import { LineChart, BarChart } from '@/components/Charts';
4
5export default async function MonitoringDashboard() {
6 const webVitals = await getWebVitalMetrics();
7 const errors = await getErrorMetrics();
8
9 return (
10 <div className="p-6">
11 <h1 className="text-3xl font-bold mb-8">Monitoring Dashboard</h1>
12
13 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
14 {/* Core Web Vitals */}
15 <div className="bg-white p-6 rounded-lg shadow">
16 <h2 className="text-xl font-semibold mb-4">Core Web Vitals</h2>
17 <LineChart
18 data={webVitals}
19 categories={['lcp', 'fid', 'cls']}
20 xKey="timestamp"
21 />
22 </div>
23
24 {/* Error Rate */}
25 <div className="bg-white p-6 rounded-lg shadow">
26 <h2 className="text-xl font-semibold mb-4">Error Rate</h2>
27 <BarChart
28 data={errors}
29 category="count"
30 xKey="hour"
31 />
32 </div>
33 </div>
34
35 {/* Real-time metrics */}
36 <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
37 <MetricCard title="Active Users" value="1,234" change="+5.2%" />
38 <MetricCard title="Response Time" value="156ms" change="-2.1%" />
39 <MetricCard title="Error Rate" value="0.01%" change="-50%" />
40 <MetricCard title="Uptime" value="99.9%" change="+0.1%" />
41 </div>
42 </div>
43 );
44}
45
46function MetricCard({ title, value, change }: {
47 title: string;
48 value: string;
49 change: string;
50}) {
51 const isPositive = change.startsWith('+');
52 const isNegative = change.startsWith('-');
53
54 return (
55 <div className="bg-white p-4 rounded-lg shadow">
56 <h3 className="text-sm font-medium text-gray-500">{title}</h3>
57 <div className="mt-2 flex items-baseline">
58 <p className="text-2xl font-semibold text-gray-900">{value}</p>
59 <p className={`ml-2 text-sm font-medium ${
60 isPositive ? 'text-green-600' : isNegative ? 'text-red-600' : 'text-gray-500'
61 }`}>
62 {change}
63 </p>
64 </div>
65 </div>
66 );
67}1// lib/sampling.ts
2
3// Function for determining whether an event should be tracked
4export function shouldSample(sampleRate: number): boolean {
5 return Math.random() < sampleRate;
6}
7
8// Adaptive sampling - more samples for errors
9export function adaptiveSample(isError: boolean): boolean {
10 return isError ? shouldSample(1.0) : shouldSample(0.1);
11}
12
13// Sampling based on user tier
14export function userTierSample(userTier: 'free' | 'premium' | 'enterprise'): boolean {
15 const rates = {
16 free: 0.05, // 5% for free users
17 premium: 0.25, // 25% for premium
18 enterprise: 1.0, // 100% for enterprise
19 };
20
21 return shouldSample(rates[userTier]);
22}1// lib/performance-monitoring.ts
2
3// Buffer for metrics - collect and send in batches
4class MetricsBuffer {
5 private buffer: any[] = [];
6 private maxSize = 100;
7 private flushInterval = 30000; // 30 seconds
8
9 constructor() {
10 // Automatic buffer flushing
11 setInterval(() => this.flush(), this.flushInterval);
12 }
13
14 add(metric: any) {
15 this.buffer.push(metric);
16
17 if (this.buffer.length >= this.maxSize) {
18 this.flush();
19 }
20 }
21
22 private async flush() {
23 if (this.buffer.length === 0) return;
24
25 const metricsToSend = [...this.buffer];
26 this.buffer = [];
27
28 try {
29 await fetch('/api/metrics/batch', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ metrics: metricsToSend }),
33 });
34 } catch (error) {
35 console.error('Failed to send metrics batch:', error);
36 // Return metrics to the buffer in case of error
37 this.buffer.unshift(...metricsToSend);
38 }
39 }
40}
41
42export const metricsBuffer = new MetricsBuffer();Performance monitoring and error analytics are key elements of any production Next.js application. A comprehensive monitoring approach includes:
When choosing monitoring tools, consider:
Remember the balance between monitoring depth and impact on application performance. Use sampling, buffering, and optimize queries so that monitoring does not negatively affect user experience.