Every website throws errors in the browser — a failed API call, a typo in the DOM, a polyfill that didn't load, a promise that silently rejected while the user navigated away. The catch is that most of these errors never reach you. No error console in production, no developer to scream into Slack, no way to know something broke until users stop using your site. JavaScript error tracking in the browser is the practice of capturing those silent failures and surfacing them as actionable issues before they tank your conversion rate.
This guide shows you how to add error tracking to any website — no framework required. You'll capture uncaught errors, promise rejections, and cross-origin script errors, see readable stack traces pointing to your real source code, and get notified when something new crashes.
Why browser errors go unnoticed
Browser JavaScript has a unique problem: errors happen in an environment you don't control. Your users see a blank form, a frozen page, or a button that doesn't respond, but nothing shows up in your logs. Unlike backend errors that crash a process and print a stack trace, browser errors are silent by default.
Worse, certain errors actively hide themselves:
- Uncaught exceptions during render can unmount a whole component tree, and only a framework-level error boundary will catch them.
- Unhandled promise rejections fail silently without a
.catch(). - Cross-origin script errors report only "Script error." and no stack trace when loaded from a CDN.
- DOM mutation errors in async handlers don't crash the page, so they go unnoticed for weeks.
The only way to know these happen is to instrument the browser to listen for them and report them home. That's where error tracking comes in.
Install the Sentry browser SDK
Add the Sentry browser SDK to any HTML page. Because LightTrace is Sentry-SDK-compatible, you point the standard @sentry/browser package at LightTrace by changing only the dsn.
npm install @sentry/browser
Then initialize it as early as possible in your app — before any other scripts run:
<!-- In your HTML head, or at the top of your main JS file -->
<script src="https://cdn.jsdelivr.net/npm/@sentry/browser@latest/bundle.min.js"></script>
<script>
Sentry.init({
dsn: "https://<key>@your-lighttrace-host/1",
environment: "production",
release: "web@1.4.2",
tracesSampleRate: 1.0,
});
</script>
Or with a module bundler:
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://<key>@your-lighttrace-host/1",
environment: "production",
release: "web@1.4.2",
tracesSampleRate: 1.0,
});
That single call installs global handlers that capture uncaught errors and unhandled promise rejections automatically. Every exception from that point forward is sent to LightTrace, grouped by fingerprint, and ready for you to investigate.
Place Sentry.init() before any other script loads. If another library throws an error before the SDK initializes, that error won't be captured.
Capture different error types
The browser produces different kinds of errors, and each needs its own handler. The SDK installs most of them automatically, but it's worth understanding what's being caught:
Uncaught exceptions — any throw statement or runtime error that bubbles up. The SDK installs a global handler via window.onerror to capture these.
// This will be captured automatically
throw new Error("Something went wrong");
Unhandled promise rejections — any .reject() or thrown error inside an async function with no .catch(). The SDK listens for the unhandledrejection event.
// This will be captured automatically
fetch("/api/data").then(r => r.json()); // No .catch()
DOM events — errors that fire during click handlers, form submissions, or other user interactions. These are global errors too and get captured the same way.
If you want to manually report an error — say, a failed checkout that doesn't throw but should be tracked — use Sentry.captureException() or Sentry.captureMessage().
try {
// Do something risky
} catch (error) {
Sentry.captureException(error, {
tags: { section: "checkout" },
});
}
Handle cross-origin script errors
JavaScript loaded from a different domain (like a CDN, third-party script, or your own domain with a different protocol) will report only "Script error." with no useful stack trace. This is a browser security feature, but you can work around it.
Add the crossorigin attribute to your script tags and configure the right CORS headers:
<script crossorigin src="https://cdn.example.com/lib.js"></script>
And on the CDN server, add the CORS header:
Access-Control-Allow-Origin: https://yourdomain.com
Now the browser will send the full error context. For more detail, see fixing Script error (CORS issues).
Add context so debugging is fast
A stack trace is a starting point, not a solution. The real speed-up comes from context — who hit the error, what were they doing, and which release introduced it. Set the user and capture breadcrumbs at meaningful points:
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
// Breadcrumbs record the events leading up to an error
Sentry.addBreadcrumb({
category: "navigation",
message: "User navigated to /checkout",
level: "info",
});
Sentry.addBreadcrumb({
category: "http",
message: "POST /api/payment",
level: "info",
data: { status: 500 },
});
The SDK captures some breadcrumbs automatically (page navigation, console logs, clicks), but the ones you add manually tell the story of what the user was doing when things broke. When an error arrives at LightTrace, you'll see the breadcrumb trail that led to it, cutting debugging time from hours to minutes.
Upload source maps so traces are readable
In production your JavaScript is minified, so a raw stack trace looks like index-4f9a.js:1:52210 — useless. Source maps are the solution. They map minified code back to your original files so every stack frame points to a readable line.
Generate them during your build:
// webpack.config.js
module.exports = {
mode: "production",
devtool: "source-map", // Generate source maps
output: {
filename: "[name]-[contenthash].js",
sourceMapFilename: "[name]-[contenthash].js.map",
},
};
Or with Vite:
// vite.config.ts
export default defineConfig({
build: { sourcemap: true },
});
Then upload the maps to LightTrace as part of your CI/CD pipeline. Our guide to uploading source maps walks through the exact pipeline for your build tool. Once uploaded, every stack trace LightTrace receives will resolve to your original TypeScript or JavaScript.
Never upload source maps to your CDN or public files directory. They expose your source code. Upload them only to LightTrace via the Sentry API.
Common browser errors to watch for
A few JavaScript errors show up in almost every website. Each has its own diagnosis and fix:
- Cannot read properties of undefined — the single most common
TypeError, usually a missing null guard. - Undefined is not a function — a typo, scope issue, or missing import.
- Unhandled promise rejections — an async operation failed with no
.catch().
With tracking in place, these stop being mystery blank screens and become issues with a stack trace, the user who hit it, and the exact line to fix.
Now you're ready to ship with real error visibility. Set up error tracking and the next production bug becomes a line in your LightTrace dashboard instead of a support ticket.
Start tracking errors in minutes
Point the Sentry browser SDK at LightTrace and get your first JavaScript errors in minutes — free up to 5,000 events a month.
The best error tracking is the one you set up once and forget about. With the SDK in place, you'll know about every silent failure your users hit, often before they do.