Skip to main content

Source Maps and Stack Traces: Debug Like a Pro

When you deploy a React app to production, your code is minified: variable names are shortened to a, b, c, function names disappear, and whitespace is removed. This makes the bundle smaller but the stack traces unreadable. A minified error looks like Cannot read property of undefined at a (bundle.e8f3d.js:1:50000) instead of Cannot read property userId of undefined at getUserData (src/api.js:42:12). Source maps are files that map minified code back to original source, so Sentry can show you readable stack traces with your original TypeScript or JSX.

How Minification and Source Maps Work

When your build tool (Webpack, Vite, esbuild) processes your code, it performs several transformations:

  1. Transpilation: Converts TypeScript to JavaScript, JSX to React.createElement calls.
  2. Bundling: Combines multiple files into a single bundle.
  3. Minification: Renames variables to single letters, removes comments, compresses whitespace.
  4. Source map generation: Creates a .map file that tracks every change.

A source map is a JSON file that looks like this:

{
"version": 3,
"file": "bundle.e8f3d.js",
"sourceRoot": "",
"sources": ["src/api.js", "src/utils.js"],
"names": ["getUserData", "userId", "fetch", "apiUrl"],
"mappings": "AAAA,SAASA,EAAAC..."
}

Each mapping encodes: "in the minified file at position X, the original code came from source file Y at line Z and column C with name N." This allows Sentry to reverse the minification and show you the original line of code.

Source Maps in the Build Pipeline

Most React build tools generate source maps automatically, but you need to configure them correctly for production. In a Vite project, edit vite.config.js:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
plugins: [react()],
build: {
sourcemap: true, // Enable source map generation
minify: "terser", // Use Terser for minification (default)
outDir: "dist", // Output directory
},
});

For Create React App, source maps are enabled by default in production builds:

GENERATE_SOURCEMAP=true npm run build

This creates files like bundle.js and bundle.js.map in your build output. The bundle.js file includes a comment at the bottom:

//# sourceMappingURL=bundle.js.map

This tells browsers and error tracking services where to find the source map.

Uploading Source Maps to Sentry

You have two options: upload source maps during your build or use a Sentry integration with your CDN/hosting platform.

Option 1: Upload via Sentry CLI

After building, upload source maps to Sentry:

sentry-cli releases files upload-sourcemaps ./dist --release v1.0.0

This command:

  1. Creates a release named v1.0.0 in Sentry.
  2. Uploads all source maps from the dist folder.
  3. Associates the source maps with your app version.

Add this to your CI/CD pipeline (GitHub Actions example):

- name: Build React app
run: npm run build

- name: Upload source maps to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
npx sentry-cli releases create v${{ github.run_number }}
npx sentry-cli releases files upload-sourcemaps \
./dist \
--release v${{ github.run_number }}

Ensure your SENTRY_AUTH_TOKEN is set as a GitHub secret. Generate it at https://sentry.io/settings/auth-tokens/.

Option 2: Use Sentry's Next.js or Remix Plugin

If you use Next.js or Remix, Sentry provides a build plugin that auto-uploads source maps:

npm install @sentry/nextjs --save-exact
npx @sentry/wizard@latest -i nextjs

Option 3: Use Vercel or Netlify Integration

If deployed to Vercel or Netlify, add the Sentry integration via their dashboard. It auto-uploads source maps on every deploy.

Verifying Source Maps in Sentry

After uploading, check the Sentry dashboard:

  1. Go to Release & Health > Releases.
  2. Click your release version.
  3. Under Artifacts, you should see your source maps listed.

Trigger a test error in production and check the stack trace. If source maps are linked correctly, you will see:

TypeError: Cannot read property of undefined

at getUserData (src/api.js:42)
in getUserData at src/api.js:42:12
in fetchUserProfile at src/user.js:18:5

Instead of the minified version:

TypeError: Cannot read property of undefined

at a (bundle.e8f3d.js:1:50000)

Security Considerations

Source maps contain your original source code, so you must not expose them on your production website. Do NOT include .map files in your deployed assets. Instead:

  1. Upload maps only to Sentry: Keep source maps private. Only Sentry and your team access them.
  2. Strip maps from the bundle: In your build, generate maps for upload but do not include them in dist/:
// vite.config.js
export default defineConfig({
build: {
sourcemap: "hidden", // Generate maps but do not reference them in JS
},
});

With "hidden", Sentry can still use them (you upload them explicitly), but browsers never request them.

  1. Delete maps after upload: In CI, delete local .map files after upload:
npm run build
sentry-cli releases files upload-sourcemaps ./dist --release v1.0.0
rm -rf dist/**/*.map

Debugging a Production Error Using Source Maps

A user reports that clicking a button shows "Something went wrong." Here is how to debug it:

  1. Open Sentry and find the error in Issues.
  2. Click the error to see the full stack trace (now readable thanks to source maps).
  3. Click the file name in the stack trace (e.g., src/api.js:42) to view the original source code.
  4. Read the surrounding code and breadcrumbs to understand what happened.
  5. Check the User and Device tabs to see which browsers or OS versions are affected.
  6. Click Session Replay to watch the user's actions before the crash.

Example stack trace from Sentry:

TypeError: Cannot read property userId of undefined

at getUserData (src/api.js:42:12)
at handleButtonClick (src/components/UserProfile.jsx:18:5)
at onClick (react-dom.js:1234:567)

You click on src/api.js:42 and see:

const { userId } = response.data.user; // Line 42

The error tells you response.data is undefined. Check the API endpoint and error handling, and you find the issue quickly.

Key Takeaways

  • Minification makes bundles smaller but stack traces unreadable; source maps reverse this.
  • Source maps are generated automatically by build tools and map minified code back to original source.
  • Upload source maps to Sentry via CLI, a build plugin, or a hosting integration.
  • Keep source maps private: use sourcemap: "hidden" to prevent them from being exposed publicly.
  • Readable stack traces drastically reduce debugging time by showing exact file names, line numbers, and variable names.

Frequently Asked Questions

Do source maps increase bundle size?

No. Source maps are never included in your production bundle. They are uploaded separately to Sentry. The sourcemap: "hidden" option ensures Sentry can use them while keeping them out of the deployed assets.

What happens if source maps are missing when an error occurs?

Sentry still receives the error and shows the minified stack trace, which is hard to read. You lose the context of file names and line numbers. Always ensure source maps are uploaded before deploying a release.

Can I upload source maps without uploading my source code?

Yes. Source maps do not include the full source code, only enough information for a debugger to map positions. However, a determined attacker could reverse-engineer significant portions. For maximum security, use sourcemap: "hidden" and upload only to Sentry.

How do I handle source maps for dynamically imported chunks?

Vite and Webpack generate separate source maps for each chunk (e.g., chunk-a123.js.map). The Sentry CLI automatically uploads all .map files in the dist folder, so you do not need to handle chunks separately. They are all linked to the same release.

What if I cannot upload source maps in CI/CD?

You can upload them manually for debugging. Run:

sentry-cli releases files upload-sourcemaps ./dist --release v1.0.0

This is useful for emergency releases, but automating it in CI is the best practice.

Further Reading