Sentry captures unhandled errors, performance bottlenecks, and user session replays. Sentry.init({ dsn, environment, release, tracesSampleRate }) initializes the SDK. Sentry.captureException(error) sends a caught error with full stack trace. Sentry.withScope(scope => { scope.setExtra("key", value); Sentry.captureException(e) }) enriches errors with contextual data. Sentry.setUser({ id, email }) associates errors with users. Sentry.addBreadcrumb({ message, category, level }) adds trail markers. startSpan({ name, op }) creates performance spans. tracesSampleRate controls the percentage of transactions sampled. beforeSend(event) filters or augments events before sending. withSentryConfig wraps the Next.js config for source map uploads. Session replay records user interactions for visual debugging. Claude Code generates Sentry initialization, error boundaries, performance instrumentation, source map configuration, and filtering patterns for production error monitoring.
CLAUDE.md for Sentry
## Sentry Stack
- Version: @sentry/nextjs >= 8.0, @sentry/node >= 8.0
- Init: Sentry.init({ dsn, environment, release, tracesSampleRate: 0.1 })
- Next.js: sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts
- Capture: Sentry.captureException(error, { extra: { context } })
- User: Sentry.setUser({ id, email, username }) — after auth
- Span: Sentry.startSpan({ name: "db.query", op: "db" }, async span => { ... })
- Filter: beforeSend: (event) => event.level === "info" ? null : event
- Source maps: withSentryConfig(nextConfig, { org, project, authToken })
Sentry Initialization
// sentry.client.config.ts — browser initialization
import * as Sentry from "@sentry/nextjs"
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_ENVIRONMENT ?? "development",
// Performance — sample 10% of transactions in prod
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
// Session replay — capture 10% of sessions, 100% on error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: false,
maskAllInputs: true, // Privacy: always mask form inputs
}),
],
// Filter noisy/irrelevant errors
beforeSend(event, hint) {
// Drop network errors from ad blockers
if (event.exception?.values?.[0]?.type === "TypeError") {
const msg = event.exception.values[0].value ?? ""
if (msg.includes("Failed to fetch") || msg.includes("Load failed")) {
return null // Don't send
}
}
// Drop errors from known browser extensions
const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? []
if (frames.some(f => f.filename?.includes("chrome-extension"))) {
return null
}
return event
},
// Add user-defined tags to all events
initialScope: {
tags: {
app_version: process.env.NEXT_PUBLIC_APP_VERSION ?? "unknown",
},
},
})
// sentry.server.config.ts — Node.js server initialization
import * as Sentry from "@sentry/nextjs"
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.ENVIRONMENT ?? "development",
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
// Profile 10% of sampled transactions
profilesSampleRate: 0.1,
integrations: [
// Auto-instrument Prisma queries (or your ORM)
Sentry.prismaIntegration(),
],
beforeSend(event) {
// Scrub sensitive values from server errors
if (event.request?.data) {
const data = event.request.data as Record<string, unknown>
delete data.password
delete data.creditCard
delete data.ssn
}
return event
},
})
Error Capture Patterns
// lib/error-reporting.ts — typed error capture utilities
import * as Sentry from "@sentry/nextjs"
interface ErrorContext {
userId?: string
orderId?: string
operation?: string
extra?: Record<string, unknown>
}
// Capture with rich context
export function captureError(error: unknown, context: ErrorContext = {}) {
Sentry.withScope(scope => {
if (context.userId) scope.setUser({ id: context.userId })
if (context.orderId) scope.setTag("orderId", context.orderId)
if (context.operation) scope.setTag("operation", context.operation)
if (context.extra) Object.entries(context.extra).forEach(([k, v]) => scope.setExtra(k, v))
scope.setLevel("error")
Sentry.captureException(error)
})
}
// API route error wrapper
export function withErrorReporting<T>(
fn: () => Promise<T>,
context: ErrorContext
): Promise<T> {
return fn().catch(error => {
captureError(error, context)
throw error // Re-throw after reporting
})
}
// Add navigation breadcrumb (call on route changes)
export function addNavigationBreadcrumb(from: string, to: string) {
Sentry.addBreadcrumb({
message: `Navigate from ${from} to ${to}`,
category: "navigation",
level: "info",
data: { from, to },
})
}
// Record user actions for replay context
export function trackUserAction(action: string, data?: Record<string, unknown>) {
Sentry.addBreadcrumb({
message: action,
category: "user",
level: "info",
data,
})
}
React Error Boundary
// components/ErrorBoundary.tsx — Sentry React error boundary
"use client"
import * as Sentry from "@sentry/nextjs"
import { Component, type ReactNode, type ErrorInfo } from "react"
interface Props {
children: ReactNode
fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode)
onError?: (error: Error, errorInfo: ErrorInfo) => void
}
interface State {
error: Error | null
eventId: string | null
}
export class SentryErrorBoundary extends Component<Props, State> {
state: State = { error: null, eventId: null }
static getDerivedStateFromError(error: Error): Partial<State> {
return { error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
const eventId = Sentry.captureException(error, {
extra: {
componentStack: errorInfo.componentStack,
},
})
this.setState({ eventId })
this.props.onError?.(error, errorInfo)
}
reset = () => this.setState({ error: null, eventId: null })
render() {
if (this.state.error) {
if (typeof this.props.fallback === "function") {
return this.props.fallback(this.state.error, this.reset)
}
if (this.props.fallback) return this.props.fallback
return (
<div className="flex flex-col items-center justify-center p-8 text-center">
<h2 className="text-lg font-semibold mb-2">Something went wrong</h2>
<p className="text-muted-foreground text-sm mb-4">
Error ID: {this.state.eventId}
</p>
<button
onClick={this.reset}
className="px-4 py-2 bg-primary text-white rounded text-sm"
>
Try again
</button>
<button
onClick={() => Sentry.showReportDialog({ eventId: this.state.eventId! })}
className="mt-2 px-4 py-2 border rounded text-sm"
>
Report this issue
</button>
</div>
)
}
return this.props.children
}
}
Performance Tracing
// lib/tracing.ts — custom spans for performance
import * as Sentry from "@sentry/nextjs"
// Trace a database query
export async function traceDbQuery<T>(
queryName: string,
query: () => Promise<T>
): Promise<T> {
return Sentry.startSpan(
{ name: queryName, op: "db.query", attributes: { "db.system": "postgresql" } },
() => query()
)
}
// Trace an external API call
export async function traceExternalCall<T>(
service: string,
operation: string,
fn: () => Promise<T>
): Promise<T> {
return Sentry.startSpan(
{
name: `${service}.${operation}`,
op: "http.client",
attributes: { "peer.service": service },
},
() => fn()
)
}
// Example: traced order service
export async function createOrderTraced(input: { customerId: string }) {
return Sentry.startSpan({ name: "order.create", op: "function" }, async span => {
span?.setAttribute("customerId", input.customerId)
const [customer, inventory] = await Promise.all([
traceDbQuery("customer.fetch", () => fetchCustomer(input.customerId)),
traceDbQuery("inventory.check", () => checkInventory()),
])
const order = await traceDbQuery("order.insert", () => insertOrder(customer, inventory))
await traceExternalCall("stripe", "charge.create", () =>
chargeStripe(order.totalCents)
)
return order
})
}
async function fetchCustomer(id: string) { return { id, email: "" } }
async function checkInventory() { return true }
async function insertOrder(_c: unknown, _i: unknown) { return { id: "ord_1", totalCents: 1000 } }
async function chargeStripe(_cents: number) { return { id: "ch_1" } }
Next.js Configuration
// next.config.js — source maps + Sentry webpack integration
import { withSentryConfig } from "@sentry/nextjs"
const nextConfig = {
// your next.js config
}
export default withSentryConfig(nextConfig, {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Upload source maps to Sentry (removes them from public bundle)
silent: true,
hideSourceMaps: true,
disableLogger: true,
// Automatically tree-shake Sentry logger statements
automaticVercelMonitors: true,
})
For the Datadog alternative when APM, infrastructure metrics, log management, and error tracking are all needed in a single unified platform — Datadog integrates errors with host-level metrics and distributed tracing across microservices in a way that Sentry’s standalone error tracking does not, see the observability platform comparison. For the OpenTelemetry alternative when vendor-agnostic tracing exportable to multiple backends (Jaeger, Zipkin, Datadog, Honeycomb) is needed — OpenTelemetry provides the instrumentation standard while Sentry acts as one of many possible exporters, see the OpenTelemetry guide for distributed tracing pipelines. The Claude Skills 360 bundle includes Sentry skill sets covering error capture, performance tracing, and source map configuration. Start with the free tier to try error monitoring integration generation.