Fix: Sentry Source Maps Not Working — Release Matching, sentry-cli Upload, Vite/Webpack Plugins
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 mapsBut 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:
- The URL of the JS file (
https://app.example.com/static/js/main-abc123.js). - The release name reported by the SDK (
[email protected]). - 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.jsbut runtime ismain-abc123.js(or vice versa). - Maps not actually generated. Build doesn’t emit
.mapfiles, or the bundler inlines them. - Wrong upload directory.
sentry-cliwalks the path but skips files that don’t have.mapextensions or are in node_modules.
Fix 1: Use the Sentry Bundler Plugins (Recommended)
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,” matchinghttps://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— flattenssourcespaths 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" ./distBoth 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.mapfiles.inlineSources— include the original.tssource inside the map. Sentry can show the original code in error views.sourceRoot— used by Sentry to resolve relativesourcespaths.
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.
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-prefixdoesn’t match. Try--url-prefix=~/(any path).- Wrong file paths in maps after build. Use
--rewriteto flatten paths or setsourceRootin tsconfig. - CI uploads run but no release shows. Check
SENTRY_ORG/SENTRY_PROJECTenv vars match the project you’re viewing. Wrong project = artifacts go elsewhere. - Plugin works locally, fails in CI. Set
SENTRY_AUTH_TOKENas a CI secret withproject:read,project:releases,org:readscopes. Don’t use a personal token. - Source maps reveal proprietary code in inspector.
hideSourceMaps: truestrips 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 IDmismatch. Modern Sentry uses Debug IDs (instead of release+URL matching) — but only if the bundler plugin injects them. Use the official plugins.
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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Loading chunk failed / ChunkLoadError
How to fix 'Loading chunk failed', 'ChunkLoadError', and 'Failed to fetch dynamically imported module' in webpack, Next.js, React, and Vite. Covers stale deployments, CDN caching, publicPath misconfiguration, service worker cache, code splitting, dynamic import retry strategies, React.lazy error boundaries, and Next.js-specific solutions.
Fix: Webpack/Vite Path Alias Not Working — Module Not Found with @/ Prefix
How to fix path alias errors in webpack and Vite — configuring resolve.alias, tsconfig paths, babel-plugin-module-resolver, Vite alias configuration, and Jest moduleNameMapper.
Fix: Module not found: Can't resolve / Cannot find module or its corresponding type declarations
How to fix 'Module not found: Can't resolve' in webpack, Vite, and React, and 'Cannot find module or its corresponding type declarations' in TypeScript. Covers missing packages, wrong import paths, case sensitivity, path aliases, node_modules corruption, monorepo hoisting, barrel files, and asset imports.
Fix: React Compiler Not Working — ESLint Plugin, Babel Setup, Bail-Outs, and Vite/Next.js Config
How to fix React Compiler issues — eslint-plugin-react-compiler not flagging, babel-plugin-react-compiler not running, 'Function contains a code construct that prevents compilation', Next.js 15 config, and removing useMemo/useCallback safely.