Source Maps Explained for Debugging

May 28, 20266 min read

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:

  1. Original file locations: The source files and their paths before transformation
  2. Generated file locations: The positions in the compiled/minified output
  3. 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:

FieldPurpose
versionSource map spec version (always 3)
sourcesArray of original source file paths
sourcesContentOptional: original source text (enables in-browser debugging without file access)
namesOriginal identifier names before mangling
mappingsVLQ-encoded position mappings
fileThe 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:

  1. Detects the sourceMappingURL comment or header in the JavaScript file
  2. Fetches the source map file
  3. Decodes the VLQ mappings
  4. Reconstructs the original source files in the Sources panel
  5. 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 .map files and adds the sourceMappingURL comment. Visible in DevTools and deployable.
  • 'inline': Embeds the source map as a base64 data URI in the JavaScript file. No separate .map file. Increases bundle size significantly—not recommended for production.
  • 'hidden': Generates .map files but omits the sourceMappingURL comment. 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.