Source maps
How devtools shows you the file you wrote when the browser is actually running something else — and the moments it gets it wrong.
You wrote your code in src/cart.ts. The browser is running dist/main.min.js — a single file with no spaces, no line breaks, no readable variable names. When you set a breakpoint or look at a stack trace, devtools shows you src/cart.ts with the original variables and line numbers anyway. That's a source map doing its job.
When source maps work, you forget they exist. When they break, you'll see a stack trace pointing to line 1, column 18,432 of an unreadable file, or a breakpoint that pauses on a line that doesn't match the line you set it on. This lesson is about understanding how the bridge between "what you wrote" and "what's running" is built — so you can recognize when it's broken.
What a source map is
A source map is a separate file — usually with the .map extension — that contains a giant lookup table. For each character in the built file, it records: which original source file it came from, and which line and column inside that source file.
Your build tool (Vite, esbuild, webpack, tsc) generates the source map alongside the built file. The browser sees a special comment at the bottom of the built file telling it where to find the map:
// ...thousands of characters of minified code... //# sourceMappingURL=main.min.js.map
When devtools loads the built file, it follows the sourceMappingURL, fetches the .map, and uses it to translate everything you see — Sources panel file tree, Console stack traces, breakpoint positions — back into the original source files.
A useful mental picture: the minified file is a foreign-language translation of your code. The source map is the dictionary. With the dictionary in hand, devtools can show you the original at every step.
You write a cart.ts line that becomes character 14,082 of main.min.js. The browser runs character 14,082. The map records "this character corresponds to cart.ts line 22, column 4." Devtools uses that to display cart.ts:22:4 in the Sources panel and in stack traces.
Verifying yours work
If you've never checked, your source maps might silently not be loading. Three quick checks:
1. Open Sources and look at the file tree on the left. With source maps working, you'll see your original file structure — folders like src/, components/, the original .ts or .tsx files. Without source maps, you'd only see the built artifacts (main.min.js, chunk-abc123.js).
2. Look at a stack trace in the Console. Trigger an error (call throw new Error("test") in the Console). Click the file path next to the error. With maps working, it opens your original source. Without, it opens the minified file.
3. Check the Network panel for .map requests. With source maps loading correctly, you'll see one .map request per built file (filter by .map to see only these). Status should be 200. A 404 means the map file is missing or the sourceMappingURL comment points to the wrong place.
In Chrome devtools settings, there's a checkbox under Preferences → Sources → Enable JavaScript source maps. If it's unchecked, no source maps load — and stack traces and breakpoints all show the minified file. Worth a glance if your maps aren't working and the build looks correct.
A common surprise: source maps usually only load when devtools is open. The browser doesn't fetch them otherwise — there's no point translating stack traces nobody's reading. So checking "are my source maps working?" requires devtools open during a reload.
main.min.js and vendor.min.js — none of your original files. Errors in the Console point to lines like main.min.js:1:14082. What's the most likely problem?When source maps lie
Source maps are sometimes wrong. The map says "this character is on line 22 column 4" but the line you're looking at in the original was actually generated from a different place. The breakpoint pauses, but on the wrong line. The variable values seem to predict the future. Stepping doesn't make sense.
Three common reasons.
1. The map is stale. You changed source files but the build didn't run. The browser is still using a map that was generated for an earlier version of the source. Run a clean build, hard-refresh, and the problem usually goes away.
2. The build did aggressive optimization. Tree-shaking, dead-code elimination, function inlining, and constant folding all change which bits of source ended up in the output. A function that the optimizer inlined no longer exists in the built file at all — its code is scattered across whatever called it. The map points back to the original location, but execution doesn't really happen there in any meaningful sense.
3. Compiled-language quirks. TypeScript and Babel both transform syntax (async/await → state machines, optional chaining → conditionals, JSX → function calls). Maps record the original line, but a single original line can become several lines of generated code. Stepping might look like you're moving backward, or skipping lines.
If your source maps are lying, switch the Sources panel to "show generated code" temporarily (right-click in the editor) and step through the built code. The line positions are honest there. Switch back to the original once you've found the bug — the maps lie about positions but tell the truth about variable names, which is still useful.
A practical rule: if your debugging session feels like you're losing your mind — variables changing in impossible orders, breakpoints firing on the wrong line, stepping that goes nowhere — suspect the source maps before you suspect your code. Build clean, hard-refresh, and try again.
const name = user?.profile?.name;. When you step over it, the debugger seems to pause three times in slightly different positions. What's happening?Source maps in production
Should you ship source maps to production? It's a tradeoff with two sides.
For: when a user hits an error in production, your error tracker (Sentry, Bugsnag, Rollbar) needs the source map to translate the stack trace back into your original source. Without it, you get unreadable lines like main.min.js:1:14082 in your error reports — useless for debugging.
Against: source maps reveal your original source code to anyone who opens devtools. For some businesses (proprietary algorithms, security-by-obscurity) that's a non-starter. They're also bigger than the built files themselves — extra bytes to host.
The middle path most teams settle on: build source maps, but don't deploy them with the built files. Upload them privately to your error tracker instead. The tracker can resolve stack traces server-side using the map, while users browsing the public site never see them.
This requires setup specific to each tracker — a CLI command at deploy time that uploads the maps and tells the tracker which version they belong to. Once it's set up, the workflow is invisible: errors arrive in the dashboard with line numbers from your real source files.
main.min.js:1:48211. What's missing?.env change and rebuilt. After a hard refresh, your breakpoint pauses but the variable values don't match the surrounding code — it looks like you're paused on a totally different version. What's the most likely cause?