Environment variables taught the hard line between public and secret. Error tracking sits on that line: a Sentry DSN is typically a public write endpoint for a project, while event payloads can contain the worst private data your UI ever touched.
I want Sentry (or any similar product) to answer three questions: what broke, which release, and is it new. I do not want a vanity dashboard of noise, or a PII museum.
What to capture vs ignore
Capture:
- Uncaught exceptions and unhandled promise rejections
- Framework error boundaries that already decided the UI failed
- Failed critical mutations (with scrubbed context): checkout, auth, save
- Performance traces selectively on key funnels, not every click
Ignore or sample heavily:
- Network blips you already retry and surface inline
- Browser extensions throwing into your page
- ResizeObserver / benign third-party script noise
- Expected 401/403 flows that are product behavior, not defects
- Localhost / preview spam unless you deliberately want it
ts// src/lib/sentry.ts, illustrative SPA init import * as Sentry from '@sentry/react'; const dsn = import.meta.env.VITE_SENTRY_DSN; export function initSentry() { if (!dsn) return; // local without telemetry is fine Sentry.init({ dsn, environment: import.meta.env.MODE, // Release set in CI, see below release: import.meta.env.VITE_APP_RELEASE, tracesSampleRate: import.meta.env.PROD ? 0.1 : 0, replaysSessionSampleRate: 0, replaysOnErrorSampleRate: import.meta.env.PROD ? 0.1 : 0, beforeSend(event) { return scrubPii(event); }, ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'Non-Error promise rejection captured', /^Loading chunk \d+ failed/, ], denyUrls: [/extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i], }); } function scrubPii(event: Sentry.ErrorEvent): Sentry.ErrorEvent | null { if (event.request?.headers) { delete event.request.headers['Authorization']; delete event.request.headers['Cookie']; } // Drop breadcrumb bodies that might include form fields if (event.breadcrumbs) { event.breadcrumbs = event.breadcrumbs.map((b) => { if (b.data && 'password' in b.data) { return { ...b, data: { ...b.data, password: '[Filtered]' } }; } return b; }); } return event; }
Tune ignoreErrors from your noise, not a copied gist. Revisit quarterly.
Release health without noise
A useful setup:
- Release string = git SHA or semver+SHA baked at build
(
VITE_APP_RELEASE) - Source maps uploaded in CI, restricted access, maps are close to source
- Environment =
production/staging, never mix in one bucket - Alert on new issues + regression spikes, not on raw event count vanity
yaml# CI sketch: upload maps after build; keep tokens server-side in CI - name: Build run: pnpm --filter @acme/web build env: VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} VITE_APP_RELEASE: ${{ github.sha }} - name: Upload source maps run: pnpm exec sentry-cli sourcemaps upload ./dist --release "$GITHUB_SHA" env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
The auth token is a secret (env-vars post). The DSN can be VITE_*. Do not
confuse them.
Wire the React error boundary so user-facing failures become events with a stable fingerprint when you know the cause:
tsximport * as Sentry from '@sentry/react'; export const AppErrorBoundary = Sentry.withErrorBoundary(App, { fallback: ({ resetError }) => ( <div role="alert"> <p>Something broke on our side.</p> <button type="button" onClick={resetError} > Try again </button> </div> ), showDialog: false, });
PII: treat events as production data
Default assumption: forms, query strings, and user objects will appear in breadcrumbs unless you stop them.
Rules I enforce:
- Never attach raw email/name/phone to
setUserunless policy says yes, prefer opaque ids - Scrub
password, tokens, and free-text fields inbeforeSend - Session replay is powerful and dangerous, off by default; on-error only if legal/privacy review agrees
- Do not
Sentry.captureMessagewith request bodies "for debugging" in prod
If you cannot explain retention and access for error payloads, you are not ready for replay.
What "good" looks like on a Monday
- New production release shows up as a Sentry release with matched commits
- One alert channel for actionable regressions; silence known third-party spam
- Issue assignees can open a stack that points at your source, not minified noise
- Staging has its own project or environment, developers do not train production alerts on WIP
Failure modes
DSN in a private repo "secret" store but events full of PII. You secured the wrong thing.
100% trace sampling in production. You bought a bill, not insight.
No release version. Every deploy looks like the same eternal app; regressions are guesswork.
Capturing expected auth failures. Your top issue becomes "Unauthorized" and nobody looks at real bugs.
Ignoring source maps because "security." Unreadable stacks also fail users. Restrict map access; do not ship blindness.
Continuity
Pairs with env-var honesty: public DSN, private CI tokens, scrubbed payloads. HMR is the local loop where many of these errors are born and fixed before Sentry ever sees them.
Takeaway
Sentry for SPAs is release-aware error capture with aggressive noise and PII control, not a firehose. Ship versioned releases, scrub like production data, and alert on regressions; ignore vanity event counts.