Uploading Source Maps to Sentry from a Vite Build
This guide walks through emitting hidden source maps from a Vite production build, uploading them to Sentry with @sentry/vite-plugin, tagging the release so events match the maps, deleting the maps from the deploy artifact, and verifying that a production stack trace symbolicates back to your .ts source. For the cross-tool model of inline versus external versus hidden maps and the security reasoning behind not shipping them, read Source Maps and Production Debugging first, then apply the Sentry-specific wiring below.
Problem Scope
Vite minifies and chunk-splits production output, so an uncaught exception arrives in Sentry as Cannot read properties of undefined (reading 'id') at index-9f2a1c.js:1:48210 — unreadable. The fix is to emit hidden source maps, hand them to Sentry keyed by a release that matches the events your app reports, and then delete the maps so they never ship to users.
Prerequisites & Reproducible Setup
You need Vite 5.x or 6.x, @sentry/vite-plugin 2.x for upload, and @sentry/browser (or the framework SDK such as @sentry/react) for runtime reporting and release tagging. Node 18, 20, or 22 all work.
# Vite 5.x / 6.x, Node 20+
npm install --save-dev @sentry/vite-plugin
npm install @sentry/browser
Create a Sentry auth token with project:releases and org:read scopes and expose it to the build environment — never commit it:
# .env.sentry-build-plugin (gitignored) or CI secret
export SENTRY_AUTH_TOKEN="sntrys_xxx"
export SENTRY_ORG="your-org"
export SENTRY_PROJECT="your-project"
Diagnosis Workflow
Before wiring the plugin, confirm what is actually wrong, in order:
- Open the Sentry issue and check the stack frames. If they show minified
index-*.jspaths with single-digit lines, no maps are associated. - Check the event’s Release tag. If it is empty or does not match any release that has artifacts, symbolication cannot occur even when maps were uploaded. Mismatched release strings are the single most common failure.
- Inspect the deployed bundle:
curl -s https://app.example.com/assets/index-*.js | tail -1. If you see a//# sourceMappingURLpragma, you are shipping public maps — switch to'hidden'. - In Sentry, open the release’s Artifacts tab. If it is empty, the build never uploaded; if it has
.mapfiles but events still fail, thesources/path prefix does not match the bundle URLs.
Complete Annotated Configuration
// vite.config.ts — Vite 5.x / 6.x with @sentry/vite-plugin 2.x, Node 20+
import { defineConfig } from 'vite';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import { execSync } from 'node:child_process';
// Derive one stable release id used by BOTH the upload and the runtime SDK.
const release =
process.env.SENTRY_RELEASE ??
`myapp@${execSync('git rev-parse --short HEAD').toString().trim()}`;
export default defineConfig({
// Expose the release to client code so Sentry.init can tag events with it.
define: {
__SENTRY_RELEASE__: JSON.stringify(release),
},
build: {
// 'hidden' writes .map files but appends NO sourceMappingURL pragma,
// so browsers never fetch them and they are not publicly discoverable.
sourcemap: 'hidden',
},
plugins: [
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Tie uploaded artifacts to the exact release the runtime reports.
release: { name: release },
sourcemaps: {
// Upload every emitted map, then delete them from the build output
// so the deploy artifact ships no .map files at all.
assets: './dist/**',
filesToDeleteAfterUpload: ['./dist/**/*.map'],
},
// telemetry off to avoid sending build metadata to Sentry.
telemetry: false,
}),
],
});
// src/sentry.ts — initialize the runtime SDK with the SAME release string
import * as Sentry from '@sentry/browser';
declare const __SENTRY_RELEASE__: string;
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
// MUST equal the release name passed to sentryVitePlugin above, or
// Sentry cannot associate the uploaded maps with incoming events.
release: __SENTRY_RELEASE__,
environment: import.meta.env.MODE,
});
Import src/sentry.ts at the very top of your entry (main.tsx) so initialization precedes any code that can throw.
Verification
After vite build, confirm the pipeline end to end:
# 1. The plugin should have deleted every map from the artifact:
find dist -name '*.map' # expect: no output
# 2. No bundle should carry a sourceMappingURL pragma:
grep -rl "sourceMappingURL" dist/assets # expect: no output
# 3. The release should exist with artifacts uploaded:
npx sentry-cli releases files "$SENTRY_RELEASE" list
Then trigger a real symbolication test. Add a temporary throwing handler, deploy, click it in production, and open the resulting Sentry issue. A correctly wired pipeline shows the frame resolved to src/...tsx with the original line, column, and surrounding code context, and the event’s Release tag matching the release you uploaded under. Remove the test handler afterward.
Gotchas & Edge Cases
- Release string drift. If the build derives the release from
git rev-parsebut CI checks out a detached HEAD or shallow clone, the runtime value can differ from the upload value. Pin both to the sameSENTRY_RELEASEenv var in CI rather than computing it twice. - Maps deleted before upload finishes.
filesToDeleteAfterUploadruns inside the plugin after upload; do not add a separaterm -rf dist/**/*.mapin a pre-deploy script that races the plugin. Let the plugin own deletion. - Missing auth scopes. A token without
project:releasesuploads nothing and the build often still succeeds silently. Verify the release’s Artifacts tab is non-empty rather than trusting an exit code of zero. sourcesContentand proprietary code. Vite embeds original source into hidden maps by default. Because these maps go only to Sentry and are deleted from the artifact, that is usually acceptable; if your Sentry org is shared broadly, setrollupOptions.output.sourcemapExcludeSources: trueand rely on Sentry’s source-link integration instead.
Related
- Source Maps and Production Debugging — the parent guide covering hidden versus external maps, VLQ mappings, and security trade-offs across all bundlers.
- Bundler Version Compatibility Reference — confirm your Vite and plugin versions support the
sourcemap: 'hidden'and deletion options used here.