Every application ships with bugs, that's the reality of software. What sets great teams apart isn't having zero errors, but the ability to catch them fast, understand them deeply, and fix them before users notice. Without proper monitoring, a crash in production is invisible until a user complains, and by then you have no stack trace, no context, and no idea how many people were affected. Sentry is the open-source error monitoring platform that closes this gap, turning silent failures into structured, actionable data.
What Sentry captures
Sentry goes far beyond catching uncaught exceptions. For every error that occurs in production, it records the full stack trace with source context, a precise timeline of the user's actions leading up to the crash, environment metadata like browser version and OS, and performance metrics across releases. The result isn't a vague crash report, it's a complete reproduction kit. You know what broke, in which environment, after which user action, and for how many people.
Configuring Sentry
For a Next.js application, Sentry runs through a client-side config that loads before your app code. The DSN (Data Source Name) identifies your project, while the sampling rates control how aggressively you capture data. Setting replaysOnErrorSampleRate to 1.0 means every session that ends in an error gets a full replay, which is invaluable for debugging hard-to-reproduce issues:
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
});Enriching error context
Raw errors are rarely enough to diagnose a problem. Sentry lets you attach structured context to every event, making each report immediately actionable rather than just a stack trace you have to decode in isolation.
User identification
Knowing which user hit an error tells you how many people are affected, whether it's isolated or widespread, and gives you a direct line to follow up. It also makes filtering and searching in the Sentry dashboard far more powerful:
import * as Sentry from "@sentry/nextjs";
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
// Clear on logout
Sentry.setUser(null);Custom tags and context
Business logic context is what transforms a cryptic stack trace into a directly actionable error. If a payment fails, you want to know the currency, the amount, and the user involved, not just the exception type. Tags are indexed and searchable in Sentry, so you can immediately filter all payment errors in EUR for example:
try {
await processPayment(amount, currency);
} catch (error) {
Sentry.captureException(error, {
tags: {
action: "payment",
currency: currency,
},
extra: {
amount: amount,
userId: user.id,
},
});
}Breadcrumbs
Breadcrumbs give you a chronological trail of events leading up to the error: HTTP requests, navigation events, UI interactions, and manual checkpoints you define in your code. Sentry captures most of these automatically, but you can also add manual ones at critical steps in your flows:
Sentry.addBreadcrumb({
category: "checkout",
message: "User clicked payment button",
level: "info",
});
// HTTP requests, console logs and navigation
// are all captured automatically by defaultSource maps
Without source maps, Sentry shows you minified stack traces pointing at bundle.js:1:84729, which tells you nothing useful. With source maps, you see the original TypeScript file, the exact function, and the exact line. Configuring them in Next.js is done through the Sentry webpack plugin, which uploads them automatically at build time:
import { withSentryConfig } from "@sentry/nextjs";
export default withSentryConfig(nextConfig, {
org: "your-org",
project: "your-project",
sourcemaps: {
disable: false,
},
hideSourceMaps: true,
});The hideSourceMaps option ensures the source maps are uploaded to Sentry but not served publicly to end users, which is exactly what you want in production.
Release tracking
Tagging each Sentry initialization with a release identifier lets you see which deployment introduced a bug. Sentry can then show you error rate trends per release, alert you when a new release spikes in errors, and let you mark issues as resolved in a specific version:
Sentry.init({
release: `my-app@${process.env.npm_package_version}`,
environment: process.env.NODE_ENV,
});Combined with source maps, this means every error in production maps back to a specific line in a specific commit. The feedback loop between shipping code and understanding its impact becomes very tight.
Filtering noise
A Sentry integration without filters quickly fills up with browser extension errors, third-party script failures and benign warnings that you can do nothing about. The beforeSend hook lets you inspect every event before it leaves the browser and drop anything that isn't worth your attention:
Sentry.init({
beforeSend(event) {
// Drop errors originating from browser extensions
if (event.exception?.values?.[0]?.stacktrace?.frames?.some(
frame => frame.filename?.includes('extensions')
)) {
return null;
}
return event;
},
ignoreErrors: [
'ResizeObserver loop limit exceeded',
'Non-Error promise rejection captured',
],
denyUrls: [
/extensions\//i,
/^chrome:\/\//i,
],
});Getting this right early saves you from alert fatigue and keeps the Sentry dashboard focused on errors that actually matter.
Performance monitoring
Sentry's performance monitoring goes beyond error tracking. It lets you trace entire transactions through your application, break them down into individual spans, and see exactly where time is being spent. Paired with release tracking, it lets you catch performance regressions the same way you catch bugs, before users start complaining:
const transaction = Sentry.startTransaction({
name: "checkout-process",
op: "transaction",
});
const validationSpan = transaction.startChild({
op: "validation",
description: "Validate cart items",
});
await validateCart();
validationSpan.finish();
const paymentSpan = transaction.startChild({
op: "http.client",
description: "Process payment",
});
await processPayment();
paymentSpan.finish();
transaction.finish();In the Sentry dashboard, this transaction shows up as a waterfall with timing for each span, making it easy to spot which operation is slow and whether it regressed between releases.
Conclusion
Sentry transforms error monitoring from a reactive chore into a proactive development practice. With source maps pointing at real code, release tracking tying bugs to deployments, enriched context making every error actionable, and performance data rounding out the picture, you stop guessing what broke in production and start knowing.