Skip to main content
Back to Blog
5 min read

The Sentry setup that catches useful errors (not noise)

My production Sentry config for Next.js: filtering out hydration spam, grouping by cause, and alerting on things that actually matter. With real config examples.

performancenext.js

A fresh Sentry install on a Next.js app produces about 400 events per day on a site with 2K daily visitors. 390 of them are noise — hydration mismatches, browser extension errors, ResizeObserver loops, and prefetch 404s. The 10 that matter get buried.

I've tuned Sentry configs across 6 client projects. Here's the setup I now copy into every new build.

Step 1: Filter the noise at ingest

Sentry charges by event volume. More importantly, noise trains your brain to ignore alerts. Kill the junk before it reaches your dashboard.

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1, // 10% of transactions — enough for perf data
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 0.5, // replay 50% of errors

  beforeSend(event) {
    const message = event.exception?.values?.[0]?.value ?? ''

    // Hydration mismatches — React's problem, not yours
    if (message.includes('Hydration failed')) return null
    if (message.includes('Text content does not match')) return null

    // Browser extensions injecting errors
    if (message.includes('chrome-extension://')) return null
    if (message.includes('moz-extension://')) return null

    // ResizeObserver — benign, fired by every browser
    if (message.includes('ResizeObserver loop')) return null

    // Network errors from prefetch/preload — user navigated away
    if (
      message.includes('Failed to fetch') &&
      event.request?.url?.includes('_next/data')
    ) {
      return null
    }

    return event
  },

  ignoreErrors: [
    // Common browser noise
    'AbortError',
    'Network request failed',
    'Load failed',
    'cancelled',
    // Bots and crawlers
    'Non-Error promise rejection captured',
  ],

  denyUrls: [
    // Browser extensions
    /extensions\//i,
    /^chrome:\/\//i,
    /^moz-extension:\/\//i,
    // Google Translate injects broken scripts
    /translate\.googleapis\.com/,
  ],
})

This alone drops event volume by ~85% on a typical Next.js site.

Step 2: Server-side filtering

The server config is cleaner — no browser extension noise. But there's still junk to filter:

// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.2,

  beforeSend(event) {
    const message = event.exception?.values?.[0]?.value ?? ''

    // Next.js throws NOT_FOUND as an error — it's not
    if (message.includes('NEXT_NOT_FOUND')) return null

    // Next.js redirect() throws — by design
    if (message.includes('NEXT_REDIRECT')) return null

    return event
  },
})

NEXT_NOT_FOUND and NEXT_REDIRECT are Next.js using exceptions for control flow. They're not bugs in your code. Filtering them removes ~30% of server-side events.

Step 3: Custom fingerprinting for useful grouping

By default, Sentry groups errors by stack trace. This means the same bug produces different issues if it happens in different user flows. Fix this with custom fingerprints:

// sentry.client.config.ts — add to beforeSend
beforeSend(event) {
  // ... noise filters above ...

  // Group API errors by endpoint, not stack trace
  const frames = event.exception?.values?.[0]?.stacktrace?.frames;
  if (frames?.some(f => f.filename?.includes('/api/'))) {
    const url = event.request?.url ?? 'unknown';
    const path = new URL(url).pathname;
    event.fingerprint = ['api-error', path];
  }

  // Group form validation errors together
  if (event.tags?.component === 'ContactForm') {
    event.fingerprint = ['form-error', 'contact'];
  }

  return event;
}

Custom fingerprints cut my issue count by ~40% — the same bug grouped correctly instead of splitting across 12 issues.

Step 4: Meaningful context on every error

The difference between a useful error and a useless one is context. Add it globally:

// In your root layout or app wrapper
Sentry.setContext('app', {
  locale: currentLocale,
  route: pathname,
  deployment: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7),
})

// For authenticated areas
Sentry.setUser({
  id: user.id,
  // Never send PII — no email, no name
})

When an error fires, I immediately see: which locale, which route, which deployment, and which user (by ID, not by name). That's usually enough to reproduce in 2 minutes instead of 20.

Step 5: Alerts that wake you for the right reasons

Default Sentry alerts fire on every new issue. After a deploy, you get 15 alerts for minor regressions. The signal-to-noise ratio collapses.

My alert rules:

Alert 1: Spike detection — fire when error count exceeds 5× the rolling average for any 15-minute window. This catches real outages, not one-off errors.

Alert 2: New issue in payments — any new error in files matching */api/payment* or */stripe*. Payments are the one area where a single error matters.

Alert 3: Unhandled rejection rate — fire when unhandled rejections exceed 1% of sessions in a 1-hour window. This catches cascading failures.

Everything else goes to the weekly digest email. I review it Monday mornings — 5 minutes to scan, triage anything worth fixing.

Step 6: Source maps (the part everyone forgets)

Without source maps, your Sentry stack traces show minified variable names. Useless. Next.js + Vercel makes this easy but not automatic:

// next.config.js
const { withSentryConfig } = require('@sentry/nextjs')

module.exports = withSentryConfig(nextConfig, {
  org: 'your-org',
  project: 'your-project',
  silent: true,
  hideSourceMaps: true, // Don't expose in browser devtools
  widenClientFileUpload: true, // Upload all client chunks
})

hideSourceMaps: true is critical — it uploads source maps to Sentry during build but doesn't serve them publicly. Your users can't reverse-engineer your code, but Sentry shows you clean stack traces.

The result

Before tuning: ~400 events/day, 3 alerts/day, 95% noise. I stopped checking Sentry.

After tuning: ~40 events/day, ~2 alerts/week, each one actionable. I actually check Sentry again.

The config took 2 hours to set up across client + server + alerts. I've copied it into every project since. The ROI isn't in catching more errors — it's in catching fewer, better ones.


Want your Sentry to tell you things worth knowing instead of crying wolf? Let's talk — I set this up for every client project and can audit your current config in a single session.