Source Maps Explained for Debugging
Production JavaScript bears little resemblance to the source code developers write. Features like TypeScript types, JSX syntax, and long descriptive variable names are stripped during compilation and minification. When errors occur in production, the resulting stack traces reference compressed, mangled code that is nearly impossible to read. Source maps solve this problem by providing a mapping between the transformed output and the original source, enabling browsers and debugging tools to reconstruct readable stack traces and even step through original code in DevTools.
What Source Maps Contain
A source map is a JSON file (typically .js.map) that encodes three relationships:
- Original file locations: The source files and their paths before transformation
- Generated file locations: The positions in the compiled/minified output
- Mapping between them: A compact encoding that maps every position in the generated output back to a position in the original source
The mapping uses a variable-length quantity encoding called VLQ (Variable-Length Quantity). Here is what a simplified source map looks like:
{
"version": 3,
"sources": ["app.ts", "utils.ts"],
"sourcesContent": ["const greet = (name: string) => ...", "export function formatDate(...)"],
"names": ["greet", "formatDate", "userName"],
"mappings": "AAAA;AACA,SAAS,GAAG...",
"file": "bundle.js"
}
Key fields:
| Field | Purpose |
|---|---|
version | Source map spec version (always 3) |
sources | Array of original source file paths |
sourcesContent | Optional: original source text (enables in-browser debugging without file access) |
names | Original identifier names before mangling |
mappings | VLQ-encoded position mappings |
file | The generated file this map corresponds to |
The browser discovers the source map through a special comment at the end of the generated file: //# sourceMappingURL=bundle.js.map, or via the SourceMap HTTP header.
How Browsers Use Source Maps
When DevTools is open and source maps are enabled (the default), the browser:
- Detects the
sourceMappingURLcomment or header in the JavaScript file - Fetches the source map file
- Decodes the VLQ mappings
- Reconstructs the original source files in the Sources panel
- Translates stack trace positions from generated coordinates to original coordinates
This reconstruction lets you set breakpoints, inspect variables, and step through code using the original TypeScript or JSX source, even though the browser is actually executing compiled JavaScript.
When DevTools is closed, the browser ignores source maps entirely—they have zero runtime performance impact on end users.
Configuring Source Map Generation
Each build tool has its own source map configuration:
Vite (and Nuxt, which wraps Vite):
// vite.config.ts
export default defineConfig({
build: {
sourcemap: true, // boolean | 'inline' | 'hidden'
},
})
true: Generates.mapfiles and adds thesourceMappingURLcomment. Visible in DevTools and deployable.'inline': Embeds the source map as a base64 data URI in the JavaScript file. No separate.mapfile. Increases bundle size significantly—not recommended for production.'hidden': Generates.mapfiles but omits thesourceMappingURLcomment. Browsers won't fetch them automatically, but error monitoring tools can use them if you upload the files separately.
esbuild:
await esbuild.build({
sourcemap: true, // 'linked' | 'inline' | 'external'
sourcesContent: true, // include original source in the map
})
Next.js enables source maps by default in development. For production, set productionBrowserSourceMaps: true in next.config.js.
Source Maps in Production: Security Considerations
Deploying source maps to your public CDN exposes your original source code—including TypeScript types, comments, and internal file structure—to anyone who opens DevTools. This is acceptable for open-source projects but problematic for proprietary codebases.
Three strategies for handling source maps in production:
Option 1: Hidden source maps + error monitoring. Generate source maps during build but do not deploy them publicly. Upload them to your error monitoring service (Sentry, Bugsnag, Datadog). The hidden mode ensures browsers never fetch the maps, while your monitoring service applies them to incoming stack traces server-side.
Option 2: Restricted access. Deploy source maps to a separate, access-controlled domain or path behind authentication. Only authorized developers can fetch them during debugging.
Option 3: Skip sourcesContent. Omit the sourcesContent field from source maps. The mapping still works for position translation (variable names, line numbers), but the original source text is not embedded. Developers need access to the source repository to view full code in DevTools.
Debugging Without Source Maps
When source maps are unavailable—perhaps a third-party script does not ship them—you can still extract useful information from minified code. Browser DevTools offer a "Pretty print" button ({ }) that reformats compressed code with proper indentation. This does not restore original variable names or types, but it makes the code structure readable enough to follow control flow.
For JavaScript files where you control the build, always generate source maps. The debugging productivity gain outweighs the minimal build configuration effort.
To minify JavaScript while preserving the ability to debug later, use the JS Minifier tool and keep your source files alongside the minified output.