We use cookies to enhance your experience on the site
CodeWorlds

Performance monitoring and error analytics

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.

The importance of monitoring in production applications

Monitoring is the process of continuously observing an application in order to:

  1. Early detection of problems - before they affect users
  2. Performance tracking - loading times, Core Web Vitals, throughput
  3. Analysis of user behavior - how users use the application
  4. Identifying bottlenecks - places requiring optimization
  5. Ensuring SLA - meeting agreed service levels

Types of monitoring

1. Performance Monitoring

  • Core Web Vitals (LCP, FID, CLS)
  • Page loading times
  • API response times
  • Resource consumption (CPU, memory)

2. Error Monitoring

  • JavaScript errors
  • API errors
  • Network errors
  • Stack traces

3. Infrastructure monitoring

  • Server availability
  • Resource consumption
  • Network metrics

4. User Experience Monitoring (UX)

  • Real User Monitoring (RUM)
  • Session recordings
  • Heat maps
  • User flows

Integration with Sentry for error monitoring

Sentry is one of the most popular tools for error monitoring in JavaScript and Next.js applications.

1. Installation and configuration of Sentry

1npm install @sentry/nextjs
2# or
3yarn add @sentry/nextjs

Create 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});

2. Configuring Next.js for Sentry

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);

3. Adding context to errors

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}

4. Using Sentry in components

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;

Monitoring Core Web Vitals

Core Web Vitals are key performance metrics that Google uses to evaluate the user experience:

  • LCP (Largest Contentful Paint) - loading time of the largest element
  • FID (First Input Delay) - response time for the first interaction
  • CLS (Cumulative Layout Shift) - layout instability

1. Web Vitals implementation in Next.js

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}

2. Component for monitoring Web Vitals

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}

3. API endpoint for collecting metrics

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}

Performance monitoring with New Relic

New Relic offers a comprehensive solution for performance monitoring of Next.js applications.

1. Configuring New Relic

1npm install newrelic
1// 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;

2. Custom instrumentation in New Relic

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}

Vercel Analytics - Next-generation monitoring in Quantum Metropolis

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.

Vercel Analytics vs Google Analytics

Before you decide on an analytics tool, it's worth understanding the key differences:

Vercel Analytics:

  • ✅ Integrated with Vercel Edge Network
  • ✅ Automatic tracking of Core Web Vitals
  • ✅ Privacy-first (no cookies, GDPR-compliant)
  • ✅ Zero configuration for basic metrics
  • ✅ Real-time data in the Vercel dashboard
  • ❌ Paid plan for a larger number of page views
  • ❌ Basic segmentation features

Google Analytics 4:

  • ✅ Advanced user segmentation
  • ✅ Funnel analysis and conversion tracking
  • ✅ Integration with Google Ads and other Google tools
  • ✅ Free plan for most use cases
  • ❌ Requires cookie consent (GDPR compliance)
  • ❌ Greater impact on page performance
  • ❌ More complicated configuration

Real User Monitoring (RUM) with Vercel Analytics

Real User Monitoring is a technique for collecting data about real user experiences, not synthetic tests. Vercel Analytics automatically tracks all key performance metrics.

Installing Vercel Analytics

1npm install @vercel/analytics
2# or
3yarn add @vercel/analytics
4# or
5pnpm add @vercel/analytics

Basic configuration

1// 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:

  • Page views
  • Unique visitors
  • Top pages
  • Top referrers
  • Devices and browsers
  • Geographic data

Core Web Vitals 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)

  • Measures the loading time of the largest content element
  • Target: < 2.5s
  • Affects: First impression, perceived performance

2. First Input Delay (FID) / Interaction to Next Paint (INP)

  • Measures responsiveness to user interactions
  • FID target: < 100ms
  • INP target: < 200ms
  • Affects: Interactivity, user experience

3. Cumulative Layout Shift (CLS)

  • Measures visual stability (unexpected layout shifts)
  • Target: < 0.1
  • Affects: Visual stability, user frustration

Access to Core Web Vitals in code

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}

Custom Events - tracking custom interactions

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}

Advanced custom events

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}

Vercel Speed Insights - real-time performance monitoring

Speed Insights is an Analytics extension that provides detailed information about application performance for real users.

Installation

1npm install @vercel/speed-insights

Configuration

1// 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}

What does Speed Insights track?

  1. Performance Score

    • Overall performance rating (0-100)
    • Based on Core Web Vitals and other metrics
  2. Field Data (RUM)

    • Real data from users
    • Segmented by device type, connection speed, geographic location
  3. Lab Data

    • Synthetic performance tests
    • Controlled conditions for consistency
  4. Opportunities

    • Specific optimization suggestions
    • Estimated time savings

Combination with other analytics tools

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}

Best practices for Vercel Analytics

1. Use environment-specific tracking

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}

2. Group custom events logically

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};

3. Monitor the conversion funnel

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}

Dashboard and reports

The Vercel Analytics Dashboard offers:

  1. Overview

    • Total page views
    • Unique visitors
    • Top pages
    • Top referrers
  2. Audiences

    • Device breakdown (mobile, desktop, tablet)
    • Browser usage
    • Operating systems
    • Geographic distribution
  3. Real-time

    • Live visitor count
    • Current popular pages
    • Recent events
  4. Web Vitals

    • LCP, FID/INP, CLS scores
    • Performance over time
    • Device-specific metrics
  5. Custom Events

    • Event counts
    • Event properties
    • Conversion tracking

Limitations and pricing

Free tier:

  • 2,500 events/month
  • Basic analytics
  • 1 member

Pro tier ($10/member/month):

  • 100,000 events/month
  • Advanced filtering
  • Unlimited members
  • Data export

Enterprise:

  • Custom event limits
  • Dedicated support
  • SLA guarantees

Integration with Vercel Edge Config

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}

Summary Vercel Analytics

In Quantum Metropolis 2150, where every millisecond matters, Vercel Analytics provides quantum precision in Next.js application monitoring:

Key advantages:

  • ✅ Zero-config setup for basic metrics
  • ✅ Automatic tracking of Core Web Vitals
  • ✅ Privacy-first approach (GDPR compliant)
  • ✅ Real-time data in the Vercel Dashboard
  • ✅ Integration with Vercel Edge Network
  • ✅ Custom events for business metrics

When to use Vercel Analytics:

  • Applications hosted on Vercel
  • You need privacy-first analytics
  • You want zero-config Web Vitals monitoring
  • Basic analytics is enough for your case
  • Budget allows for a paid plan for larger traffic

When to consider alternatives:

  • You need advanced user segmentation
  • You require funnel analysis and attribution modeling
  • You have very high traffic (> 1M page views/month)
  • You need integration with ad platforms
  • You prefer a self-hosted solution

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!

Custom monitoring with Prometheus and Grafana

For more advanced monitoring, you can configure your own stack with Prometheus and Grafana.

1. Configuring Prometheus metrics

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};

2. Middleware for HTTP tracking

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};

3. Endpoint for Prometheus metrics

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}

Alerting and notifications

An important aspect of monitoring is automatic notification of problems.

1. Integration with Slack

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}

2. Monitoring uptime

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}

Dashboard and data visualization

1. A simple dashboard in Next.js

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}

Monitoring best practices

1. Sampling strategies

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}

2. Monitoring performance optimization

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();

Summary

Performance monitoring and error analytics are key elements of any production Next.js application. A comprehensive monitoring approach includes:

  1. Error tracking - automatic detection and reporting of problems
  2. Performance monitoring - Core Web Vitals and other key metrics
  3. Alerting - immediate notifications about critical problems
  4. Dashboards - data visualization for decision-making
  5. User analytics - understanding how users use the application

When choosing monitoring tools, consider:

  • Sentry - for comprehensive error tracking
  • Vercel Analytics - for simple monitoring on Vercel
  • New Relic/DataDog - for advanced performance monitoring
  • Custom solutions - for specialized needs

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.

Go to CodeWorlds