Documentation
Monitoring
Error Tracking

Error Tracking

Monitor and fix errors in production before users report them.

Why Error Tracking?

  • Catch errors users don't report - Most users just leave
  • Stack traces in production - Source maps for readable errors
  • Context - User info, browser, actions before error
  • Alerts - Get notified of new issues instantly

Sentry Setup

npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs

Configuration

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
 
  // Performance monitoring
  tracesSampleRate: 1.0, // 100% in dev, lower in production
 
  // Session replay
  replaysSessionSampleRate: 0.1, // 10% of sessions
  replaysOnErrorSampleRate: 1.0, // 100% of sessions with errors
 
  // Environment
  environment: process.env.NODE_ENV,
 
  // Release tracking
  release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
 
  // Filter errors
  ignoreErrors: [
    'ResizeObserver loop limit exceeded',
    'Non-Error promise rejection captured',
  ],
 
  // Before sending
  beforeSend(event) {
    // Don't send in development
    if (process.env.NODE_ENV === 'development') {
      return null;
    }
    return event;
  },
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';
 
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  environment: process.env.NODE_ENV,
});

next.config.mjs

import { withSentryConfig } from '@sentry/nextjs';
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Your config
};
 
export default withSentryConfig(nextConfig, {
  // Upload source maps
  org: 'your-org',
  project: 'your-project',
  authToken: process.env.SENTRY_AUTH_TOKEN,
 
  // Suppress logs
  silent: true,
 
  // Hide source maps from users
  hideSourceMaps: true,
});

Error Boundaries with Sentry

// components/error-boundary.tsx
'use client';
 
import * as Sentry from '@sentry/nextjs';
import { Component, ReactNode } from 'react';
 
interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}
 
interface State {
  hasError: boolean;
  eventId?: string;
}
 
export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };
 
  static getDerivedStateFromError(): State {
    return { hasError: true };
  }
 
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    Sentry.withScope((scope) => {
      scope.setExtras({ componentStack: errorInfo.componentStack });
      const eventId = Sentry.captureException(error);
      this.setState({ eventId });
    });
  }
 
  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div className="error-container">
            <h2>Something went wrong</h2>
            <button
              onClick={() => Sentry.showReportDialog({ eventId: this.state.eventId })}
            >
              Report feedback
            </button>
          </div>
        )
      );
    }
 
    return this.props.children;
  }
}

Manual Error Capture

import * as Sentry from '@sentry/nextjs';
 
// Capture exception
try {
  await riskyOperation();
} catch (error) {
  Sentry.captureException(error);
}
 
// Capture message
Sentry.captureMessage('Something unexpected happened', 'warning');
 
// With context
Sentry.captureException(error, {
  tags: {
    section: 'checkout',
    feature: 'payment',
  },
  extra: {
    orderId: order.id,
    amount: order.total,
  },
});

User Context

// After user logs in
import * as Sentry from '@sentry/nextjs';
 
function setUserContext(user: User) {
  Sentry.setUser({
    id: user.id,
    email: user.email,
    username: user.username,
  });
}
 
// On logout
function clearUserContext() {
  Sentry.setUser(null);
}

Breadcrumbs

import * as Sentry from '@sentry/nextjs';
 
// Add custom breadcrumb
Sentry.addBreadcrumb({
  category: 'ui.click',
  message: 'User clicked checkout button',
  level: 'info',
});
 
// Navigation breadcrumb
Sentry.addBreadcrumb({
  category: 'navigation',
  message: 'Navigated to /checkout',
  level: 'info',
  data: {
    from: '/cart',
    to: '/checkout',
  },
});

Performance Monitoring

import * as Sentry from '@sentry/nextjs';
 
// Custom transaction
const transaction = Sentry.startTransaction({
  name: 'processOrder',
  op: 'task',
});
 
try {
  const span = transaction.startChild({
    op: 'db.query',
    description: 'SELECT * FROM orders',
  });
 
  await db.query('SELECT * FROM orders');
  span.finish();
 
  // More operations...
} finally {
  transaction.finish();
}

API Route Error Handling

// app/api/users/route.ts
import * as Sentry from '@sentry/nextjs';
 
export async function GET() {
  try {
    const users = await db.users.findMany();
    return Response.json(users);
  } catch (error) {
    Sentry.captureException(error, {
      tags: { endpoint: '/api/users' },
    });
    return Response.json({ error: 'Failed to fetch users' }, { status: 500 });
  }
}

Global Error Handler (Pages Router)

// pages/_error.tsx
import * as Sentry from '@sentry/nextjs';
import { NextPageContext } from 'next';
 
interface ErrorProps {
  statusCode?: number;
  hasGetInitialPropsRun?: boolean;
  err?: Error;
}
 
function Error({ statusCode, hasGetInitialPropsRun, err }: ErrorProps) {
  if (!hasGetInitialPropsRun && err) {
    Sentry.captureException(err);
  }
 
  return (
    <div>
      <h1>
        {statusCode
          ? `An error ${statusCode} occurred on server`
          : 'An error occurred on client'}
      </h1>
    </div>
  );
}
 
Error.getInitialProps = async (context: NextPageContext) => {
  const { res, err, asPath } = context;
 
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
 
  if (err) {
    Sentry.captureException(err);
    await Sentry.flush(2000);
  }
 
  return { statusCode, hasGetInitialPropsRun: true, err };
};
 
export default Error;

Release Tracking

# In CI/CD
export SENTRY_RELEASE=$(git rev-parse HEAD)
 
# Upload source maps
sentry-cli releases new $SENTRY_RELEASE
sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps .next/
sentry-cli releases finalize $SENTRY_RELEASE

Filtering Errors

Sentry.init({
  // Ignore specific errors
  ignoreErrors: [
    // Browser extensions
    'top.GLOBALS',
    // Network errors
    'Network request failed',
    'Failed to fetch',
    // Ad blockers
    'The play() request was interrupted',
  ],
 
  // Filter by URL
  denyUrls: [
    /extensions\//i,
    /^chrome:\/\//i,
    /^moz-extension:\/\//i,
  ],
 
  // Custom filtering
  beforeSend(event, hint) {
    const error = hint.originalException;
 
    // Ignore specific error types
    if (error instanceof AbortError) {
      return null;
    }
 
    // Modify event
    if (event.user) {
      delete event.user.email; // Remove PII
    }
 
    return event;
  },
});

Alternatives to Sentry

ServiceProsCons
SentryFeature-rich, open sourceCan be expensive
LogRocketSession replayHigher price
BugsnagSimple setupLess features
RollbarGood for teamsUI less polished
TrackJSLightweightFewer integrations

Best Practices

  1. Use source maps - Readable stack traces in production
  2. Set user context - Know who's affected
  3. Add breadcrumbs - Understand the user journey
  4. Filter noise - Ignore browser extension errors
  5. Sample in production - Don't capture 100% of transactions
  6. Monitor alerts - Set up Slack/email notifications