How to Debug JavaScript Errors in Production (Without Guessing)
A minified stack trace is a riddle until you add source maps. Pair them with a console, network, and session replay, and you stop guessing: reproduce from the real user session, isolate the cause, ship the fix.

An error count ticks up in your dashboard. The stack trace reads t is not a function at main.4f2a.js:1:88213. You cannot reproduce it locally, the user is gone, and the logs show a dozen unrelated lines around the crash. So you start guessing: add a log, redeploy, wait, refresh, repeat. This is log-spelunking, and it is the default way most teams debug production JavaScript. It is slow, and it is unreliable because you are reconstructing a failure you never actually saw.
There is a repeatable alternative. Capture the real failing session, resolve the trace through source maps, reproduce from the recording, isolate the cause, and ship. This guide walks that method end to end and contrasts the two approaches honestly. The argument for replay-driven debugging is not aesthetic. The 2025 Stack Overflow Developer Survey found that 66% of developers name "AI solutions that are almost right, but not quite" as their top frustration, and 45.2% say debugging AI-generated code is more time-consuming than debugging human-written code. Plausible-looking is not the same as correct. The fix is to debug from evidence, not from a story you tell yourself about what happened.
How do you debug a JavaScript error in production without guessing?
Resolve the minified stack trace with source maps so it points to real files and lines. Capture the console, network requests, and a session replay together on one timeline. Reproduce the failure by replaying the user's session rather than inferring steps. Isolate the cause against the last deploy, then ship and verify the fix. Capture first, diagnose second.
The five steps are ordered deliberately: capture, resolve, reproduce, isolate, ship. The expensive mistake is reversing capture and diagnose. If you start writing a fix before you have reproduced the failure, you are patching a symptom you guessed at. Every step below produces an artifact that the next step consumes, and the same artifacts hand off cleanly to an AI agent at the end.
Step 1: Resolve the stack trace with source maps
A production bundle is minified, so the browser sees a.js:1:18327, not checkout.ts:42. A source map is a JSON file that maps the transformed code back to your original source. Per MDN, browsers locate it via the SourceMap HTTP response header or a sourceMappingURL annotation in the generated file. That mapping is the mechanism that turns a meaningless column offset into a real function, file, and line.
Chrome DevTools can de-minify a production trace once you enable JavaScript source maps under Settings > Preferences > Sources. The Console then links to your authored files, and when execution pauses at a breakpoint the Call Stack shows original filenames. This is not a niche capability anymore: in March 2025 AWS added JavaScript source map support to CloudWatch RUM, converting minified production traces into readable file, line, and column locations. Mapping minified errors back to source is table stakes across monitoring vendors now, not a nice-to-have.
# Generate source maps at build time, but do NOT serve them publicly.
# Vite / Rollup
vite build --sourcemap hidden # emits .map files + sourceMappingURL stripped from bundle
# A 'hidden' source map produces the .map file for your tooling
# without the sourceMappingURL=... comment in the shipped JS,
# so the public bundle never advertises where the map lives.
# Upload the maps to your error/capture tool over an authenticated channel,
# then delete them from the public web root before deploy:
curl -sf -H "Authorization: Bearer $TOKEN" \
-F "release=$GIT_SHA" \
-F "sourcemap=@dist/assets/index.4f2a.js.map" \
https://your-tool.example.com/api/sourcemaps
rm dist/assets/*.map # readable traces for your team; no source for the publicStep 2: Capture console, network, and a replay together
A resolved stack trace tells you where the code threw. It does not tell you why. For that you need the state at the moment of failure: what the console logged just before, what network response came back malformed, and what the user actually clicked. The problem is that those three signals normally live in three different places and disappear when the tab closes.
This is where session replay changes the loop. rrweb (around 19.8k GitHub stars) records the DOM, DOM mutations, and user interactions as a stream of JSON events rather than as video. The recording stays small enough to attach to a bug report, and because it is structured DOM rather than pixels, you can inspect the live DOM at any point on the timeline. Align that replay with the console errors and the network waterfall and you have the full picture of one failing session in a single artifact.
| Feature | Log-spelunking | Replay-driven (rrweb) | BugMojo |
|---|---|---|---|
| Works with zero added tooling | ✓ | — | — |
| Source-mapped stack trace in context | manual | partial | ✓ |
| DOM + console + network on one timeline | — | ✓ | ✓ |
| Reproduction travels with the ticket | — | ✓ | ✓ |
| Mature production error aggregation & alerting | partial | partial | early |
| Failing session exposed to an AI agent over MCP | — | — | ✓ |
Step 3: Reproduce from the user session
"Cannot reproduce" is the phrase that kills production bugs. It is also an artifact of log-spelunking: you cannot reproduce because you are guessing at the steps. Replay-driven debugging removes the problem by definition, because the reproduction is the recording. You scrub the timeline to the moment the console error fired, watch the exact clicks and form inputs that preceded it, and inspect the DOM state and the network response that the code choked on. There is nothing to reconstruct.
Concretely: open the replay, find the red console entry on the timeline, step back a few hundred milliseconds, and read the network request that resolved just before. Nine times out of ten the malformed payload, the unexpected null, or the race between two requests is sitting right there. You have now reproduced the failure without ever asking the user "what were you doing?"
Step 4: Isolate the cause
With a reproducible failure in hand, isolation is mechanical. Start with the question that resolves most production incidents: is this a regression? Check the last deploy against the first timestamp the error appeared. If the error started within minutes of a release, your suspect list is that diff, not the entire codebase. Next, confirm the blast radius from the captured metadata: which browser, which version, which route. An error that only reproduces in one browser engine narrows the cause to an API or syntax difference; an error scoped to one route narrows it to that route's code path.
From the source-mapped trace you already know the throwing line. From the network capture you know the input that reached it. The cause is the intersection: the line of original source that received an input it did not handle. Set a conditional breakpoint there in DevTools, replay the captured input, and watch it break in your own debugger. That is isolation complete.
Step 5: Ship the fix (and hand the repro to an agent)
Here is where most production-debugging guides stop: a human reads the source-mapped trace, writes the patch, and ships. That assumes a person is sitting in front of the browser cross-referencing the replay, the console, and the trace by hand. The artifacts you captured in steps 1 through 4 are more useful than that, because they are structured, and structured failure data is exactly what an AI coding agent can consume.
When the rrweb replay, the console logs, the network requests, and the source-mapped stack trace are exposed as structured context over an MCP server, an agent such as Claude Code or Cursor can read the full reproduction and draft the fix without a human re-tracing the steps. The agent sees the failing line in your real source, the network response that triggered it, and the user actions that led there. This is the one thing the existing tools do not do: every error monitor and replay product stops at "a human reads the trace." None turn the reproduction into machine-readable context for an autonomous agent.
It also matters because of trust. The same 2025 Stack Overflow survey reports that 46% of developers distrust the accuracy of AI-tool output (26.1% somewhat distrust plus 19.6% highly distrust), while only about 3% highly trust it. The practical takeaway is direct: an agent's proposed fix should be grounded in a captured reproduction, not generated from a guess. Feed the agent the real failing session and its output stops being "almost right, but not quite."
Copy-paste production triage checklist
Run this top to bottom on every production JavaScript error. The first half is capture; the second half is diagnose. Do them in that order. The contrast to keep in mind: a log-spelunking pass jumps straight to step 6 and works backward through guesses, while a replay-driven pass completes steps 1 through 5 first so step 6 reasons from evidence.
- 1. Capture the error string + de-minified trace. Resolve
main.x.js:1:NNNNto a real file and line via source maps before reading anything else. - 2. Confirm the environment. Record browser, browser version, OS, and the exact route or URL where it fired.
- 3. Pull console state at failure. Capture the console entries immediately before and at the throw, not just the error itself.
- 4. Pull network state at failure. Capture the request and response that resolved just before the error, including status and payload.
- 5. Attach a session replay. Include the rrweb recording so the reproduction travels with the ticket and the next person does not start from zero.
- 6. Reproduce from the replay. Scrub to the error, watch the preceding user actions, confirm you can trigger it.
- 7. Check for regression. Compare the first-seen timestamp against the last deploy; if they line up, suspect the diff.
- 8. Assign severity. Scope the blast radius (one user, one browser, all users) and prioritize accordingly.
- 9. Only now write the fix. Or hand the captured bundle to an AI agent over MCP and review the patch it drafts from the real reproduction.
A production stack trace is half the story. The source map gives you the line; the replay, console, and network give you the why. Capture all four and the fix stops being a guess.BugMojo engineering
Install the free BugMojo extension to capture the rrweb replay, console logs, network requests, and a source-mapped stack trace as one shareable artifact — then let an AI agent read the real reproduction over MCP and draft the fix. No project setup required.
Install the extensionFrequently asked questions
Frequently asked questions
Sources
- Stack Overflow 2025 Developer Survey — AI sentiment, trust, and top frustrations with AI-generated code — Stack Overflow (2025)
- rrweb — record and replay the web, capturing DOM and interactions as JSON events (GitHub repo) — rrweb-io (2025)
- Debug your original code instead of deployed with source maps — Chrome for Developers (Google) (2024)
- Source map — Glossary (definition and how browsers locate maps) — MDN Web Docs (Mozilla) (2025-12-15)
- SourceMap header — HTTP reference — MDN Web Docs (Mozilla) (2025-11-21)
- CloudWatch RUM now supports JavaScript source maps for easier error debugging — Amazon Web Services (2025-03)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

