Vite + Sentry SDK + source maps: the missing config

The three Vite plugin options that everyone forgets, and what production stack traces look like before vs after each one.

·
vitesource-maps

You get a critical production error alert. You click the link, ready to fix it, only to find a stack trace that looks like a cat walked across a keyboard: assets/index-D7x9a2.js:1:15902. Without the right Vite configuration, your expensive error-tracking tool is just a glorified "something went wrong" notification. The frustrating part is that this is almost always a configuration gap, not a hard problem—and it's usually three missing pieces of config standing between you and a clean stack trace that points at src/checkout/PaymentForm.tsx:88.

This post walks through those three pieces in order, shows what the trace looks like before and after each one, and ends with the security pattern most teams forget until a competitor downloads their entire frontend.

The "Why" Behind the Minified Mystery

Locally, everything just works. You hit a bug in npm run dev, the console points at the exact line in your TypeScript, and you fix it. That's because Vite's dev server serves your modules essentially unbundled, with inline source maps generated automatically. Production is a different animal.

Why npm run build breaks your error tracking

When you run vite build, Vite hands your code to Rollup, which tree-shakes, bundles, and minifies it. Every calculateCartTotal becomes e, whitespace vanishes, and dozens of source files collapse into a handful of hashed chunks on a single line. By default, Vite does not emit source maps for production builds. So the trace your tracker receives is genuinely all it has to work with: a column offset into a minified blob.

The anatomy of a useless stack trace vs. a useful one

A useless trace is a coordinate with no map: index-D7x9a2.js:1:15902. A useful one is what you get once the tracker can reverse that coordinate through a source map: PaymentForm.tsx:88:12 → submitPayment(). Same error, same byte offset—the only difference is whether a .map file made it to the tracker. The mechanics of that reversal are covered in our walkthrough of deminifying a JavaScript stack trace.

Missing Config #1: The build.sourcemap Flag

The single most common mistake is assuming the Sentry plugin generates maps for you. It does not. The plugin uploads maps; it doesn't create them. If Vite never emits a .map, there is nothing to upload, and your traces stay minified.

Setting sourcemap: true vs. 'hidden'

Vite's build.sourcemap accepts true, false, 'inline', and 'hidden'. With true, Vite emits the .map file and appends a //# sourceMappingURL=index.js.map comment to the bottom of each bundle. That comment is the problem: it tells every browser exactly where to download your source. With 'hidden', Vite emits the identical .map file but omits the comment.

Why 'hidden' is the gold standard for production

You still get a real source map on disk—which is all your tracker's upload step needs—but browsers and casual scrapers have no breadcrumb pointing at it. This is the foundation of keeping maps private without losing debuggability.

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    // Emit .map files but NO sourceMappingURL comment.
    sourcemap: 'hidden',
  },
});

Missing Config #2: The Vite Sentry Plugin Orchestration

With maps now being generated, you need something to ship them to your tracker during the build. That's @sentry/vite-plugin (which is fully compatible with GlitchReplay's Sentry-style ingest). This is where most auth and project-scoping errors happen.

Essential properties

The plugin needs four things to authenticate and route the upload correctly: the url of your tracker, an authToken with upload scope, your org, and your project. Keep the token out of source—pull it from an environment variable so it never lands in git.

The include and ignore pitfalls

By default the plugin uploads everything in your build output. You almost never want to upload maps for vendored chunks you didn't write, and you definitely don't want to accidentally sweep in node_modules. Scope the upload to your assets directory and let the plugin's sensible defaults handle the rest. Critically, the plugin should run last in your plugins array so it sees the final emitted maps.

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

export default defineConfig({
  build: { sourcemap: 'hidden' },
  plugins: [
    // ...your other plugins first
    sentryVitePlugin({
      url: 'https://glitchreplay.com',
      org: 'your-org',
      project: 'web-app',
      authToken: process.env.SOURCEMAP_AUTH_TOKEN,
      release: { name: process.env.GIT_SHA },
      sourcemaps: {
        assets: './dist/assets/**',
        ignore: ['node_modules/**'],
      },
    }),
  ],
});

Missing Config #3: The "Delete After Upload" Security Pattern

Here is the piece that keeps engineering managers up at night. Even with 'hidden' removing the comment, the .map files still physically exist in ./dist. If you deploy that folder straight to a CDN or Cloudflare Pages, anyone who guesses the filename (index-D7x9a2.js.map) can download your full source.

Why you shouldn't ship .map files to your CDN

A source map with sourcesContent is your original TypeScript, comments and all. Shipping it publicly is effectively open-sourcing your app by accident. The 'hidden' setting hides the pointer; it doesn't remove the file. You have to delete it yourself, after the plugin has finished uploading.

Cleaning up after the upload

The reliable approach is to delete maps as a post-build step in your deploy script, once the upload has completed. A one-liner does it:

# package.json scripts
{
  "scripts": {
    "build": "vite build",
    "deploy": "npm run build && rm -f dist/assets/*.map && wrangler pages deploy dist"
  }
}

Order matters: the plugin uploads during vite build, then rm strips the maps before wrangler publishes. The full rationale and framework variations live in hiding source maps from end users, and the security tradeoffs are summarized in our security docs.

Verifying the Stack: The "Before vs. After" Comparison

Once all three pieces are in place, an incoming error gets resolved on ingest: the tracker reads the bundle's release, finds the matching uploaded map, reverses 1:15902 back through the mappings, and shows you PaymentForm.tsx:88. The "event facts" pane that used to show a hashed filename now shows your real path.

Common "Source Map Not Found" errors

When it doesn't resolve, the cause is almost always one of three mismatches. First, the release name reported by the runtime SDK doesn't match the release the plugin uploaded under—both must derive from the same value (the git SHA is ideal). Second, the auth token scope was insufficient and the upload silently failed. Third, the artifact simply wasn't present because the plugin ran before maps were emitted, or you deleted them too early.

A quick verification checklist: confirm the SDK release matches the uploaded release, confirm the token has upload scope, and confirm the map exists by running it through the source map validator before you trust the pipeline. If a one-off trace is still raw, paste it into the deminifier with the local .map and read it instantly.

Performance Impact: Does This Slow Down CI/CD?

The honest answer: a little, and it's worth it. Generating maps adds modest build time, and uploading them adds a few seconds proportional to size—a single 1 MB vendor chunk can produce a 3–5 MB map. On a typical app the whole generate-and-upload step is a handful of seconds.

Why the debuggability tax is always worth paying

Weigh those seconds against the alternative: an unreadable production trace that turns a five-minute fix into an afternoon of guessing. The build tax is paid once per deploy; the debugging tax, without maps, is paid every single time something breaks. There is no contest.

The GlitchReplay Advantage: Sentry-Compatible, Scale-Ready

Because GlitchReplay speaks the same SDK and uses the same @sentry/vite-plugin, every config above works by changing a single url. What changes is the pricing model. On a per-event plan, you start rationing what debug info you're willing to send—dropping breadcrumbs, sampling errors—precisely to avoid a surprise bill. Flat-rate pricing removes that anxiety: send full context on every error, re-run your source map setup as many times as it takes to get it right, and never watch a meter while you debug. And because the platform runs on Cloudflare, map resolution on ingest is fast.

Stop debugging blind. Add the three Vite settings—sourcemap: 'hidden', the upload plugin, and the post-build delete—and your next production error will point at the line you actually need to fix.

Stop watching your error bill spike.

GlitchReplay is Sentry-SDK compatible, includes session replay and security signals, and never charges per event. Free to start, five minutes to first event.