Fixing Source Map Not Found Warnings in DevTools
You open the Sources panel to read an original file and instead find a yellow banner reading “Source map detected but could not be loaded” — or worse, silence and a wall of minified output. This how-to isolates one specific failure: DevTools did request a map but the request came back missing or malformed, so the sourceMappingURL pointer is the thing to repair. It sits under Debugging Source Maps in Browser DevTools, part of the broader Source Map Generation & Stack Trace Debugging reference. If the bundle emits the wrong path in the first place, Fixing Incorrect Source Map Paths After CDN Deployment covers the emit side, and Using Chrome DevTools to Debug Minified Bundles is your fallback while the map is still broken.
Symptom / Trigger
The trigger is a warning that surfaces in one of two places. In Chrome, the Console’s Issues tab logs a DevTools failed to load source map entry the moment the page finishes loading the offending script. In Firefox, the Debugger toolbar shows a small warning triangle beside the mapped file. The exact string Chrome writes is stable enough to grep your session recordings for:
DevTools failed to load source map: Could not load content for
https://cdn.example.com/assets/app.9f2c1.js.map:
HTTP error: status code 404, net::ERR_HTTP_RESPONSE_CODE_FAILURE
The tell-tale detail is that DevTools names the exact .map URL it tried. That URL is not something you typed — it is computed from the //# sourceMappingURL= comment (or a SourceMap: response header) resolved against the script’s own directory. When symbolication silently fails, that failing frame is what you get in the error tracker and in the Console stack:
Uncaught TypeError: Cannot read properties of undefined (reading 'total')
at o (https://cdn.example.com/assets/app.9f2c1.js:1:44820)
at HTMLButtonElement. (app.9f2c1.js:1:52310)
Single-letter function names (o), everything on line 1, and a five-digit column are the signature of a bundle DevTools could not map. The warning and the useless frame are the same problem viewed from two panels.
It is worth pinning down which warning you actually have before you touch the build, because two very different failures share the “not found” language. A true not-found is a network status — the .map never arrived. A content failure arrives with a 200 but unusable JSON, and Chrome phrases both with the words “Could not load content”, which sends people down the wrong path. Read the status code in the message, not just the leading verb: status code 404 means the pointer is wrong; a parse error means the pointer is right but what it returned is not a map.
Root Cause Explanation
DevTools treats the sourceMappingURL comment as a URL relative to the script that contains it. It takes the directory of the script, joins the comment value onto it, and issues a plain GET. Anything that breaks that join — a moved CDN path, a comment written for a different directory layout, a build that emitted an absolute filesystem path — produces a URL that resolves but 404s, or a URL that is syntactically wrong and never fetches at all. The most common broken pattern is a comment that assumes the map sits next to the script when the deploy step actually moved maps to a separate, often access-controlled, prefix:
// Tail of app.9f2c1.js as deployed. The comment is relative...
//# sourceMappingURL=app.9f2c1.js.map
// ...but the build pipeline uploaded maps to /sourcemaps/, not /assets/.
// DevTools computes: https://cdn.example.com/assets/app.9f2c1.js.map -> 404
// The map actually lives at: https://cdn.example.com/sourcemaps/app.9f2c1.js.map
There are three ways the join goes wrong in practice. First, a relative comment after a path change: the map moved but the comment did not, so the computed URL misses. Second, a stale hash: the comment names app.7b3d9.js.map while the deployed script is app.9f2c1.js, because a partial deploy shipped a new bundle without its matching map. Third, an absolute local path baked in by a misconfigured bundler — sourceMappingURL=/home/ci/build/app.js.map — which is meaningless to a browser. All three end at the same warning, and the fix in every case is to make the pointer resolve to a real, reachable .map.
The reason this class of bug is so persistent is that the comment and the map travel through different parts of a deploy. The bundler writes the comment at build time, guessing where the map will live, while a separate upload or CDN-sync step decides where it actually lands. Nothing checks that the guess and the destination agree, so a routine change to a CDN prefix or an asset-hashing scheme quietly desynchronizes them. DevTools is the first place the mismatch becomes visible, which is why the warning so often appears after an otherwise green deploy.
Step-by-Step Fix
- Read the tail of the deployed script to see the exact comment DevTools is parsing.
# Fetch only the last bytes of the bundle to reveal its sourceMappingURL comment.
curl -s https://cdn.example.com/assets/app.9f2c1.js | tail -c 200
# Look for: //# sourceMappingURL=<value> (missing = a different problem entirely)
- Reproduce the browser’s resolution to prove the computed URL is the one that 404s.
# Join the comment onto the script directory exactly as DevTools does, then probe it.
SCRIPT="https://cdn.example.com/assets/app.9f2c1.js"
COMMENT="$(curl -s "$SCRIPT" | grep -o 'sourceMappingURL=[^ ]*' | cut -d= -f2)"
curl -s -o /dev/null -w '%{http_code}\n' "$(dirname "$SCRIPT")/$COMMENT" # 404 confirms it
- Confirm where the map really lives so you know the correct target path.
# Probe the path your pipeline actually uploaded to; 200 means this is the real home.
curl -s -o /dev/null -w '%{http_code}\n' https://cdn.example.com/sourcemaps/app.9f2c1.js.map
- Fix the emitted comment at the source by pointing the bundler at the real map location.
// webpack.config.js — make the emitted comment match where maps are served.
module.exports = {
output: {
// Absolute URL prefix DevTools will resolve the comment against.
sourceMapFilename: '[file].map',
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
},
// sourceMappingURL is rewritten to the served path, not the on-disk one.
devtool: 'source-map',
};
- For a live incident, override the comment in the browser without redeploying.
// Enable Sources > Overrides, then "Save for overrides" on app.9f2c1.js and
// rewrite its final line to a reachable map URL you control:
//# sourceMappingURL=https://cdn.example.com/sourcemaps/app.9f2c1.js.map
console.log('%coverride active', 'color:#16a34a'); // reload; DevTools re-reads the comment
The override in step five is session-local and never touches the deployed artifact, which makes it the safe first move during an incident: you confirm the content of the map is good before you spend a deploy cycle fixing the emitted path. Once the browser resolves the rewritten comment and original files appear in the tree, you know the map itself is healthy and the only remaining work is making step four’s build change ship the same URL for everyone.
Verification
Prove the pointer resolves before you close the incident. The scripted check below mirrors what DevTools does internally — compute the URL, fetch it, and confirm it is a version 3 map with real content — so a green run guarantees the warning is gone:
# End-to-end check that the emitted comment now resolves to a valid map.
SCRIPT="https://cdn.example.com/assets/app.9f2c1.js"
COMMENT="$(curl -s "$SCRIPT" | grep -o 'sourceMappingURL=[^ ]*' | cut -d= -f2)"
MAP_URL="$(dirname "$SCRIPT")/$COMMENT"
# 1. The computed URL must return 200, not the 404 behind the warning.
test "$(curl -s -o /dev/null -w '%{http_code}' "$MAP_URL")" = "200" \
&& echo "OK: map reachable" || echo "FAIL: still 404/403"
# 2. It must parse as a v3 map that actually carries source content.
curl -s "$MAP_URL" | node -e '
const m = JSON.parse(require("fs").readFileSync(0));
const ok = m.version === 3 && Array.isArray(m.sourcesContent) && m.sourcesContent.some(Boolean);
console.log(ok ? "OK: v3 map with content" : "FAIL: bad version or empty content");
'
Then confirm it interactively: reload the page with DevTools open, expand the Sources tree, and the original files should reappear as lighter-coloured entries beneath the minified bundle. Clicking a stack frame now lands on readable source instead of column 44820 of line 1.
Do not stop at the file appearing, though — a resolved file does not guarantee a resolved position. Set a breakpoint in the recovered original file, trigger the code path, and confirm execution pauses on the line you expect rather than a few lines away. If the file loads but the breakpoint binds to the wrong line, the pointer is fixed but the map is stale relative to the deployed bundle, and you are now debugging a build-versioning problem rather than a not-found one. Catching that distinction here saves you from declaring victory on a map that still lies about where your code is.
Edge Cases & Gotchas
- A
SourceMap:response header overrides the in-bundle comment. If the tail of the file shows no comment but symbolication still fails, inspect the script’s response headers in the Network panel — the header may point at a stale path while the comment channel is empty. - A
200that returns HTML is worse than a404: a CDN configured to serveindex.htmlfor unknown paths hands DevTools a page instead of JSON, producing a “not valid JSON” parse error rather than a not-found one. Check thecontent-typeisapplication/json, nottext/html. - Access-controlled maps 404 for the browser even when they exist. If the map is intentionally private, drop the comment and symbolicate server-side rather than fighting the warning — the not-found banner is expected behaviour there.
- Browser and Network caches can keep serving the old comment after you fix the build. Enable Network → Disable cache while DevTools is open and hard-reload, or you will verify against a stale bundle and think the fix failed.
FAQ
Why does DevTools show the map URL as 404 when I can open that exact URL in a new tab?
Almost always a caching or credential difference. A new tab may send cookies or hit a CDN edge that the DevTools fetch does not, or the browser is serving a cached copy of the old bundle whose comment pointed elsewhere. Enable Network → Disable cache, hard-reload, and re-read the tail of the script from the Console to confirm you are looking at the currently deployed comment rather than a cached one.
Can I fix a not-found warning without redeploying the bundle?
Yes. Use Chrome’s per-file “Add source map…” context-menu action in the Sources panel to paste a reachable map URL by hand, or enable Local Overrides and rewrite the //# sourceMappingURL= comment to a URL you control. Both are session-local and safe to run against live production traffic, which lets you confirm the map content is good before committing to a build change.
How do I tell a missing map apart from an empty one?
A missing map returns 404/403 and produces the “Could not load content … status code 404” wording. An empty map returns 200 but was built with nosources-source-map, so its sourcesContent array is absent — DevTools resolves line numbers but shows a “Could not load content” placeholder instead of code. The verification script’s second check distinguishes them: reachability passes but the content assertion fails.
Related
- Debugging Source Maps in Browser DevTools — the parent guide covering Workspaces, Overrides, and all four DevTools map failure signatures.
- Fixing Incorrect Source Map Paths After CDN Deployment — the emit-side fix when the comment is written for the wrong origin.
- Using Chrome DevTools to Debug Minified Bundles — how to keep working while the map is still broken, using pretty-print.
- Source Map Generation & Stack Trace Debugging — the wider reference on generating, uploading, and symbolicating source maps.