Skip to main content
Technology & EngineeringAnalytics Services335 lines

Vercel Analytics

"Vercel Analytics: Web Vitals, page views, custom events, audience insights, Next.js integration, Speed Insights"

Quick Summary21 lines
Vercel Analytics provides first-party, privacy-friendly analytics deeply integrated with the
Next.js deployment platform. It consists of two complementary products: Web Analytics for page
views and visitor metrics, and Speed Insights for Core Web Vitals performance monitoring. Since
data is collected as a first-party integration, there are no cookie banners or ad-blocker

## Key Points

- **Use the `beforeSend` callback** to strip sensitive query parameters and normalize dynamic
- **Keep custom event property values as strings.** The Vercel Analytics API accepts string
- **Add both `Analytics` and `SpeedInsights` components.** They serve different purposes:
- **Return `null` from `beforeSend`** to exclude internal pages, bot traffic, or admin sessions
- **Use `mode="development"`** during local development to see events in the console without
- **Leverage the Vercel dashboard filters** for deployment-specific analysis. You can compare
- **Track server-side events** for critical conversions like checkout completions to ensure they
- **Sending too many unique custom events.** Vercel Analytics has limits on event volume for
- **Including PII in event properties.** Never pass email addresses, user IDs, or other
- **Wrapping the Analytics component in conditional logic.** The component handles environment
- **Ignoring the `beforeSend` callback.** Without it, URLs with tokens, session IDs, or other
- **Using Vercel Analytics outside of Vercel.** The analytics service requires Vercel hosting
skilldb get analytics-services-skills/Vercel AnalyticsFull skill: 335 lines
Paste into your CLAUDE.md or agent config

Vercel Analytics

Core Philosophy

Vercel Analytics provides first-party, privacy-friendly analytics deeply integrated with the Next.js deployment platform. It consists of two complementary products: Web Analytics for page views and visitor metrics, and Speed Insights for Core Web Vitals performance monitoring. Since data is collected as a first-party integration, there are no cookie banners or ad-blocker issues. The philosophy is zero-config analytics that "just works" when deployed to Vercel, with optional custom event tracking for deeper product understanding. Speed Insights measures real user performance metrics (LCP, FID, CLS, TTFB, INP) so teams can monitor and improve the experience for actual visitors rather than relying on synthetic lab tests.

Setup

Installation

// Install both packages
// npm install @vercel/analytics @vercel/speed-insights

// app/layout.tsx — Add both components
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}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

Configuration Options

// app/layout.tsx — With configuration
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}
        <Analytics
          mode={process.env.NODE_ENV === "development" ? "development" : "production"}
          debug={false}
          beforeSend={(event) => {
            // Strip query parameters containing sensitive data
            const url = new URL(event.url);
            url.searchParams.delete("token");
            url.searchParams.delete("email");
            return { ...event, url: url.toString() };
          }}
        />
        <SpeedInsights
          route={undefined} // Auto-detected in Next.js
          debug={false}
        />
      </body>
    </html>
  );
}

Pages Router Setup

// pages/_app.tsx — For Pages Router projects
import type { AppProps } from "next/app";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Analytics />
      <SpeedInsights />
    </>
  );
}

Key Techniques

Custom Event Tracking

// lib/analytics.ts
import { track } from "@vercel/analytics";

// Track a signup event
export function trackSignup(method: "email" | "google" | "github"): void {
  track("signup", { method });
}

// Track a feature interaction
export function trackFeatureUsage(feature: string, variant?: string): void {
  track("feature_used", {
    feature,
    variant: variant ?? "default",
    path: window.location.pathname,
  });
}

// Track a purchase
export function trackPurchase(plan: string, amount: number, interval: string): void {
  track("purchase", {
    plan,
    amount: String(amount),
    interval,
  });
}

// Track search usage
export function trackSearch(query: string, resultCount: number): void {
  track("search", {
    query_length: String(query.length),
    result_count: String(resultCount),
    has_results: String(resultCount > 0),
  });
}

React Hook for Tracking

// hooks/useAnalytics.ts
"use client";

import { useCallback } from "react";
import { track } from "@vercel/analytics";
import { usePathname } from "next/navigation";

interface AnalyticsHook {
  trackEvent: (name: string, properties?: Record<string, string>) => void;
  trackClick: (element: string, properties?: Record<string, string>) => void;
  trackFormSubmit: (formName: string, success: boolean) => void;
}

export function useAnalytics(): AnalyticsHook {
  const pathname = usePathname();

  const trackEvent = useCallback(
    (name: string, properties?: Record<string, string>) => {
      track(name, { ...properties, path: pathname });
    },
    [pathname]
  );

  const trackClick = useCallback(
    (element: string, properties?: Record<string, string>) => {
      track("click", { element, ...properties, path: pathname });
    },
    [pathname]
  );

  const trackFormSubmit = useCallback(
    (formName: string, success: boolean) => {
      track("form_submit", {
        form: formName,
        success: String(success),
        path: pathname,
      });
    },
    [pathname]
  );

  return { trackEvent, trackClick, trackFormSubmit };
}

URL Sanitization with beforeSend

// lib/analytics-config.ts
import type { BeforeSendEvent } from "@vercel/analytics";

export function sanitizeAnalyticsEvent(event: BeforeSendEvent): BeforeSendEvent | null {
  const url = new URL(event.url);

  // Remove sensitive query parameters
  const sensitiveParams = ["token", "session", "email", "key", "secret"];
  sensitiveParams.forEach((param) => url.searchParams.delete(param));

  // Block tracking for internal/admin paths
  if (url.pathname.startsWith("/admin") || url.pathname.startsWith("/internal")) {
    return null; // Returning null drops the event entirely
  }

  // Normalize dynamic route segments for cleaner grouping
  const normalizedPath = url.pathname
    .replace(/\/users\/[^/]+/, "/users/[id]")
    .replace(/\/posts\/[^/]+/, "/posts/[slug]");
  url.pathname = normalizedPath;

  return { ...event, url: url.toString() };
}

Server-Side Event Tracking

// app/api/checkout/route.ts
import { track } from "@vercel/analytics/server";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest): Promise<NextResponse> {
  const body = await request.json();

  // Process the checkout logic
  const order = await processCheckout(body);

  // Track the conversion server-side
  await track("checkout_completed", {
    order_id: order.id,
    plan: order.plan,
    amount: String(order.amount),
  });

  return NextResponse.json({ orderId: order.id });
}

async function processCheckout(body: unknown): Promise<{ id: string; plan: string; amount: number }> {
  // Checkout processing logic
  return { id: "ord_123", plan: "pro", amount: 29 };
}

Speed Insights with Custom Reporting

// lib/vitals.ts
import type { Metric } from "web-vitals";

// Custom Web Vitals reporter for additional monitoring
export function reportVitals(metric: Metric): void {
  const body = {
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // "good" | "needs-improvement" | "poor"
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
    url: window.location.href,
  };

  // Send to your own monitoring endpoint alongside Vercel
  if (metric.rating === "poor") {
    fetch("/api/vitals-alert", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
  }
}

// pages/_app.tsx (Pages Router) — Export the reporter
export function reportWebVitals(metric: Metric): void {
  reportVitals(metric);
}

Environment-Aware Analytics Wrapper

// lib/track.ts
import { track as vercelTrack } from "@vercel/analytics";

type Properties = Record<string, string | number | boolean>;

export function analyticsTrack(event: string, properties?: Properties): void {
  const stringProps: Record<string, string> = {};
  if (properties) {
    for (const [key, value] of Object.entries(properties)) {
      stringProps[key] = String(value);
    }
  }

  if (process.env.NODE_ENV === "development") {
    console.log(`[Analytics] ${event}`, stringProps);
    return;
  }

  vercelTrack(event, stringProps);
}

Best Practices

  • Use the beforeSend callback to strip sensitive query parameters and normalize dynamic route segments before data reaches the Vercel dashboard.
  • Keep custom event property values as strings. The Vercel Analytics API accepts string values only. Convert numbers and booleans to strings explicitly.
  • Add both Analytics and SpeedInsights components. They serve different purposes: Analytics tracks visitor behavior, Speed Insights monitors performance. Use both.
  • Return null from beforeSend to exclude internal pages, bot traffic, or admin sessions from your analytics data entirely.
  • Use mode="development" during local development to see events in the console without sending them to the Vercel API.
  • Leverage the Vercel dashboard filters for deployment-specific analysis. You can compare performance between preview and production deployments automatically.
  • Track server-side events for critical conversions like checkout completions to ensure they are recorded even if the client navigates away before the event fires.

Anti-Patterns

  • Sending too many unique custom events. Vercel Analytics has limits on event volume for free and Pro plans. Focus on 10-20 meaningful events rather than tracking every click.
  • Including PII in event properties. Never pass email addresses, user IDs, or other personally identifiable information as event properties.
  • Wrapping the Analytics component in conditional logic. The component handles environment detection internally. Conditionally rendering it can cause missed page views.
  • Ignoring the beforeSend callback. Without it, URLs with tokens, session IDs, or other sensitive data in query strings are sent to the analytics dashboard in plain text.
  • Using Vercel Analytics outside of Vercel. The analytics service requires Vercel hosting for data collection. Self-hosted Next.js deployments cannot use it.
  • Not monitoring Speed Insights regressions. Performance metrics are collected continuously. Set up alerts or review the dashboard regularly to catch regressions introduced by new deployments before they affect too many users.
  • Passing numeric values directly. The track() function requires string property values. Passing numbers causes silent failures where events are sent without their properties.

Install this skill directly: skilldb add analytics-services-skills

Get CLI access →