Debugging minified JavaScript errors with source maps
Minified JavaScript errors are unreadable. Learn how JavaScript source maps turn t.a is not a function back into the real file, function, and line.
A production JavaScript error looks like this: t.a is not a function at app.4f3a.js:1:88291. That tells you nothing. The code your users ran was bundled, minified, and mangled into a single line, so every variable is a letter and the line number points at column 88,291 of a file you never wrote. The fix is JavaScript source maps — the missing translation layer between the shipped bundle and your real source. With source maps in place, that same error reads pay() is not a function at CheckoutForm.tsx:42:12. This post explains what source maps are, why minified errors are gibberish without them, and how to wire them up so production stack traces become readable.
Why minified errors are unreadable
Bundlers exist to make your JavaScript small and fast: they rename calculateTotal to t, strip whitespace, inline modules, and collapse everything onto one line. Great for load times, terrible for debugging. When that code throws, the browser reports the location in the bundle — a meaningless filename, line 1, and a giant column number. You can't grep for t.a, and the line:column points into machine-generated soup. Without a translation layer, every production JS error is a dead end.
What a source map actually does
A source map is a JSON file (usually app.4f3a.js.map) that records how the bundle maps back to your original files. For a given position in the minified output, it can answer: which source file, which line, which column, and what the symbol was originally named. It's a coordinate translator. Give it 1:88291 in app.4f3a.js and it hands back CheckoutForm.tsx:42:12, pay.
You don't read source maps by hand — they're built for tools. Your bundler already produces them; the job is getting them somewhere that can apply them to incoming errors.
Symbolication: applying the map
Turning a minified frame back into a real one is called symbolication. The tricky part is that production should not ship its source maps to the browser — you don't want to hand your original source to every visitor. So the maps live server-side, and symbolication happens where the errors are collected.
Lookout supports uploading your JavaScript source maps, then symbolicates minified frames as errors arrive. The flow:
- Build your app and generate source maps (most bundlers do this with a config flag).
- Upload the
.mapfiles to your project, tagged to the release they belong to. - When a minified error comes in, Lookout matches it against the right map and rewrites the stack trace to your real files, functions, and lines.
You see CheckoutForm.tsx:42, not app.4f3a.js:1.
Match maps to the right release
The most common reason symbolication silently fails: the uploaded map doesn't match the bundle that threw. Every deploy produces new hashed filenames and new maps, so an error from last week's bundle needs last week's map. Tie each upload to a release identifier and capture that same release on the client, so incoming errors resolve against the exact build that produced them. Get this right once and it keeps working across every deploy.
Capture the column, too
VLQ source maps resolve by line and column. Minified code is one giant line, so the column is what actually pinpoints the symbol — without it, you land on the right line (line 1) and learn nothing. Make sure your browser error capture records the column number; modern error events include it, and the symbolicator needs it to find the exact frame.
Wrapping up
JavaScript source maps are the difference between t.a is not a function and a real file-and-line you can open in your editor. The bundle is for browsers; the map is for you. Generate maps at build time, upload them tied to each release, capture the column, and let symbolication rewrite every production frame back to your source. Track and symbolicate your JS errors free — 10,000 events a month, one project, no credit card.