Source Maps in Production: Debug Minified Code

Learn how to implement source maps in production to debug minified JavaScript code effectively. Covers Webpack, Vite, and esbuild configuration, security strategies, and error tracking integration.
While I was investigating a critical production bug the other day, I realized something frustrating: the error stack trace pointed to line 1, character 84,392 in a minified JavaScript file. I had no idea what code was actually breaking. Little did I know that this moment would send me down a path to understanding one of the most underutilized tools in modern web development—production source maps.
Why Source Maps Matter in Production
I was once guilty of thinking source maps were only for development. After all, you have readable code locally, hot module replacement, and all the debugging tools you need right there in your IDE. But production is where real users encounter real problems. When something breaks at 3 AM and your error monitoring service shows you Cannot read property 'x' of undefined at a.b.c (bundle.min.js:1:84392), you're completely blind.
The stakes are higher in production. A single bug can affect thousands of users simultaneously. I cannot stress this enough! Without source maps, you're essentially debugging with one hand tied behind your back, trying to reverse-engineer minified code while your users are waiting for a fix.
How Source Maps Work: Mapping Minified to Original Code
Here's what I realized when I finally decided to understand source maps: they're essentially JSON files that create a mapping between your original source code and the transformed, minified version running in production. When an error occurs, the browser (or error tracking service) uses this mapping to translate the cryptic minified location back to the actual line in your original TypeScript or JavaScript file.
The source map file contains a few key pieces of information:
- Sources: An array of original file paths
- Names: Original variable and function names
- Mappings: A compressed string using Base64 VLQ encoding that maps positions in the minified file to positions in the original files
- SourcesContent: The actual original source code (optional)
When your browser encounters a source map, it downloads it in the background and uses it to reconstruct readable stack traces in the DevTools console. Fascinating!

Generating Source Maps: Webpack, Vite, and esbuild Configuration
Let's look at how to actually generate source maps across different build tools. I've worked with all three of these, and each has its own quirks.
For Webpack, here's the configuration I came across that works reliably in production:
// webpack.config.js
module.exports = {
mode: 'production',
devtool: 'hidden-source-map', // Generates map but doesn't reference it
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
sourceMapFilename: 'sourcemaps/[file].map', // Store in separate directory
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: true, // Must enable this
},
}),
],
},
};For Vite, the configuration is wonderfully simpler:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
sourcemap: 'hidden', // or 'true' for public maps
rollupOptions: {
output: {
sourcemapExcludeSources: false, // Include original sources
},
},
},
});The hidden option is crucial here. It generates the source map files but doesn't add the //# sourceMappingURL= comment to your production bundles. In other words, regular users won't download the source maps, but your error tracking service can still use them.
Security Strategies: Hidden vs Public Source Maps
When I first deployed source maps to production, I made a rookie mistake: I used public source maps that anyone could access. This meant exposing my entire source code to anyone who opened DevTools. Not ideal!
Here are the strategies I've learned:
Hidden Source Maps: Generate the .map files but don't reference them in your bundle. Upload these to your error tracking service (Sentry, Rollbar, etc.) which uses them server-side to translate errors. Users never see your source code. This is my go-to approach for most projects.
Public Source Maps: Include the sourceMappingURL comment in your bundles. Anyone can view your original code in DevTools. Use this only if your code isn't proprietary or if developer experience is paramount (like open-source projects).
Authentication-Protected Source Maps: Serve source maps behind authentication, so only your team can access them. Luckily we can configure this with reverse proxies or CDN rules.

Integrating Source Maps with Error Tracking Services
The real power of production source maps comes from integrating them with error tracking services. I use Sentry in most of my projects, but the concept applies to other services too.
Here's a practical example of uploading source maps to Sentry during your build process:
// scripts/upload-sourcemaps.ts
import * as SentryCli from '@sentry/cli';
async function uploadSourceMaps() {
const cli = new SentryCli();
await cli.releases.new('my-app@1.2.3');
await cli.releases.uploadSourceMaps('my-app@1.2.3', {
include: ['./dist'],
urlPrefix: '~/assets', // Match your CDN structure
rewrite: true,
ignore: ['node_modules'],
});
await cli.releases.finalize('my-app@1.2.3');
console.log('Source maps uploaded successfully!');
}
uploadSourceMaps().catch(console.error);What I love about this approach is that your source maps never leave your build server. They go straight to Sentry's secure storage, and when errors occur, Sentry translates them server-side before showing you the readable stack trace.
Debugging Production Issues with Browser DevTools
Even with hidden source maps, you can still debug production issues locally. Here's the workflow I use:
When I encounter a production bug, I download the source map files from my CI artifacts or error tracking service. Then I manually load them in Chrome DevTools by:
- Opening DevTools and navigating to the Sources panel
- Right-clicking in the file tree and selecting "Add source map..."
- Providing the URL or local path to the
.mapfile
The browser then reconstructs the original source code, and you can set breakpoints, step through code, and inspect variables as if you were debugging locally. Wonderful!
Source Map Upload Automation and CI/CD Integration
I was once guilty of manually uploading source maps after each deployment. This was error-prone and easy to forget. Now I automate everything in my CI/CD pipeline.
Here's a GitHub Actions workflow example that builds your app and uploads source maps:
// .github/workflows/deploy.yml
name: Deploy with Source Maps
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Build production bundle
run: npm run build
env:
NODE_ENV: production
- name: Upload source maps to Sentry
run: |
npx @sentry/cli releases new "$GITHUB_SHA"
npx @sentry/cli releases files "$GITHUB_SHA" upload-sourcemaps ./dist --url-prefix '~/assets'
npx @sentry/cli releases finalize "$GITHUB_SHA"
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: my-org
SENTRY_PROJECT: my-project
- name: Deploy to production
run: npm run deploy
- name: Clean up source maps
run: rm -rf ./dist/**/*.mapNotice how I delete the source map files before deploying? This ensures they never accidentally get uploaded to your CDN or public hosting.
Best Practices for Production Source Map Management
After working with source maps across dozens of projects, here are the practices I swear by:
Version your source maps: Always tie source maps to specific release versions. When debugging a week-old error, you need the source maps that match that exact build.
Store them securely: Keep source maps in a separate, access-controlled location. I typically store them in a private S3 bucket with 90-day retention.
Monitor map file sizes: Source maps can be large, especially if you include original sources. Use compression and consider excluding node_modules to keep sizes manageable.
Test your integration: Before you need it in an emergency, verify that your error tracking service can actually use your source maps. Create a test error and confirm the stack trace is readable.
Document your process: Make sure your entire team knows where source maps are stored and how to access them. You don't want to be the only person who can debug production issues.
The return on investment for properly implementing source maps is massive. I've seen production debugging time drop from hours to minutes once teams have readable stack traces. The initial setup takes maybe an afternoon, but it pays dividends every single time something breaks in production.
And that concludes the end of this post! I hope you found this valuable and look out for more in the future!