You wire up Sentry on a Friday. By Monday there are 3,000 events across 60 issues, and the first four you open are:
TypeError: Cannot read properties of null (reading 'querySelector')from a file calledinject.jsLoading chunk 4821 failedError: ENOENT: no such file or directory, open '/.env'AbortError: The operation was aborted
None of them is a bug in your app. If you spend the morning on them you will learn nothing and, worse, you will start ignoring the feed, which is how the real incident on Thursday goes unnoticed for six hours.
The four signatures of noise
1. Browser extensions and injected scripts
Looks like: stack frames from chrome-extension://, moz-extension://,
safari-web-extension://, or files you have never heard of (inject.js,
content.js, gtm.js). Errors about DOM elements that do not exist in your
markup. Often a wide spread of browsers and a single event per user.
Why it happens: an extension runs in the page, throws, and your global handler catches it. It is genuinely not your code.
Filter at: denyUrls in src/instrumentation-client.ts.
denyUrls: [/extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i, /^safari-web-extension:\/\//i],
Do not dismiss too fast: an extension error that only happens on your checkout page can still mean your DOM broke an assumption it makes for everyone. Check whether the issue is spread across routes or concentrated on one.
2. Stale tabs after a deploy
Looks like: Loading chunk N failed,
Failed to fetch dynamically imported module, Unexpected token '<' from a
.js URL. A sharp spike immediately after each deploy, then decay over a few
hours.
Why it happens: a user's tab has been open since before the deploy. It asks
for a chunk hash that no longer exists, gets a 404 (or your HTML 404 page,
hence the <), and fails.
Filter at: ignoreErrors. It is already there in this configuration.
But also fix the product problem: the user's tab is broken. Catch chunk load errors in the client and prompt a reload, or use a router-level error boundary that recovers by navigating. The error is noise; the broken session is not.
3. Bots, scanners and vulnerability probes
Looks like: server-side errors on paths you never wrote, /wp-admin,
/.env, /phpmyadmin, /.git/config, /api/v1/../../etc/passwd. Malformed
JSON bodies. null user on every event. A handful of IPs, or a thousand. Often
a burst at a strange hour.
Why it happens: the internet scans everything. Within a day of a domain going live it is being probed continuously.
Filter at: ideally before your app: a WAF or platform-level rule. Failing
that, ignoreErrors on the specific parse errors, plus making sure your 404
path does not throw.
The thing to actually check: a scanner hitting a route that exists and
getting a 500 is a real finding. /api/users?id=1%20OR%201=1 returning a
database error means your route is not validating input, and the bot just did
your security review for you.
4. Users who left
Looks like: AbortError, The operation was aborted, ECONNRESET,
ERR_STREAM_PREMATURE_CLOSE, ClientClosedRequest. Frequently on long
requests: streaming responses, uploads, slow searches.
Why it happens: the client navigated away or closed the tab. The in-flight request was cancelled. Nothing is wrong.
Filter at: ignoreErrors on the server config, and check error.name ===
"AbortError" before capturing in your own handlers.
But: a rise in abort rate on one route is a real signal. Users abort
requests that are too slow. If aborts on /search triple, that is a latency
regression wearing a disguise.
The three signals that it is real
Against those four, the shape of a genuine incident:
1. It correlates with a deploy. Sentry shows first-seen release. If an issue's first event is the release you shipped twenty minutes ago and the count is climbing, stop reading and go look at that diff. This single signal resolves most real incidents.
2. Multiple distinct users, in a short window, on one route. Noise is usually one user many times, or many users once across many routes. A real break is many users, on one path, right now. Sort by user count, not event count.
3. The stack trace is in your code. The top frames are your files, your function names, your line numbers, not a vendor bundle, not an extension. Combined with signal 1, this is close to conclusive.
A triage rule that holds up
For each new issue at the top of the feed, in order:
- Is the top frame my code? No → probably noise; classify it as one of the four and filter it at the right layer.
- Is it new since a recent release? Yes → treat as a regression until proven otherwise. Read the diff before the stack trace.
- How many distinct users? Many → incident. One → interesting, not urgent.
- Is it growing, flat or decaying? Growing → act now. Decaying → probably a stale-tab spike, already over.
- Can I reproduce it? No → do not ship a speculative fix. A wrong fix changes the stack trace, which creates a new issue and makes it look resolved.
Suppress with a reason, and review it
Every filter you add is a decision to be blind to something. That is fine, and it needs a comment:
ignoreErrors: [
// A user navigated away mid-request. Not actionable.
"AbortError",
// Stale tab holding a pre-deploy chunk hash. See the reload prompt in
// src/components/chunk-error-boundary.tsx.
"Loading chunk",
],
An ignoreErrors entry with no explanation is how a real error gets silently
dropped two years from now, when the string happens to match something new.
Re-read the list every few months. Filters accumulate, browsers change, and the entry that was noise in one release can be masking a bug in the next.
Get to zero, then keep it there
The goal is not a small number of issues; it is a feed where every unresolved issue is something you intend to act on. That state is worth real effort to reach, because it changes what an alert means: a notification from a clean project is information, and a notification from a noisy one is a reflex you have already learned to ignore.