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 nextjsConfiguration
// 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_RELEASEFiltering 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
| Service | Pros | Cons |
|---|---|---|
| Sentry | Feature-rich, open source | Can be expensive |
| LogRocket | Session replay | Higher price |
| Bugsnag | Simple setup | Less features |
| Rollbar | Good for teams | UI less polished |
| TrackJS | Lightweight | Fewer integrations |
Best Practices
- Use source maps - Readable stack traces in production
- Set user context - Know who's affected
- Add breadcrumbs - Understand the user journey
- Filter noise - Ignore browser extension errors
- Sample in production - Don't capture 100% of transactions
- Monitor alerts - Set up Slack/email notifications