Using Chrome DevTools to Debug Minified Bundles
A practical workflow for locating, formatting, and stepping through minified production bundles in Chrome DevTools when source maps are absent. This page is one concrete technique within Debugging Minified Code Without Source Maps; the broader Source Map Generation & Stack Trace Debugging reference covers the full pipeline from deterministic build configuration to automated symbolication.
Symptom / Trigger
The session starts with a minified stack trace in the DevTools Console or your error monitoring platform. The frame below is typical — no source file name, only a bundled asset path, and an opaque column offset:
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
at t.render (https://cdn.example.com/assets/main.a8f3c.js:1:84732)
at t.e (https://cdn.example.com/assets/main.a8f3c.js:1:6109)
at https://cdn.example.com/assets/main.a8f3c.js:1:1903
The Network tab shows the following when you reload and filter for .map:
GET https://cdn.example.com/assets/main.a8f3c.js.map → 404 Not Found
No source map request at all means the //# sourceMappingURL comment was stripped or never written. Either way, you are working with the raw minified bytes.
Root Cause Explanation
The {} pretty-printer in DevTools exists precisely because minifiers collapse all code to a single line. Without it, the column number 84732 in a 1.2 MB single-line file is effectively unnavigable. The underlying problem is that minification trades human readability for delivery speed, and without a .map file the inverse transformation is impossible.
The broken pattern looks like this — a single line containing the entire application:
// Minified output (single line, truncated for display)
!function(e){var t={};function n(r){if(t[r])return t[r].exports;...}(84732 chars later)...e.render=function(){return e.state.items.map(function(t){return t.id})}
The Cannot read properties of undefined (reading 'map') error fires because e.state.items is undefined at runtime. Without formatting, finding that e.state.items call at column 84732 requires scrolling across a single enormous line — impractical without tooling.
Step-by-Step Fix
Step 1 — Confirm the Missing Map and Record the Raw Offset
Open DevTools (F12), go to the Network tab, and reload the page. Filter by JS. Locate the failing bundle and click it. Under the Response tab, scroll to the very end of the file and look for:
//# sourceMappingURL=main.a8f3c.js.map
If that comment is absent or returns 404, you are in map-less territory. Switch to the Console tab and expand the stack trace. Copy the exact filename:line:column string from the failing frame — you need 1:84732 (or whatever the real offset is) for every subsequent step.
Step 2 — Open the Bundle in the Sources Panel and Pretty-Print
Navigate to the Sources panel. In the left file tree, expand the domain and locate main.a8f3c.js. Click it to open the file. The entire bundle renders as one long line.
Click the {} icon in the bottom toolbar of the code editor pane. DevTools reformats the code instantly with indentation and line breaks. Function boundaries, try/catch blocks, and module wrappers become readable.
// After pretty-printing, the previously unnavigable line becomes:
e.render = function () {
return e.state.items.map(function (t) { // ← the failing call
return t.id;
});
};
The pretty-printed line number for this expression will not be 1. Note that discrepancy — Error.stack always reports the pre-pretty-print offset.
Step 3 — Navigate to the Raw Column Offset
Before or after pretty-printing, use DevTools’ go-to-position shortcut to jump to the raw minified offset:
- With the bundle open in Sources (before clicking
{}), pressCtrl+G(Windows/Linux) orCmd+G(macOS). - Enter
1:84732to jump to line 1, column 84732. - Note the 5–10 character token at that position — typically a function call, property access, or operator.
- Search (
Ctrl+F) for that exact token in the pretty-printed view to re-anchor your position.
// DevTools Console — calculate a quick delta between raw column and pretty-print position
// After visually finding the expression in both views:
const rawCol = 84732;
const prettyLine = 318; // whatever pretty-print line the expression landed on
console.log(`Anchor: raw col ${rawCol} → pretty-print line ${prettyLine}`);
// Use this as a consistent reference for all subsequent breakpoints
Step 4 — Set a Conditional Breakpoint at the Failure Point
In the pretty-printed view, click the line number gutter next to the failing .map() call. A blue breakpoint marker appears. Right-click it and choose Edit breakpoint. Enter a condition that fires only when the failure state is active:
// Condition for the breakpoint on the e.state.items.map() line
e.state === undefined || e.state.items === undefined
Reload the page. Execution pauses at the breakpoint only when the condition is true, skipping the thousands of times the code runs correctly. In the Scope panel, expand Local and Closure to inspect all live variable values. The minified name e will show its full runtime value — likely a React component instance, Redux state slice, or custom class with known property shapes.
// Additional console expression to log the scope chain without a pause
// Inject via the Console while paused at the breakpoint:
console.table(Object.entries(e).filter(([k, v]) => v !== null && v !== undefined));
Step 5 — Apply a Local Override for Persistent Tracing
If you need to trace across multiple reloads or share the debugging session, use Local Overrides to serve a patched version of the bundle from your local machine without modifying the deployed artifact.
In the Sources panel, open the Overrides tab (next to Filesystem). Click Select folder for overrides and pick a local directory. Grant DevTools file system permission when prompted. Enable the Enable Local Overrides toggle.
Navigate back to the bundle file. Right-click the tab and choose Save for overrides. The file is saved locally. DevTools now serves your local copy instead of the CDN version. Open the local file, navigate to the failure offset, and inject a tracing statement:
// Injected at the pretty-printed failure line in the overridden bundle
if (!e.state || !e.state.items) {
console.warn('[DEBUG] items undefined; component state at this point:', JSON.stringify(e.state));
debugger; // Pauses execution unconditionally — remove after session
}
Set window.__debug = true in the Console if you want to gate the trace behind a flag. Reload. The override persists across reloads until you disable it. An orange dot on the file tab in the Sources panel confirms the override is active.
Verification
Confirm the breakpoint or override is working before investing in deeper analysis:
// Run in DevTools Console immediately after the page loads
// Verifies that the patched scope check ran at least once
if (window.__dbgItemsUndefined) {
console.log('Trace fired — check Console for state snapshot');
} else {
console.warn('Trace did NOT fire — re-check the offset or the condition expression');
}
// In the override, set this flag when the trace fires:
// window.__dbgItemsUndefined = true;
After identifying the root cause, verify the fix in a staging environment using the full source — not the minified version. If retroactive source maps are available, upload them and confirm symbolicated frames appear in your error monitoring dashboard before closing the incident.
Edge Cases & Gotchas
- Pretty-print produces incorrect structure for heavily obfuscated code. Some bundlers apply string-array rotation or
eval-based packing on top of standard minification. The{}formatter cannot handle these patterns. In those cases, use the raw column offset and grep for surrounding unique string literals instead of relying on the formatted view. - Column offsets differ between bundle versions. A single character added or removed anywhere before the failing line shifts every subsequent column number. Never reuse a saved offset against a different build hash — always re-derive from the live stack trace.
- Local Overrides persist after your session. If you forget to disable the override toggle, the browser silently continues serving the local file on every subsequent load. Check the orange dot on file tabs and disable via the Overrides panel when done.
- CSP blocks
debuggerinjection via the Console. AContent-Security-Policyheader withscript-src 'none'or without'unsafe-eval'prevents inline Console expressions from injecting code. Local Overrides bypass this because the file is served from the local filesystem, not as an eval string.
FAQ
Does Pretty Print alter the actual minified bundle on the server?
No. It is a client-side display transformation inside DevTools. The server continues to serve the original compressed bytes. The pretty-printed rendering exists only in the DevTools UI for the duration of your session.
How do I trace errors when source maps are blocked by CORS?
CORS restrictions on .map files prevent the browser from loading them automatically. Use Local Overrides to serve a locally patched version of the bundle from your machine — this bypasses CORS entirely because the file now loads from the local override path rather than the CDN.
Can I set precise breakpoints on a single-line bundle without pretty-printing?
Yes, with column-level breakpoints. In Sources, right-click anywhere in the single-line view and choose Add column breakpoint — Chrome supports column-precise breakpoints even on a single line. Alternatively, inject debugger; via Local Overrides at the known byte offset. For complex sessions, though, pretty-printing first and then re-anchoring is considerably faster.