Skip to content

Fix: Sentry Source Maps Not Working — Release Matching, sentry-cli Upload, Vite/Webpack Plugins

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix Sentry source maps errors — minified stack traces, release name mismatch between build and runtime, sentry-cli upload-sourcemaps options, Vite/Webpack/Next.js plugin setup, and hiding maps from public.

The Error

You see a Sentry error and the stack is unreadable:

TypeError: undefined is not an object (evaluating 'a.b')
  at t (https://app.example.com/static/js/main-abc123.js:1:1234)
  at e (https://app.example.com/static/js/main-abc123.js:1:5678)

Instead of:

TypeError: Cannot read property 'name' of undefined
  at fetchUser (src/api/users.ts:42:18)
  at loadProfile (src/pages/Profile.tsx:15:8)

Or the upload command succeeds but Sentry’s UI still shows minified stacks:

$ sentry-cli sourcemaps upload [email protected] ./dist
 Uploaded 12 source maps

But the next error reported is still minified.

Or your plugin processes a build but no maps make it to Sentry:

// vite.config.ts
import { sentryVitePlugin } from "@sentry/vite-plugin";
export default { plugins: [sentryVitePlugin({ org: "...", project: "..." })] };

Or maps appear in production HTML, exposing your source:

<script>...</script>
<!--# sourceMappingURL=main-abc123.js.map -->
<!-- Anyone can download /static/js/main-abc123.js.map -->

Why This Happens

Sentry symbolicates minified stack traces by matching:

  1. The URL of the JS file (https://app.example.com/static/js/main-abc123.js).
  2. The release name reported by the SDK ([email protected]).
  3. An artifact uploaded for that release with the same URL.

If any of those don’t match, symbolication fails silently — Sentry shows the minified stack with no warning. Common causes:

  • Release mismatch. The SDK reports [email protected] but you uploaded for [email protected].
  • URL mismatch. Artifact uploaded as main.js but runtime is main-abc123.js (or vice versa).
  • Maps not actually generated. Build doesn’t emit .map files, or the bundler inlines them.
  • Wrong upload directory. sentry-cli walks the path but skips files that don’t have .map extensions or are in node_modules.

Why “silently”? Sentry’s design priority is to keep your event ingestion alive even when source-map data is missing — a half-symbolicated event is more useful than a dropped one. So when symbolication fails, you get the event with the minified stack and a small “Issues with Source Maps” warning in the event detail page. If you never click in, you never see the warning. Multiply that by hundreds of unique issues per week, and the missing-map problem hides in plain sight. The fix is always to verify on a known test error before assuming it’s working: throw a deliberate exception, find it in Sentry, and look for either symbolicated frames or the source-map warning.

The release identity is the most underappreciated knob. Two builds with different content but the same release name overwrite each other’s artifacts; two builds with identical content but different release names are treated as unrelated by Sentry. Most teams trip over this in CI: the release name is built from $GITHUB_SHA at upload time but package.json version at runtime, and the two never match. Modern Sentry’s “Debug IDs” feature solves this by embedding a unique identifier into the bundle itself and into the map file — when both are present, Sentry pairs them by ID rather than by release + URL. But Debug IDs only work if the official bundler plugins inject them; hand-rolled webpack configs miss them.

Bundler plugins handle release detection, source map generation, upload, and deletion in one step:

Vite:

// vite.config.ts
import { defineConfig } from "vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";

export default defineConfig({
  plugins: [
    sentryVitePlugin({
      org: "my-org",
      project: "my-app",
      authToken: process.env.SENTRY_AUTH_TOKEN,
      sourcemaps: {
        assets: ["./dist/**"],
      },
      release: {
        name: process.env.GITHUB_SHA,  // Or your release naming
      },
    }),
  ],
  build: {
    sourcemap: true,  // Required: emit source maps
  },
});

Webpack:

// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

module.exports = {
  devtool: "source-map",  // Generate maps
  plugins: [
    sentryWebpackPlugin({
      org: "my-org",
      project: "my-app",
      authToken: process.env.SENTRY_AUTH_TOKEN,
    }),
  ],
};

Next.js (App Router):

// next.config.js
const { withSentryConfig } = require("@sentry/nextjs");

const nextConfig = {
  // your config
};

module.exports = withSentryConfig(nextConfig, {
  org: "my-org",
  project: "my-app",
  authToken: process.env.SENTRY_AUTH_TOKEN,
  widenClientFileUpload: true,
  hideSourceMaps: true,  // Hide map URLs from production HTML
  disableLogger: true,
});

The Next.js plugin handles client and server bundles separately, uploads both, and removes source map comments from the HTML.

Pro Tip: The bundler plugins read SENTRY_AUTH_TOKEN from env automatically. Set it via your CI secrets, never commit it.

Fix 2: Manual Upload With sentry-cli

If you can’t use a plugin, upload manually:

# Set up:
export SENTRY_AUTH_TOKEN=...
export SENTRY_ORG=my-org
export SENTRY_PROJECT=my-app

# Or via ~/.sentryclirc:
# [auth]
# token = ...
# [defaults]
# org = my-org
# project = my-app

# Build:
npm run build  # Produces dist/ with .map files

# Create the release:
sentry-cli releases new "$RELEASE_NAME"

# Upload source maps:
sentry-cli sourcemaps upload \
  --release="$RELEASE_NAME" \
  --url-prefix="~/static/js" \
  ./dist/static/js

# Finalize the release:
sentry-cli releases finalize "$RELEASE_NAME"

--url-prefix:

  • ~/static/js — the ~/ means “any host,” matching https://anywhere.com/static/js/....
  • For absolute URLs: --url-prefix=https://app.example.com/static/js.

The prefix must match the URL the browser reports. If your CDN serves https://cdn.example.com/static/... but you set ~/static/..., the artifact won’t match.

For uploading both .js and .map:

sentry-cli sourcemaps upload \
  --release="$RELEASE_NAME" \
  --url-prefix="~/static/js" \
  --validate \
  --rewrite \
  ./dist/static/js
  • --validate — checks each source map is valid.
  • --rewrite — flattens sources paths in the maps, removing local absolute paths.

Fix 3: Match the Release Name

The release name reported at runtime must match what you uploaded:

Runtime (in your app):

import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: "...",
  release: import.meta.env.VITE_RELEASE,  // Same source as build time
});

Build time:

VITE_RELEASE="app@$(git rev-parse --short HEAD)" npm run build
sentry-cli sourcemaps upload --release="$VITE_RELEASE" ./dist

Both use the same git rev-parse --short HEAD value.

For SemVer-based releases:

VERSION=$(cat package.json | jq -r '.version')
RELEASE="app@$VERSION"

Then use $RELEASE everywhere — at build, runtime, and upload.

Common Mistake: Reading package.json at runtime in a frontend context — that file isn’t bundled. Read it at build time, inject as an env var.

For Next.js, the plugin handles this automatically via SENTRY_RELEASE:

// next.config.js
module.exports = withSentryConfig(nextConfig, {
  release: {
    name: process.env.GITHUB_SHA ?? "dev",
  },
});

Fix 4: Hide Source Maps From Production

Source maps reveal your code. You don’t want them publicly accessible. Two options:

Option A — Don’t deploy .map files:

# Build:
npm run build
sentry-cli sourcemaps upload --release=$RELEASE ./dist
# Then delete maps before deploying:
find ./dist -name "*.map" -delete
# Now /dist has the .js but no .map files.

The browser will fail to load *.map URLs (and won’t show maps in DevTools either), but Sentry’s server-side symbolication still works — Sentry has the maps uploaded.

Option B — Use the bundler plugin’s hideSourceMaps:

withSentryConfig(nextConfig, {
  hideSourceMaps: true,  // Removes sourceMappingURL comments
});

The maps are uploaded but their sourceMappingURL comments are stripped from the JS. DevTools can’t auto-load them.

Pro Tip: Combine: set hideSourceMaps: true (no public reference) and delete .map files post-build (no public file). Sentry still has the maps from the upload step.

Fix 5: Verify the Upload

After uploading, check via CLI or UI:

sentry-cli releases artifacts list "$RELEASE_NAME"

Should list .js and .js.map files. If empty, your upload didn’t include source maps.

In the Sentry UI: Project → Releases → [your release] → Artifacts. The list should show paired main-abc123.js and main-abc123.js.map.

For test events:

Sentry.captureException(new Error("Test error"));

In the resulting error, click “Source Maps” → “Show Resolved” — confirms symbolication is working.

If Sentry’s UI says “no matching source map found for https://example.com/static/js/main.js”:

  • The URL doesn’t match any uploaded artifact. Check exact strings.
  • The release reported by the SDK doesn’t have artifacts. Check release name match.

Fix 6: Multi-Bundle Apps

For apps with multiple bundles (vendor, main, chunks), upload all of them:

sentry-cli sourcemaps upload \
  --release="$RELEASE" \
  --url-prefix="~/static/js" \
  ./dist/static/js

./dist/static/js contains main.*.js, vendor.*.js, chunk-*.js — all uploaded.

For dynamically imported chunks, Sentry needs maps for them too. The bundler plugins handle this automatically — manual upload may need to crawl the dist directory recursively, which the plugins do but a single sourcemaps upload may not.

Common Mistake: Uploading only main.js.map but not vendor or chunk maps. Errors that occur inside vendor code won’t symbolicate.

Fix 7: Server-Side Source Maps

For Node.js servers, source maps work the same way but for .js in node_modules or your dist/:

// In your server:
import * as Sentry from "@sentry/node";

Sentry.init({
  dsn: "...",
  release: process.env.SENTRY_RELEASE,
  integrations: [Sentry.rewriteFramesIntegration({ root: process.cwd() })],
});

rewriteFramesIntegration normalizes stack frames so Sentry can match them against uploaded maps.

For uploading server maps:

sentry-cli sourcemaps upload \
  --release="$RELEASE_NAME" \
  --url-prefix="app:///" \
  ./dist

--url-prefix=app:/// (yes, three slashes) is the convention for server-side. The runtime SDK uses app:/// as the synthetic origin for local files.

For Next.js (mixed client/server), the plugin handles both automatically.

Fix 8: TypeScript Sources vs Compiled JS

If your stacks show .js line numbers but you want .ts line numbers, ensure your TS compiler emits maps that point to the .ts source:

// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSources": true,
    "sourceRoot": "/src"
  }
}
  • sourceMap — emit .map files.
  • inlineSources — include the original .ts source inside the map. Sentry can show the original code in error views.
  • sourceRoot — used by Sentry to resolve relative sources paths.

For Vite/esbuild:

build: {
  sourcemap: true,         // External .map files
  // or "inline" for inline data URLs
  // or "hidden" for external maps without sourceMappingURL comment
}

sourcemap: "hidden" is recommended for Sentry — generates maps for upload but no sourceMappingURL in production JS.

Pro Tip: inlineSources: true slightly increases map size but makes Sentry’s UI more useful — you can see the actual source code at the error location, not just file/line/column.

Sentry vs Datadog vs New Relic vs Rollbar vs Honeybadger for Source Maps

Every error-tracking vendor handles source maps slightly differently. If you’re migrating between them — or evaluating — the upload model is one of the bigger decision points.

Sentry. Releases + URL matching, or modern Debug IDs. Official bundler plugins for Vite, Webpack, Rollup, esbuild, Next.js, Remix, SvelteKit. Auth tokens are project-scoped or org-scoped. Free tier includes source-map storage; paid tiers raise the per-release file count cap. The official plugin is opinionated and usually right — fight it only when you have an unusual build pipeline (multi-CDN, dynamic chunk URLs).

Datadog RUM (Real User Monitoring). Uses release name + service name + version. Upload via datadog-ci sourcemaps upload. No Debug-ID equivalent yet — you must keep release names in lockstep between build and runtime. Datadog ties source maps to its RUM product (separate from its APM and logs products), which can confuse first-time users looking for them in the wrong place.

New Relic Browser Agent. Upload via the New Relic CLI or REST API. Map files are tied to your “application ID” and “release name” — closer to Sentry’s model. Stack traces in errors get symbolicated server-side. Less polished bundler-plugin story than Sentry; expect to script your upload step.

Rollbar. Upload via rollbar source map CLI or HTTP POST to /api/1/sourcemap. Matched by code_version (their term for release) and minified URL. Historically Rollbar’s source-map UI was excellent — clean stack traces with surrounding code context. Less active development in recent years; their bundler plugins lag behind Sentry’s.

Honeybadger. Upload via the Honeybadger CLI or webpack plugin. Source maps are matched by revision (their term for release) and source URL. Smaller team than Sentry, simpler API, fewer features. Often the cheapest option for low-volume apps. If you’re using Honeybadger, you’re already opted out of the “biggest vendor” race.

The common pitfalls cross vendor lines: every system needs (a) the release/version name to match between build and runtime, (b) the source URL in the map to match what the browser reports, and (c) the map file to be reachable by the vendor’s symbolication service. Sentry’s Debug IDs sidestep (a) and (b) — until other vendors adopt similar identifiers, expect to spend setup time on release-name discipline.

Still Not Working?

A few less-obvious failures:

  • Maps uploaded but Sentry still shows minified. Wait a minute — Sentry processes uploads asynchronously. Hit the same error again after upload finishes.
  • No artifact found matching URL. URL is one of: bundler emitted different filename than what’s served (hashes), CDN rewrites paths, or --url-prefix doesn’t match. Try --url-prefix=~/ (any path).
  • Wrong file paths in maps after build. Use --rewrite to flatten paths or set sourceRoot in tsconfig.
  • CI uploads run but no release shows. Check SENTRY_ORG/SENTRY_PROJECT env vars match the project you’re viewing. Wrong project = artifacts go elsewhere.
  • Plugin works locally, fails in CI. Set SENTRY_AUTH_TOKEN as a CI secret with project:read, project:releases, org:read scopes. Don’t use a personal token.
  • Source maps reveal proprietary code in inspector. hideSourceMaps: true strips the comment but maps may still be on the server. Delete them post-build.
  • Cannot upload source map: file too large. Sentry has per-artifact size limits. Split huge bundles or use bundling that emits smaller chunks.
  • Debug ID mismatch. Modern Sentry uses Debug IDs (instead of release+URL matching) — but only if the bundler plugin injects them. Use the official plugins.
  • Source maps stop working after switching CDNs. If you serve assets from a new origin, the runtime URLs change. Either re-upload with the new --url-prefix, or switch to Debug IDs and stop caring about URLs entirely.
  • @sentry/cli works locally but CI uploads return 200 with no artifacts. Almost always an auth scope issue. project:read lets you query; project:write and project:releases are required for upload. Test with sentry-cli info from CI to confirm the token resolves to the expected org/project.
  • Self-hosted Sentry rejects modern Debug-ID uploads. Debug IDs need self-hosted Sentry 23.x or newer. Older deployments only understand the release+URL model, even if your bundler plugin tries the modern path. Pin the plugin’s release.create and sourcemaps.assets options to the legacy flow.

For related error tracking and observability issues, see Sentry not working, OpenTelemetry not working, Webpack bundle size too large, and Vite failed to resolve import.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles