Errors

How to monitor JavaScript console errors in production

Open your site, press F12, and read the console. Now remember: every user has one of those too — theirs is full of errors you have never seen, and none of them will tell you.

Browser console errors being collected and grouped

Monitoring JavaScript console errors in production means capturing the uncaught exceptions, unhandled promise rejections and failed loads that surface in real users' browser consoles, reporting them to a collector, grouping duplicates by fingerprint, and alerting when a site's error rate deviates sharply from its own normal. The console on your development machine shows one environment; production is thousands of environments — browser versions, extensions, network conditions — each with its own console you will never look at. Monitoring is how those consoles report back.

Key takeaways
  • A console error in production is a failure that happened to a real user — the console is just where the evidence lands.
  • Two browser events capture most of it: error (uncaught exceptions, resource failures) and unhandledrejection (async failures).
  • Raw capture without fingerprint grouping is unusable: one bug at scale is tens of thousands of near-identical messages.
  • Error text must be treated as hostile input for privacy: messages and URLs routinely carry tokens and personal data.
  • Alert on deviation from the site's own error baseline, never on individual errors.

Do console errors actually matter?

The sceptical position — "there are always console errors, it's noise" — is half right. Some are: third-party extensions injecting broken scripts, museum-piece browsers, bots executing half a page. If your policy is "zero console errors", production will humble you by lunchtime.

But inside the noise, console errors are precisely the failures that matter and go unreported:

The task is not "eliminate console errors"; it is see them, group them, and notice when their rate changes — because a rate change is a regression with a timestamp.

What to capture, technically

Two global listeners cover most of the surface:

Optionally, wrapping fetch observes failed network calls (status ≥ 400, network failures) — with one non-negotiable guard: never report failures of the reporting endpoint itself, or a collector outage becomes an infinite error loop.

Delivery discipline: batch reports on an interval rather than firing per-error; cap the queue so a loop cannot flood; use sendBeacon on page hide so the error that broke the page is not lost when the user closes it; and wrap the entire reporter so a bug in monitoring can never break the page it monitors.

Grouping: from 40,000 messages to 6 bugs

One broken deploy on a busy site generates tens of thousands of error events in an afternoon. Stored raw, that is a haystack. The fix is fingerprinting:

  1. Normalise the message — replace numbers, IDs, UUIDs and URLs with placeholders, so user 4821 not found and user 9317 not found share a shape.
  2. Hash the shape together with the error kind and source file into a fingerprint.
  3. Count per fingerprint per hour — one row per bug per hour, holding a counter and one representative message.

The questions that matter — which errors, how often, since when, still growing? — are all answerable from those counters, at a millionth of the storage. And the counter shape is what makes rate-based alerting possible at all.

Privacy: error text is a data leak by default

Nobody designs error messages to carry secrets; they carry them anyway. URLs arrive with session tokens in query strings; API errors echo emails ("no account for jo@example.com"); stack traces embed request payloads. Collect all of that verbatim and your error database quietly becomes your most sensitive datastore — with none of the access controls.

Minimum discipline, enforced at both ends:

If you run websites for clients, this is doubled: the users in those consoles are your client's customers, and your diligence is part of what the client is paying for.

Alerting: the baseline is the site's own history

The wrong rule is a fixed threshold ("alert over 100 errors/hour") — instantly too noisy for a busy site and too deaf for a quiet one. The right rule is self-referential: compare this hour against this site's usual hour, using a median of recent history so one previous bad afternoon does not distort the norm.

Best practice folds the spike into the application's wider health signal rather than alerting in isolation: an error-rate deviation plus rising API latency plus a deploy twenty minutes ago is one early warning with three pieces of evidence, not three notifications. Frontend error monitoring is one instrument in the orchestra, not a soloist.

Quick reference

Capture surfacewindow 'error' event (exceptions + resource failures), 'unhandledrejection', optional fetch wrapping
Reporter disciplineBatch on an interval, cap the queue, sendBeacon on page hide, never report the reporter's own endpoint
Grouping methodNormalise message (IDs/numbers/URLs → placeholders) → hash with kind+source → hourly counter per fingerprint
Privacy rulesStrip query strings client-side; redact tokens/emails/card shapes server-side; no cookies or identity; short retention
Alert ruleDeviation vs the site's own median hour — never fixed thresholds, never individual errors
Common real culpritsUndefined property access per-browser, unhandled rejections in save flows, ChunkLoadError after deploys, CSP blocks

Frequently asked questions

How do I capture JavaScript errors in production?

Register listeners for the browser's global 'error' event, which receives uncaught exceptions and (with the capture flag) resource load failures, and the 'unhandledrejection' event, which receives promise rejections nothing caught. Report them in small batches to a collection endpoint, using sendBeacon when the page is being closed so final errors are not lost.

What is error fingerprinting?

Fingerprinting is grouping error events by their normalised shape rather than their exact text. Numbers, IDs and URLs in the message are replaced with placeholders, and the result is hashed together with the error kind and source file. Thousands of occurrences of one bug then collapse into a single group with a counter, which is what makes both triage and rate-based alerting practical.

Should I alert on every console error?

No. Production sites always carry background errors from extensions, old browsers and bots, so per-error alerting is unusable noise. Alert when a site's hourly error count deviates strongly from its own historical baseline — a many-times-normal spike almost always marks a real regression, usually one that shipped in a recent deploy.

Are console errors a privacy risk?

Yes, if collected carelessly. Error messages and URLs routinely contain session tokens, email addresses and other personal data. A responsible pipeline strips query strings before reporting, redacts secret-shaped and personal-shaped strings again at the collector, sets no cookies, collects no user identity, and retains error detail for weeks rather than years.

What is a ChunkLoadError and why does it spike after deploys?

Single-page applications load code in named chunks. A deploy that renames those chunks invalidates the file names referenced by pages already open in users' browsers; when such a page tries to lazy-load a route, the old chunk name 404s and throws ChunkLoadError. A spike right after deployment is the signature. Mitigations include keeping old chunks available for a grace period and prompting stale sessions to reload.

Can I monitor console errors without slowing my site down?

Yes. A capture script needs only two event listeners and a small batching queue — a few kilobytes of code, no framework, deferred loading, and no work at all on pages where nothing fails. The essential guard is defensive wrapping so the monitor itself can never throw or loop; monitoring must never become the thing that breaks the page.

How Merik handles it

This article describes merik.js, Merik's browser reporter, almost mechanism for mechanism — because these are the mechanics we consider table stakes. One script tag captures uncaught errors, unhandled rejections, failed requests and failed resources; batches on a ten-second interval; caps its queue; flushes by beacon on page hide; and refuses to report failures of its own endpoint. Query strings never leave the browser, and the collector re-redacts tokens, emails and card-shaped strings before anything touches storage.

Grouping is fingerprint-based into hourly counters per site, and alerting is exactly the self-referential rule above: this hour against the site's own median, folded into the asset's early-warning signal beside uptime, latency and deploy evidence rather than shouting on its own. Add the snippet to a site you run, and its console errors stop being a thing you discover during screen-shares.

Create your workspace →

Or talk to us about your team →