Source Maps Exposed in Production: How to Fix It (2026)

We built a three-line Vite app with one code comment in it: // TODO remove admin bypass before launch. After vite build, that comment was gone from the JavaScript bundle. It was still sitting, word for word, in the .js.map file next to it.

That's the whole problem with source maps exposed in production. Minification strips comments and renames variables, and a lot of founders treat that as a light layer of privacy. A public source map undoes it, handing anyone your original files as you (or your AI editor) wrote them.

TL;DR

Open your bundle's URL, add .map to the end, and see if it downloads. If it does, turn production source maps off in your build config (or set them to hidden and delete the .map files before deploy if you need them for Sentry). Vite, Next.js, Nuxt and webpack all default to off, so a leak almost always means someone switched it on, usually for error tracking.

What actually leaks

A source map is a JSON file that maps minified code back to the original. The part that matters is the sourcesContent field: an array holding the full text of each original file. Here's what our test map contained, generated by Vite 8.3.5 with build.sourcemap: 'hidden':

Contents of dist/assets/index-*.js.map (trimmed)
{
  "sources": ["../../main.js"],
  "sourcesContent": [
    "// TODO remove admin bypass before launch\nconst SUPABASE_URL = \"https://example.supabase.co\";\n..."
  ]
}

So a reader gets your folder structure, real function names like isAdmin, and every comment. None of that is a secret on its own. But it turns "read 400KB of minified soup" into "browse the repo", and it points straight at whatever your comments admit is unfinished.

One thing a source map doesn't do is reveal anything the browser never received. Server code, API routes and .env values that stayed on the server aren't in it. If an API key is in your source map, it was already in your bundle, and that's the bigger fix: see exposed API keys.

The highest-profile version of this mistake in 2026 wasn't a website. On March 31, version 2.1.88 of Anthropic's @anthropic-ai/claude-code npm package shipped with a source map that exposed the tool's TypeScript source, as InfoQ reported. Anthropic called it "a release packaging issue caused by human error." Same mechanism, different delivery channel: a .map file went out with the build.

Check your site in two minutes

1

Find a bundle URL. Open your live site, view source, and copy the src of a <script> tag on your own domain. On Vite apps it looks like /assets/index-z40z2RPZ.js. On Next.js it's under /_next/static/chunks/.

2

Look at the last line of the bundle.

curl -s https://yourapp.com/assets/index-z40z2RPZ.js | tail -c 200

If it ends with //# sourceMappingURL=index-z40z2RPZ.js.map, the bundle is advertising its map. If it ends with sourceMappingURL=data:application/json;base64,..., the whole map is embedded inside the bundle itself.

3

Request the map directly, even if there's no comment.

curl -s -o /dev/null -w "%{http_code}\n" https://yourapp.com/assets/index-z40z2RPZ.js.map

A 200 means it's public. This step matters because of hidden mode, covered below: the comment can be gone while the file is still there.

What our scanner checks (and what it doesn't)

CheckYourVibe's free scan runs this as the Exposed Source Maps check. To be precise about it: it fetches the <script src> files referenced in your homepage HTML, then looks for a sourceMappingURL comment in the last 500 characters of each one. It reports two findings:

  • Source Map Reference Found (high): the bundle points to an external .map file.
  • Inline Source Map Found (medium): the map is embedded in the bundle as a data: URI.

It does not request the .map file itself, and it only sees scripts linked from your homepage, not chunks loaded later. That has two practical consequences. A "hidden" map that's still deployed won't be flagged, so run step 3 above yourself. And a reference finding can fire on a map that already returns 404, which happens when Sentry deletes the files after upload but the build left the comment in. In that case nothing is leaking, but switch to hidden anyway so the reference goes away.

If the flagged script is on someone else's domain (a chat widget, an analytics snippet, a CDN library), that's the vendor's source, not yours. Nothing to fix.

The fix, by build tool

Every major tool ships with production browser source maps off. If yours are public, find the line that turned them on.

Vite (Lovable, Bolt, most React and Vue apps)

Vite's build docs list build.sourcemap as boolean | 'inline' | 'hidden', default false. Look in vite.config.ts:

vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false, // was true or 'inline'
  },
})

true writes a separate .map and the reference comment. 'inline' puts the whole map inside the bundle, which is the worst option for production because there's no separate file to delete. 'hidden' writes the .map but drops the comment.

Next.js

The Next.js docs say browser source maps are disabled in production "to prevent you leaking your source on the client" unless you opt in. Check next.config.js for:

productionBrowserSourceMaps: true, // delete this line

With it on, Next.js writes the maps beside the JavaScript and serves them automatically.

Nuxt

Nuxt's sourcemap option defaults to { server: true, client: false }. Server maps stay on the server. If nuxt.config.ts has sourcemap: true (a single boolean applies to both) or sourcemap: { client: true }, set the client side back to false.

webpack and Create React App

webpack's devtool defaults to false outside development mode. Watch for devtool: 'source-map' in a production config. Old Create React App projects generate maps unless you build with GENERATE_SOURCEMAP=false:

GENERATE_SOURCEMAP=false npm run build

Why "hidden" isn't the fix on its own

This one catches people who did read the docs. We built the same test app three ways:

build.sourcemap.map in dist?Comment in bundle?
trueyesyes
'hidden'yesno
'inline'no (it's inside the JS)yes, as a data URI

hidden only removes the pointer. The file still lands in dist/assets/, and Vercel, Netlify or Cloudflare Pages will serve whatever is in that folder. Anyone who appends .map to your bundle URL gets it. webpack's own docs say the same about its hidden-source-map setting: "You should not deploy the Source Map file to the webserver."

If you don't use an error tracker, just set source maps to false. If you do, keep reading.

Keeping source maps for Sentry without publishing them

Readable stack traces are the legitimate reason maps get turned on. The pattern is: generate, upload, delete, then deploy.

Next.js with @sentry/nextjs: Sentry's Next.js source map docs list sourcemaps.deleteSourcemapsAfterUpload with a default of true for client-side maps. Leave it on.

Vite with @sentry/vite-plugin: Sentry's Vite guide warns that "generating source maps may expose them to the public, potentially causing your source code to be leaked." Use hidden and delete after upload:

vite.config.ts with Sentry
import { sentryVitePlugin } from "@sentry/vite-plugin"

export default defineConfig({
  build: { sourcemap: "hidden" },
  plugins: [
    sentryVitePlugin({
      org: "your-org",
      project: "your-project",
      authToken: process.env.SENTRY_AUTH_TOKEN,
      sourcemaps: { filesToDeleteAfterUpload: ["./dist/**/*.map"] },
    }),
  ],
})

No Sentry plugin but maps still get generated somewhere? A blunt build step works too:

"build": "vite build && find dist -name '*.map' -delete"

Confirm it's gone

Redeploy, then rerun steps 2 and 3 against the new bundle URL (the hash in the filename will change). You want no sourceMappingURL at the end of the file and a 404 on the .map request.

One last thing: deleting the maps from your next deploy doesn't erase what was public before. If those maps contained something sensitive, such as a key that was inlined in client code, rotate it. Hiding the map doesn't hide the bundle.

Are exposed source maps actually a security problem?

They don't create a new hole, because the browser already downloads your front-end code in minified form. What they add is everything the minifier threw away: original file names and folder layout, real variable names, and your code comments, which is where TODOs about unfinished auth checks tend to live. Treat it as information disclosure that makes every other bug in your client code faster to find.

Is sourcemap: 'hidden' enough to stop source maps leaking?

No. In Vite, 'hidden' still writes the .map file into dist, it only removes the comment that points to it. If you deploy dist as-is, the file is still served at the bundle's URL with .map on the end. Hidden is the right setting when you upload maps to an error tracker, but you also have to delete the .map files before deploy.

Does Next.js expose source maps by default?

No. Next.js docs say browser source maps are disabled in production builds unless you set productionBrowserSourceMaps: true in next.config.js. If that flag is on, Next.js outputs the maps next to the JavaScript files and serves them automatically.

Do I need to fix a source map finding on a script that isn't mine?

No. If the flagged file lives on another domain (a chat widget, analytics, a CDN library), it's the vendor's code and the vendor's choice. Only the scripts served from your own domain carry your source.

Can I keep source maps for Sentry without exposing them?

Yes. Generate them, upload them to Sentry during the build, then delete them before deploy. Sentry's Next.js SDK deletes client-side maps after upload by default (sourcemaps.deleteSourcemapsAfterUpload). For Vite, set build.sourcemap to 'hidden' and pass sourcemaps.filesToDeleteAfterUpload to the Sentry plugin.

Our free scan checks the JavaScript your homepage loads for source map references, alongside headers, TLS and exposed keys.

How-To Guides

Source Maps Exposed in Production: How to Fix It (2026)