You've deployed a production build, an error lands in your dashboard, and your error tracker can't decode the stack trace. The DevTools console shows a warning: "No sources are declared in this source map" — or you see the original JavaScript file listed but it's not actually embedded in the map. This error means your bundler generated a source map, but it's missing the critical data your browser (or error tracker) needs to translate minified code back to your original source. There are two distinct failure modes here, and they need different fixes.
A functional source map is a three-part system: the sources array lists which files were bundled, the sourcesContent array holds the actual source code, and the mappings field provides the coordinate lookups. When "No sources are declared" appears, it typically means either the sources array is empty or sourcesContent is null and the original files can't be fetched from disk. Understanding which one you're hitting — and why — is the first step to fixing it.
How source maps store source code
A source map is a JSON file that bridges minified code back to its original form. Here's a minimal example:
{
"version": 3,
"file": "app.min.js",
"sources": ["src/app.js", "src/utils.js"],
"sourcesContent": [
"function handler() { return 42; }",
"export const helper = () => 'hi';"
],
"names": ["handler", "helper"],
"mappings": "AAAA,QAAQ,CAAC,CAAC;AACV,OAAO,CAAC,MAAM"
}
The key fields:
sources— array of original file paths (relative to sourceRoot).sourcesContent— array of the actual source code strings, one per file insources. Can benullor omitted entirely.mappings— compressed lookup table (VLQ-encoded) that says "character N in the minified output came from line X, column Y in source file Z."sourceRoot— optional base path for resolving relative source paths.
When sources is empty or sourcesContent is missing, browsers and error trackers can't decode frames. Your error tracker sees a position in the minified bundle but has nowhere to look it up — which is the whole problem source maps exist to solve, and why the trace stays stuck in minified form.
The two failure modes
Mode 1: Empty sources array
The bundler ran but didn't record which files it bundled. This is rare but happens if:
- A bundler misconfiguration skipped the source map generation step entirely.
- A post-processing tool (minifier, obfuscator) regenerated the map without preserving the sources list.
- The map was truncated or corrupted during upload.
Check: Fetch the map file and run:
curl https://your-cdn.com/app-abc123.js.map | jq '.sources'
If it returns [] or null, the sources array is empty.
Mode 2: Missing or null sourcesContent
The sources array lists files, but the actual code is missing. The bundler generated a reference map (names only) instead of an embedded map (with code). This happens when:
- Your bundler config omits sourcesContent by design (for size or security).
- The browser tries to fetch the original files from disk but they're not deployed alongside the bundle.
- The
sourceRootpath is wrong or relative paths don't resolve.
Check:
curl https://your-cdn.com/app-abc123.js.map | jq '.sourcesContent | length'
If it returns null or 0, sourcesContent is missing.
Why this happens: bundler configuration
Most source map errors stem from how you configure code bundling. Here's what each major bundler does by default — and how to fix it.
Vite
By default, Vite includes full source code in maps. If you're seeing empty sources:
// ❌ This omits sourcesContent
export default defineConfig({
build: {
sourcemap: 'hidden' // Generates map without embedding code
}
});
Fix: Use true or 'full' for production maps:
// ✅ Embeds full source
export default defineConfig({
build: {
sourcemap: true // or 'full'
}
});
For details on all Vite options, see Vite source maps.
Webpack
Webpack's devtool option controls what goes into the map. Several variants omit sources:
// ❌ These skip sourcesContent or generate reference-only maps
devtool: 'cheap-source-map' // No source code, just line/col
devtool: 'eval-source-map' // Inline eval, doesn't scale to files
devtool: 'hidden-source-map' // Generates map but doesn't link it
Fix: Use source-map for production with full code:
// ✅ Production-grade full map
module.exports = {
mode: 'production',
devtool: 'source-map'
};
If you upload maps separately to your error tracker (not your CDN), use hidden-source-map and ensure maps are uploaded in CI. See Webpack source maps for the full walkthrough.
Esbuild
Esbuild omits sourcesContent by default to save space. Enable it explicitly:
// ❌ Generates reference map without code
esbuild.buildSync({
sourcemap: true, // Reference only
minify: true
});
// ✅ Include source code
esbuild.buildSync({
sourcemap: true,
sourcesContent: true, // Embed original files
minify: true
});
Rollup
Rollup's sourcemapExcludeSources flag controls this:
// ❌ Omits sourcesContent for size
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
sourcemap: true,
sourcemapExcludeSources: true // Strips source code
}
};
Fix: Remove the flag or set it to false:
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
sourcemap: true
// sourcemapExcludeSources: false (default)
}
};
Deployment and path resolution issues
Even with sourcesContent enabled, the error can occur if your sourceRoot is misconfigured or relative source paths don't resolve at deployment time.
Scenario: Your map was built locally with sourceRoot: "../src", but the deployed bundle folder structure doesn't match:
{
"sources": ["app.js", "utils.js"],
"sourceRoot": "../src",
// Maps try to load from ../src/app.js relative to map location
// But deployed files are at /src/app.js — path mismatch
}
Fix: Ensure sourceRoot is absolute or matches your CDN structure:
{
"sources": ["app.js", "utils.js"],
"sourceRoot": "/src/", // Absolute path
// Or use the file's full URL from the error event
}
Error trackers like LightTrace can also work around this by using the release version — if you upload maps by release tag, the error tracker uses the uploaded map file (which has correct paths) instead of trying to fetch from your CDN.
Security: the sourcesContent tradeoff
Including source code in production maps is a security decision. Embedding sourcesContent means your original, unminified code is in a .map file — and if that file is publicly accessible, anyone can read your full source code.
Never serve .map files from your public CDN. Configure your CDN or web server to block requests to *.js.map from the public internet. You can still generate maps locally and upload them privately to your error tracker.
The standard practice:
- Generate full maps locally with
sourcesContent: true. - Upload to your error tracker (LightTrace, Sentry, etc.) in your CI/CD pipeline.
- Keep maps off your public CDN — configure cache rules to reject
.maprequests. - Your error tracker decodes stack traces server-side using the maps; users never see them.
This gives you full debugging power (readable stack traces) without exposing your source.
If you must omit sourcesContent for compliance reasons, the tradeoff is that stack frames show file/line but not the actual code — your error tracker can't inline the problematic lines in the dashboard.
Inspecting a map to diagnose the issue
To quickly check what's in a map file, fetch it and inspect the key fields:
# See what sources are listed
curl https://your-cdn.com/app.js.map | jq '.sources'
# Check if sourcesContent exists and has data
curl https://your-cdn.com/app.js.map | jq '.sourcesContent | length'
# See the sourceRoot setting
curl https://your-cdn.com/app.js.map | jq '.sourceRoot'
If sources is [] or null, your bundler skipped source recording. If sourcesContent is null or has length 0, sourcesContent is missing. Cross-check your bundler config against the section above.
Use jq -r '.sourcesContent[0]' map.js.map to print the first few lines of actual source code from the map — confirms the file is really there.
Common causes in a pipeline
Source maps work fine in isolation but can break when chained through a build pipeline:
- Minifier after bundler: Your bundler generates a full map, then a downstream minifier (like Terser) regenerates it without sourcesContent.
- Multiple map chains: Some tools consume a
.mapfile and produce their own, losing sourcesContent in the second pass. - Environment-specific builds: Your dev build includes sourcesContent, but your production build config strips it.
Prevention: Use a single, unified build command for production. Keep map generation and sourcesContent settings in one place (your bundler config, not scattered across scripts).
If you're using Next.js, the framework handles source maps for you — enable them once in next.config.js and the entire pipeline (pages, API routes, server code) generates correct maps automatically.
Fix checklist
- Check your bundler config — enable
sourcemap: true(Vite),devtool: 'source-map'(Webpack), or setsourcesContent: true(Esbuild). - Verify the generated map file — run
jq '.sources, .sourcesContent | length'on a built map to confirm data is there. - Upload to your error tracker — maps must reach LightTrace (or Sentry) in your CI pipeline, not just sit in your CDN.
- Tag your releases — tag every deploy with a version (e.g.,
web@1.2.3); errors need that tag to find matching maps. - Block map file downloads — configure your CDN/web server to deny public access to
*.mapfiles. - Test end-to-end — trigger an error in production and verify your error tracker shows readable, decoded stack frames.
Start tracking errors in minutes
Set up source maps in your build, upload them to LightTrace, and ship production stack traces you can actually read — start free and decode your first error in minutes.
No sources are declared means your maps are incomplete — but it's always fixable in one or two config lines. Once you've gotten them right, every production error becomes debuggable, and your team's MTTR (mean time to resolution) drops dramatically.