Skip to content

Fix: Sentry Not Working — Errors Not Captured, Source Maps Missing, or Performance Traces Empty

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

Quick Answer

How to fix Sentry issues — SDK initialization, source map uploads, error boundaries, performance monitoring, Next.js integration, release tracking, and alert configuration.

The Problem

Errors throw in your app but nothing appears in Sentry:

throw new Error('Something went wrong');
// Error fires but Sentry dashboard shows zero events

Or errors appear but stack traces show minified code:

TypeError: Cannot read properties of undefined
  at e.render (main.abc123.js:1:45678)
  at t.processChild (main.abc123.js:1:12345)

Or performance monitoring is enabled but no traces appear:

Sentry.init({
  tracesSampleRate: 1.0,
});
// Zero transactions in the Performance tab

Why This Happens

Sentry captures errors and performance data through its SDK, which must be correctly initialized and configured:

  • Sentry.init() must run before any errors occur — the SDK intercepts uncaught exceptions and unhandled promise rejections. If initialization happens after the first error, that error is lost. In Next.js, the SDK must be initialized in multiple places (client, server, edge).
  • Source maps must be uploaded to Sentry — production builds are minified. Without source maps, Sentry can’t map minified code back to your original source. Source maps must be uploaded during the build process, not at runtime.
  • The DSN must be correct — the Data Source Name tells the SDK where to send events. A wrong DSN silently drops all events. The DSN is project-specific and found in Sentry’s project settings.
  • Sample rates control what’s capturedtracesSampleRate: 0 means no performance traces are collected. In production, you typically sample a percentage (e.g., 0.1 for 10%) to reduce costs.

The pathological case for Sentry is silent failure of the monitoring system itself. Most SRE incidents start with “I’m seeing errors in production” — but the worst Sentry failures start with “we haven’t seen any errors in two weeks, that’s weird.” When your error tracker stops capturing events, the blast radius is your entire observability stack. You’re flying blind across every service that depends on Sentry for alerts, including paging, SLO tracking, and release health.

Sentry SDK initialization is timing-sensitive in ways that are easy to miss. Sentry.init() installs global handlers for window.onerror, unhandledrejection, and (in Node) uncaughtException. Any error thrown before init completes is lost. In Next.js this matters because the SDK must be installed in three runtimes (client, server, edge) and each has its own sentry.*.config.ts file. Forget to add sentry.edge.config.ts and every error in an edge middleware silently goes unreported.

Source maps are the other reliable failure point. The minified stack traces you get without source maps are technically captured events, but they’re useless for debugging — every error groups into one bucket called “error in main.abc123.js line 1.” When triaging an incident at 3 AM, the difference between a readable stack trace and a minified one is the difference between a fifteen-minute fix and a multi-hour outage. Treat source-map upload as production infrastructure: alert on the Sentry release API when no new releases have been uploaded in 24 hours, and verify the upload step is required for the deploy pipeline to succeed.

Fix 1: Next.js Integration

npx @sentry/wizard@latest -i nextjs
# Or manually:
npm install @sentry/nextjs
// sentry.client.config.ts — client-side SDK
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,

  // Performance monitoring
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

  // Session replay (optional)
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    Sentry.replayIntegration(),
    Sentry.browserTracingIntegration(),
  ],

  // Filter noisy errors
  ignoreErrors: [
    'ResizeObserver loop',
    'Non-Error promise rejection',
    /Loading chunk \d+ failed/,
  ],

  // Before sending — sanitize sensitive data
  beforeSend(event) {
    if (event.request?.cookies) {
      delete event.request.cookies;
    }
    return event;
  },
});
// sentry.server.config.ts — server-side SDK
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});
// sentry.edge.config.ts — edge runtime SDK
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
});
// next.config.mjs — wrap with Sentry
import { withSentryConfig } from '@sentry/nextjs';

const nextConfig = {};

export default withSentryConfig(nextConfig, {
  org: 'my-org',
  project: 'my-project',
  authToken: process.env.SENTRY_AUTH_TOKEN,

  // Upload source maps
  sourcemaps: {
    deleteSourcemapsAfterUpload: true,  // Don't expose source maps publicly
  },

  // Automatically instrument API routes
  autoInstrumentServerFunctions: true,
  autoInstrumentMiddleware: true,
  autoInstrumentAppRouter: true,

  // Hide source maps from the client
  hideSourceMaps: true,

  // Silence warnings during build
  silent: !process.env.CI,
});

Fix 2: Manual Error Capture

// Capture exceptions explicitly
import * as Sentry from '@sentry/nextjs';

async function fetchData() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error(`API error: ${res.status}`);
    return res.json();
  } catch (error) {
    // Capture with additional context
    Sentry.captureException(error, {
      tags: { section: 'data-fetch' },
      extra: { url: '/api/data' },
      level: 'error',
    });
    throw error;
  }
}

// Capture messages (non-error events)
Sentry.captureMessage('User reached rate limit', {
  level: 'warning',
  tags: { userId: 'user-123' },
});

// Set user context — appears on all subsequent events
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.name,
});

// Clear on logout
Sentry.setUser(null);

// Add breadcrumbs — trail of events leading to an error
Sentry.addBreadcrumb({
  category: 'navigation',
  message: 'User navigated to /dashboard',
  level: 'info',
});

Sentry.addBreadcrumb({
  category: 'api',
  message: 'Fetched user profile',
  data: { userId: '123', status: 200 },
  level: 'info',
});

Fix 3: React Error Boundary

// app/global-error.tsx — Next.js App Router global error boundary
'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>Something went wrong!</h2>
          <p>Our team has been notified.</p>
          <button onClick={reset}>Try again</button>
        </div>
      </body>
    </html>
  );
}

// app/dashboard/error.tsx — route-specific error boundary
'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function DashboardError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error, {
      tags: { page: 'dashboard' },
    });
  }, [error]);

  return (
    <div>
      <h2>Dashboard Error</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  );
}

Fix 4: Source Map Configuration

# .env
SENTRY_AUTH_TOKEN=sntrys_xxxxxxxx  # From Settings → Auth Tokens
SENTRY_ORG=my-org
SENTRY_PROJECT=my-nextjs-app
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/zzz
// For non-Next.js projects — upload source maps manually
// Install: npm install -D @sentry/cli

// .sentryclirc
[auth]
token=sntrys_xxxxxxxx

[defaults]
org=my-org
project=my-project
# Upload source maps after build
npx sentry-cli sourcemaps upload \
  --release=1.0.0 \
  --url-prefix='~/' \
  ./dist

# Or in package.json
# "postbuild": "sentry-cli sourcemaps upload --release=$(git rev-parse HEAD) ./dist"

Fix 5: Performance Monitoring

// Custom transactions
import * as Sentry from '@sentry/nextjs';

async function processOrder(orderId: string) {
  return Sentry.startSpan(
    { name: 'process-order', op: 'task' },
    async (span) => {
      // Child spans for sub-operations
      const order = await Sentry.startSpan(
        { name: 'fetch-order', op: 'db.query' },
        () => db.query.orders.findFirst({ where: eq(orders.id, orderId) }),
      );

      await Sentry.startSpan(
        { name: 'charge-payment', op: 'http.client' },
        () => chargePayment(order),
      );

      await Sentry.startSpan(
        { name: 'send-email', op: 'http.client' },
        () => sendConfirmation(order),
      );

      span.setAttributes({ orderId, total: order.total });
      return order;
    },
  );
}

// API route instrumentation — automatic with withSentryConfig
// app/api/users/route.ts
export async function GET() {
  // Sentry automatically traces this route
  const users = await db.query.users.findMany();
  return Response.json(users);
}

Production Incident: Error Monitoring Goes Dark, You’re Blind to All Errors

A Sentry outage is the worst kind of silent failure. There’s no alert because the alerting system is what stopped working. You only discover the gap when a customer reports a bug that has been broken for hours, or when you log into Sentry to triage an unrelated issue and notice the event volume dropped to zero.

The most common root causes in real incidents:

Wrong DSN after environment rotation. A team rotates Sentry keys, updates .env.production on the deploy host, but the secret store in Vercel or AWS still has the old DSN. The new deploy succeeds, the SDK initializes, events go to the old project which has been deleted. The Sentry dashboard for the active project shows zero events. Diagnose this by opening DevTools, filtering Network for ingest, and checking the response code — 403 means a stale DSN.

tracesSampleRate: 0 shipped accidentally. Performance monitoring goes dark with no warning. Errors still capture, but transactions, span data, and release health metrics stop populating. Add a CI check that asserts your production config has tracesSampleRate > 0.

Ad blockers and CSP blocks. A surprising number of users have uBlock Origin or similar tools that block requests to *.ingest.sentry.io. If you rely on client-side Sentry for product metrics, you’re systematically missing 5-15% of users. Use a tunnel — Sentry’s tunnel option proxies events through your own domain so ad blockers don’t intercept them.

To prevent silent failure, build a dead-man’s-switch: a scheduled job (cron, GitHub Actions, Cloudflare Worker) that throws a known error every hour. Alert if Sentry hasn’t received the heartbeat event in two hours. This converts a silent gap into a paging alert.

The other prophylactic to build is a release-health dashboard you actually read. Sentry’s release detail view shows crash-free session rate, adoption rate, and regression flags per release. Treat the first 24 hours after a deploy as the danger window: if crash-free session rate drops below your SLO, automatically halt further rollouts and page the on-call. Without this, a regression in error capture can hide a real spike in user-impacting errors for days, because the absolute event count looks normal while the rate per session has tripled.

Fix 6: Alerts and Issue Management

// Sentry.init — configure which errors trigger alerts
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // Attach release version for tracking
  release: process.env.NEXT_PUBLIC_SENTRY_RELEASE || 'development',

  // Environment for filtering
  environment: process.env.NODE_ENV,

  // Before sending — filter or modify events
  beforeSend(event, hint) {
    const error = hint.originalException;

    // Ignore specific errors
    if (error instanceof TypeError && error.message.includes('cancelled')) {
      return null;  // Don't send this event
    }

    // Add custom fingerprint for grouping
    if (error instanceof ApiError) {
      event.fingerprint = ['api-error', error.endpoint, String(error.status)];
    }

    return event;
  },

  // Before sending breadcrumbs — filter sensitive data
  beforeBreadcrumb(breadcrumb) {
    if (breadcrumb.category === 'xhr' && breadcrumb.data?.url?.includes('/api/auth')) {
      breadcrumb.data = { ...breadcrumb.data, body: '[REDACTED]' };
    }
    return breadcrumb;
  },
});

Still Not Working?

Zero events in Sentry — check the DSN is correct in NEXT_PUBLIC_SENTRY_DSN. Open browser DevTools → Network and filter for sentry or ingest — you should see outgoing requests when errors occur. If requests appear but return 4xx, the DSN is wrong or the project is disabled.

Events captured but stack traces are minified — source maps aren’t uploaded. Verify SENTRY_AUTH_TOKEN is set during build (not just at runtime). Check the Sentry dashboard → Settings → Source Maps to see if maps were uploaded for the current release. The release in Sentry.init() must match the release used during upload.

Performance tab is emptytracesSampleRate might be 0. Set it to 1.0 in development to capture all traces. Also check that browserTracingIntegration() is in the integrations array for client-side tracing.

Too many events / hitting quota — use beforeSend to filter noisy errors, ignoreErrors for known non-actionable errors, and lower tracesSampleRate in production. Set sampleRate: 0.5 to send only 50% of error events (not recommended for critical apps).

Events captured in dev but not in productionNEXT_PUBLIC_SENTRY_DSN is exposed to the client but the build pipeline may strip it. Check the bundled JS in production for the literal DSN string; if missing, your build host isn’t picking up the env var. For Vercel and Cloudflare Pages, ensure the variable is set in the dashboard environment for production scope, not just preview.

Source maps uploaded but stack traces still minified — the release in Sentry.init() does not match the release used during upload. Sentry groups source maps by release ID. Use the same git SHA in both places: release: process.env.NEXT_PUBLIC_SENTRY_RELEASE matched against the --release flag in sentry-cli. Mismatch causes a silent fallback to minified traces.

Replay sessions show blank screens — Session Replay uses Web APIs that some hosting setups block (MutationObserver proxy, fetch interception). Privacy-strict CSPs need worker-src 'self' blob: to allow Replay’s worker. Check the browser console for CSP violations during the replay capture window.

For related monitoring issues, see Fix: OpenTelemetry Not Working, Fix: PostHog Not Working, Fix: React Hydration Error, and Fix: Vercel Deployment Failed.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles