Is there an existing issue for this?
How do you use Sentry?
Sentry SaaS (sentry.io)
Which SDK are you using?
@sentry/nuxt
SDK Version
11.6.0 (also reproduced on 10.75.0)
Framework Version
Nuxt 3.21.11, nitropack 2.13.4, node-server preset, Node 22
Link to Sentry event
n/a
Reproduction Example/SDK Setup
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sentry/nuxt/module'],
sourcemap: { client: 'hidden' },
sentry: {
org: '...',
project: '...',
sourcemaps: {
filesToDeleteAfterUpload: ['.output/public/**/*.map'],
},
},
});
No auth token is needed to reproduce, because the deletion runs either way.
Steps to Reproduce
nuxt build
find .output/public -name '*.map' finds nothing, because the maps were deleted.
grep -o '"/_nuxt/[^"]*\.js\.map"' .output/server/chunks/nitro/nitro.mjs | head still lists every map in Nitro's public-asset manifest.
node .output/server/index.mjs
- Request a real chunk with
curl -i localhost:3000/_nuxt/<chunk>.js, then the same URL with .map appended: curl -i localhost:3000/_nuxt/<chunk>.js.map.
Expected Result
The .map request returns 404, the same as an unknown path (/_nuxt/doesnotexist.js.map already returns 404).
Actual Result
The .map request returns 500, with the server log showing:
[request error] [unhandled] [GET] http://localhost:3000/_nuxt/<chunk>.js.map
H3Error: ENOENT: no such file or directory, open '.../.output/public/_nuxt/<chunk>.js.map'
The 500 response also carries the deleted file's ETag, its Content-Length, and Cache-Control: public, max-age=31536000, immutable, all taken from the stale manifest entry.
Additional Context
Ordering problem:
@nuxt/nitro-server copies the client build into .output/public in Nitro's rollup:before hook (dist/index.mjs:780-783: copyPublicAssets, then nitro:build:public-assets).
- Nitro then globs
.output/public into the virtual #nitro-internal-virtual/public-assets-data manifest (nitropack/dist/rollup/index.mjs ~L1232) and bundles it into the server.
@sentry/nuxt deletes the files in Nuxt's close hook, after the server bundle exists (build/module/module.mjs L620-623 in 11.6.0, deleteSourceMapsAfterBuild).
- At runtime the static handler finds the manifest entry, sets headers, and calls
readAsset, which runs fsp.readFile and throws ENOENT. That surfaces as a 500 (nitropack/dist/runtime/internal/static.mjs).
The automatic deletion fallback (when filesToDeleteAfterUpload is unset) uses the same close hook, so it should behave the same way.
hidden maps (#13993) stop browsers from requesting maps, but crawlers and scanners still request them. They get 500s, and Sentry's own server SDK reports those 500s as errors. Another project diagnosed the same chain independently after a bot requested about 47 maps and triggered an alert burst (kodes-agency/reputation-key#631).
Workaround we use, which removes the maps after the copy and before the manifest is built:
hooks: {
async 'nitro:build:public-assets'(nitro) {
const { readdir, rm } = await import('node:fs/promises');
const { join } = await import('node:path');
const dir = nitro.options.output.publicDir;
const files = await readdir(dir, { recursive: true });
await Promise.all(files.filter(f => f.endsWith('.map')).map(f => rm(join(dir, f))));
},
},
The client upload is unaffected because the Vite plugin uploads from .nuxt/dist/client at writeBundle, before Nitro runs. We confirmed in production that the upload succeeds and the URLs return 404. nitro.ignore: ['**/*.map'] does not work, because Nuxt appends !<buildDir>/dist/client/_nuxt/**/* to nitro.ignore (@nuxt/nitro-server/dist/index.mjs:400).
Suggested fix: for client maps, have the module delete in nitro:build:public-assets (or exclude *.map when Nitro builds the manifest) instead of in close. Server maps can keep the current timing.
Priority
Low. Nothing leaks, but it produces stray 500s and alert noise.
Is there an existing issue for this?
How do you use Sentry?
Sentry SaaS (sentry.io)
Which SDK are you using?
@sentry/nuxt
SDK Version
11.6.0 (also reproduced on 10.75.0)
Framework Version
Nuxt 3.21.11, nitropack 2.13.4,
node-serverpreset, Node 22Link to Sentry event
n/a
Reproduction Example/SDK Setup
No auth token is needed to reproduce, because the deletion runs either way.
Steps to Reproduce
nuxt buildfind .output/public -name '*.map'finds nothing, because the maps were deleted.grep -o '"/_nuxt/[^"]*\.js\.map"' .output/server/chunks/nitro/nitro.mjs | headstill lists every map in Nitro's public-asset manifest.node .output/server/index.mjscurl -i localhost:3000/_nuxt/<chunk>.js, then the same URL with.mapappended:curl -i localhost:3000/_nuxt/<chunk>.js.map.Expected Result
The
.maprequest returns 404, the same as an unknown path (/_nuxt/doesnotexist.js.mapalready returns 404).Actual Result
The
.maprequest returns 500, with the server log showing:The 500 response also carries the deleted file's ETag, its
Content-Length, andCache-Control: public, max-age=31536000, immutable, all taken from the stale manifest entry.Additional Context
Ordering problem:
@nuxt/nitro-servercopies the client build into.output/publicin Nitro'srollup:beforehook (dist/index.mjs:780-783:copyPublicAssets, thennitro:build:public-assets)..output/publicinto the virtual#nitro-internal-virtual/public-assets-datamanifest (nitropack/dist/rollup/index.mjs~L1232) and bundles it into the server.@sentry/nuxtdeletes the files in Nuxt'sclosehook, after the server bundle exists (build/module/module.mjsL620-623 in 11.6.0,deleteSourceMapsAfterBuild).readAsset, which runsfsp.readFileand throws ENOENT. That surfaces as a 500 (nitropack/dist/runtime/internal/static.mjs).The automatic deletion fallback (when
filesToDeleteAfterUploadis unset) uses the sameclosehook, so it should behave the same way.hiddenmaps (#13993) stop browsers from requesting maps, but crawlers and scanners still request them. They get 500s, and Sentry's own server SDK reports those 500s as errors. Another project diagnosed the same chain independently after a bot requested about 47 maps and triggered an alert burst (kodes-agency/reputation-key#631).Workaround we use, which removes the maps after the copy and before the manifest is built:
The client upload is unaffected because the Vite plugin uploads from
.nuxt/dist/clientatwriteBundle, before Nitro runs. We confirmed in production that the upload succeeds and the URLs return 404.nitro.ignore: ['**/*.map']does not work, because Nuxt appends!<buildDir>/dist/client/_nuxt/**/*tonitro.ignore(@nuxt/nitro-server/dist/index.mjs:400).Suggested fix: for client maps, have the module delete in
nitro:build:public-assets(or exclude*.mapwhen Nitro builds the manifest) instead of inclose. Server maps can keep the current timing.Priority
Low. Nothing leaks, but it produces stray 500s and alert noise.