Next.js Analytics and Monitoring

Track page views, Web Vitals, and errors in Next.js with analytics and monitoring tools.

TL;DR

  1. Add Google Analytics to the root layout with the GoogleAnalytics component.
  2. Report Core Web Vitals from the browser with useReportWebVitals().
  3. Catch every server error in one place with onRequestError().

Page Analytics

    <GoogleAnalytics>

    Add Google Analytics to every page by rendering one component in the root layout.

    import {
      GoogleAnalytics,
    } from '@next/third-parties/google';
    
    // inside the root layout
    <GoogleAnalytics gaId="G-XXXXXXX" />
    sendGAEvent()

    Record a custom event, such as a purchase, from a Client Component.

    'use client';
    import {
      sendGAEvent,
    } from '@next/third-parties/google';
    
    <button
      onClick={() => sendGAEvent('event', 'purchase')}
    >
      Buy
    </button>
    <Analytics>

    Track page views on Vercel by adding its analytics component to the layout.

    import { Analytics } from '@vercel/analytics/next';
    
    // inside the body of the root layout
    <Analytics />

Web Vitals

    useReportWebVitals()

    Receive each Core Web Vitals metric as real visitors load your pages.

    'use client';
    import { useReportWebVitals } from 'next/web-vitals';
    
    export function WebVitals() {
      useReportWebVitals((metric) => {
        console.log(metric.name, metric.value);
      });
      return null;
    }
    sendBeacon()

    Send a metric to your server reliably, even as the page is closing.

    useReportWebVitals((metric) => {
      const body = JSON.stringify(metric);
      navigator.sendBeacon('/api/vitals', body);
    });
    Vitals Endpoint

    Receive the metrics in a route handler and store or log them.

    // app/api/vitals/route.ts
    export async function POST(request: Request) {
      const metric = await request.json();
      console.log(metric.name, metric.value);
      return new Response(null, { status: 204 });
    }

Server Monitoring

    register()

    Run setup code once when the server starts by exporting this function.

    // instrumentation.ts
    export async function register() {
      if (process.env.NEXT_RUNTIME === 'nodejs') {
        await import('./monitoring/server');
      }
    }
    onRequestError()

    Report every uncaught server error from a single function.

    export async function onRequestError(
      err: unknown,
      request: { path: string }
    ) {
      const body = JSON.stringify({
        message: String(err),
        path: request.path,
      });
      await fetch(LOG_URL, { method: 'POST', body });
    }
    registerOTel()

    Send traces to any OpenTelemetry backend with one call.

    import { registerOTel } from '@vercel/otel';
    
    export function register() {
      registerOTel('my-app');
    }

Client Monitoring and Sentry

    instrumentation-client.ts

    Run monitoring code in the browser before the app becomes interactive.

    // instrumentation-client.ts
    window.addEventListener('error', (event) => {
      sendError(event.error);
    });
    onRouterTransitionStart()

    Observe the start of every client-side navigation to time route changes.

    export function onRouterTransitionStart(
      url: string
    ) {
      performance.mark(`nav-start-${url}`);
    }
    @sentry/wizard

    Set up Sentry for client, server, and build with one command.

    npx @sentry/wizard@latest -i nextjs
    captureException()

    Report a handled error to Sentry and still let the caller see it.

    import * as Sentry from '@sentry/nextjs';
    
    try {
      await chargeCard(order);
    } catch (err) {
      Sentry.captureException(err);
      throw err;
    }

Tips

  1. Load analytics only in production by checking process.env.NODE_ENV, so local testing does not pollute your reports.
  2. Send the error.digest value with every client error report, so you can match it to the server log entry.

Warnings

  1. A public analytics ID is fine in a NEXT_PUBLIC_ variable, but secret keys for monitoring tools must stay on the server.
  2. The instrumentation.ts file runs in every server instance, so keep register() fast and free of heavy startup work.

In Practice

FAQ