JavaScript Error Monitoring

JavaScript Error Monitoring: The Complete Guide

From window.onerror to source-mapped traces, learn how to capture, group, and fix the browser errors your users actually hit in production.

Every browser throws errors silently. A null reference crashes a form, a promise rejects without a .catch(), a third-party script fails to load — and your user sees a frozen page while you see nothing at all. JavaScript error monitoring is the practice of listening for these errors as they happen, capturing the context around them, and surfacing them as grouped, actionable issues before they cascade into support tickets and bad reviews.

The gap between "an error happened somewhere" and "here's the exact line that broke and who it affected" is where most teams lose time. This guide walks through how JavaScript error monitoring works, what patterns capture which errors, how to de-minify traces with source maps, and how to integrate monitoring into your workflow so that the next production bug becomes a dashboard issue instead of a surprise.

How JavaScript errors hide themselves

Unlike backend processes that crash and print a stack trace, browser JavaScript is designed to fail silently. Several categories of errors never reach you without instrumentation:

  • Uncaught exceptions during async operations — a callback throws but nobody was listening.
  • Unhandled promise rejections — a .reject() or thrown error in an async function with no .catch().
  • Cross-origin script errors — JavaScript loaded from a CDN reports only "Script error." with no stack trace.
  • Event handler failures — an error in a click handler doesn't crash the page; it just silently doesn't run.
  • Third-party library errors — a script you didn't write but loaded anyway breaks; you only know if users report it.

Each pattern requires a different capture strategy. Modern error monitoring instruments the browser to listen for these events globally so you don't have to wrap every function in a try/catch or replay customer sessions.

The anatomy of JavaScript error monitoring

A complete monitoring system captures errors in four stages: detection, enrichment, grouping, and alerting. The browser SDKs you use (like the Sentry browser SDK) handle detection and enrichment. LightTrace handles the rest.

Detection means installing global handlers that fire whenever an error matches a known pattern. The SDK uses:

  • window.onerror for uncaught exceptions and runtime errors.
  • addEventListener('error', ...) for resource-loading failures.
  • addEventListener('unhandledrejection', ...) for unhandled promise rejections.
// The SDK installs these automatically; shown here for clarity
window.onerror = (message, source, lineno, colno, error) => {
  // Capture the full error context
};

addEventListener('unhandledrejection', (event) => {
  // Capture rejected promises
});

Enrichment wraps each error with context: the user who hit it, the release version, breadcrumbs (the events leading up to the crash), network requests, console logs, and tags. Without this context, a stack trace is just a line number. With it, you can reproduce the exact path that broke.

Grouping uses fingerprinting to deduplicate. One bug can generate thousands of events if 5,000 users all hit it. A good grouper sees that all those events have the same root cause — a specific line of code throwing the same exception type — and collapses them into a single issue. You fix the issue once; the fix applies to all instances.

Alerting tells you when something new appears or an existing error spikes. With millions of events flowing in, you need your system to say "this one matters" before you spend time investigating.

Installing a JavaScript error monitoring SDK

Because LightTrace is Sentry-SDK-compatible, you use the standard Sentry SDKs for every framework and only change the dsn to point to LightTrace. A minimal setup takes two lines:

import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "https://<key>@your-lighttrace-host/1",
  environment: "production",
  tracesSampleRate: 1.0,
});

That single call installs handlers for uncaught exceptions, unhandled rejections, and global errors. From that point forward, every error flows to LightTrace automatically. You don't wrap functions; you don't write try/catch blocks everywhere. The SDK catches what matters.

For frameworks, the pattern is identical. React has @sentry/react, Vue has @sentry/vue, Angular has @sentry/angular. Each works the same way: install, initialize with the DSN, and errors start flowing.

Initialize the SDK before any other code runs. If another library throws an error before Sentry initializes, that error won't be captured. Place initialization at the very top of your entry file.

From raw traces to readable stack traces

In production, your JavaScript is minified. A raw error looks like this:

Error: Cannot read properties of undefined (reading 'id')
  at file.js:1:52210
  at Object.<anonymous> (app-4f9a.js:1:8123)

That line number 52210 points nowhere in your actual code. It's an offset into minified soup. Source maps are the solution. They map minified code back to your original files so every frame points to a readable line:

Error: Cannot read properties of undefined (reading 'id')
  at getUser (utils.ts:42:15)
  at handleSubmit (form.tsx:87:8)

Now you can navigate straight to the problem. Enable source maps in your build:

// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "declaration": true
  }
}

Then upload the maps to LightTrace as part of your deploy. Our guide to uploading source maps covers the exact pipeline for your build tool. Once set up, every stack trace resolves automatically, turning a mystery into a specific function and line.

Never commit source maps to your Git repo or upload them to your CDN. They expose your source code. Upload them only to LightTrace via the Sentry API.

Adding context that makes debugging fast

A stack trace is a starting point. What makes a trace useful is context — who hit it, what were they doing, which release introduced it. Set user identity and capture breadcrumbs at key moments:

// Set the current user
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.name,
});

// Add breadcrumbs at meaningful moments
Sentry.addBreadcrumb({
  category: "navigation",
  message: "User navigated to /checkout",
  level: "info",
});

Sentry.addBreadcrumb({
  category: "http",
  message: "POST /api/payment failed",
  level: "warning",
  data: { status: 500, statusText: "Internal Server Error" },
});

The SDK captures many breadcrumbs automatically — page transitions, console logs, network requests, DOM mutations. But the ones you add manually tell the full story. When an error arrives at LightTrace, you'll see the breadcrumb trail that led to it, turning a blind stack trace into a reproducible scenario.

Handling special cases and tricky errors

Some errors need special handling. Unhandled promise rejections are the easiest to miss:

// This silently fails without error tracking
fetch("/api/user").then(r => r.json()); // No .catch()

// Better: add a catch
fetch("/api/user")
  .then(r => r.json())
  .catch(err => {
    Sentry.captureException(err);
  });

The SDK listens for the unhandledrejection event and captures these automatically. But wrapping critical operations with explicit .catch() blocks gives you a chance to add context.

Cross-origin script errors are trickier. Scripts from a CDN report only "Script error." with no useful context:

<script src="https://cdn.example.com/lib.js"></script>

To unlock the full error context, add the crossorigin attribute to your script tags and configure CORS on the server:

<script crossorigin src="https://cdn.example.com/lib.js"></script>

With the right headers in place, the browser will share the full stack trace. See fixing Script error (CORS issues) for the full setup.

Errors in event handlers don't crash the page, so they're silent by default. The SDK catches these through the global error event listener, but you can also catch them manually:

button.addEventListener("click", (e) => {
  try {
    handleCheckout();
  } catch (error) {
    Sentry.captureException(error, {
      tags: { action: "checkout" },
    });
  }
});

JavaScript error monitoring across browsers and environments

Errors behave differently across browsers. Some throw in Chrome but silently fail in Safari. Others are specific to old versions of Edge or Firefox. This is where cross-browser error monitoring pays off — you see which browsers hit which errors and can prioritize fixes based on impact.

Memory leaks are a special class of browser error that doesn't throw but slowly degrades performance. Long-running single-page apps can accumulate detached DOM nodes, lingering event listeners, and unreleased resources until the tab becomes unresponsive. Error tracking won't catch this directly, but adding custom instrumentation can help — debugging memory leaks covers the tools and patterns.

Not all errors that matter throw exceptions. Some are silent degradations: stalled infinite loops, missed cleanup, resource exhaustion. Add manual capture points for these: Sentry.captureMessage() to log anomalies you detect through other instrumentation.

Integrating with your workflow

Once error monitoring is in place, the real value comes from integrating it into your incident response workflow. When a new issue arrives at LightTrace, it should trigger:

  1. Alert — get notified via email when a new issue appears or an existing one spikes.
  2. Triage — check how to read a stack trace, see the affected user count and release, read the breadcrumb trail.
  3. Reproduce — the breadcrumbs and context should let you recreate the failure locally or in staging.
  4. Fix — push a fix, ship a release, and tag the new release in LightTrace so the system knows the next events are from a new build.
  5. Verify — watch the error count drop to zero as the new release rolls out.

Teams that nail this cycle see dramatic reductions in mean-time-to-resolution. Errors go from "something broke, where?" to "here's the code, here's the fix" in minutes.

Why JavaScript error monitoring matters for browser UX

A single unhandled error can cascade across a session. One TypeError unmounts a React component tree, leaving users staring at a blank screen. One unhandled promise rejection can stall a critical user flow like checkout or signup. Without visibility, these become silent failures that quietly tank conversion.

With monitoring in place, you know about every silent failure, often before customers report them. You can set up alerting rules to page you only for errors that matter (new issues, spikes, critical paths). You can tag issues by severity, track crash-free rate per release, and tie each error back to the exact line of code that broke.

Start tracking errors in minutes

Point the Sentry browser SDK at LightTrace and get your first JavaScript errors grouped in minutes — free up to 5,000 events a month.

JavaScript error monitoring turns the darkness of production into visibility. Set it up once, and the next time a user experiences a blank screen or frozen button, you'll know about it, see the breadcrumbs, and ship the fix before they leave.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.