From 23d34422d5714d40f623a0681960745e02bf7d4b Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Tue, 20 Oct 2026 10:27:54 -0400 Subject: [PATCH] fix(nextjs): Only inject applicationKey metadata into browser bundles The metadata snippet was injected into server and edge modules too, where nothing reads it, adding to every cold start. Fixes #25544 --- packages/nextjs/src/config/getBuildPluginOptions.ts | 4 +++- .../src/config/turbopack/constructTurbopackConfig.ts | 3 ++- .../nextjs/test/config/getBuildPluginOptions.test.ts | 11 +++++++++++ .../config/turbopack/constructTurbopackConfig.test.ts | 10 +++++----- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/src/config/getBuildPluginOptions.ts b/packages/nextjs/src/config/getBuildPluginOptions.ts index 3c27f71330d7..fb3defba4c50 100644 --- a/packages/nextjs/src/config/getBuildPluginOptions.ts +++ b/packages/nextjs/src/config/getBuildPluginOptions.ts @@ -323,7 +323,9 @@ export function getBuildPluginOptions({ const skipSourcemapsUpload = shouldSkipSourcemapUpload(buildTool, useRunAfterProductionCompileHook); return { - applicationKey: sentryBuildOptions.applicationKey, + // Only the browser reads these tags (`thirdPartyErrorFilterIntegration`), so server bundles skip the per-module cost. + applicationKey: + buildTool === 'webpack-nodejs' || buildTool === 'webpack-edge' ? undefined : sentryBuildOptions.applicationKey, authToken: sentryBuildOptions.authToken, headers: sentryBuildOptions.headers, org: sentryBuildOptions.org, diff --git a/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts b/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts index 3eb22ab2263d..391dee0e39f1 100644 --- a/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts +++ b/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts @@ -74,12 +74,13 @@ export function constructTurbopackConfig({ // so it is safe even for node_modules with strict initialization order. // We only exclude Next.js build polyfills which contain non-standard syntax that causes // parse errors when any code is prepended (Turbopack re-parses the loader output). + // Server and edge modules are skipped because only the browser reads these tags. const applicationKey = userSentryOptions?.applicationKey; if (applicationKey && nextJsVersion && supportsTurbopackRuleCondition(nextJsVersion)) { newConfig.rules = safelyAddTurbopackRule(newConfig.rules, { matcher: '*.{ts,tsx,js,jsx,mjs,cjs}', rule: { - condition: { not: { path: /next\/dist\/build\/polyfills/ } }, + condition: { all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }, loaders: [ { loader: path.resolve(__dirname, '..', 'loaders', 'moduleMetadataInjectionLoader.js'), diff --git a/packages/nextjs/test/config/getBuildPluginOptions.test.ts b/packages/nextjs/test/config/getBuildPluginOptions.test.ts index 3fe418ad0970..e04c76e5cb98 100644 --- a/packages/nextjs/test/config/getBuildPluginOptions.test.ts +++ b/packages/nextjs/test/config/getBuildPluginOptions.test.ts @@ -90,6 +90,17 @@ describe('getBuildPluginOptions', () => { expect(result.applicationKey).toBe('my-app-key'); }); + it.each(['webpack-nodejs', 'webpack-edge'] as const)('does not forward applicationKey for %s builds', buildTool => { + const result = getBuildPluginOptions({ + sentryBuildOptions: { applicationKey: 'my-app-key' }, + releaseName: mockReleaseName, + distDirAbsPath: mockDistDirAbsPath, + buildTool, + }); + + expect(result.applicationKey).toBeUndefined(); + }); + it('normalizes Windows paths to posix for glob patterns in after-production-compile builds', () => { const windowsPath = 'C:\\Users\\test\\.next'; const sentryBuildOptions: SentryBuildOptions = { diff --git a/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts b/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts index 844ac3f97644..3670ff6244e7 100644 --- a/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts +++ b/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts @@ -963,7 +963,7 @@ describe('moduleMetadataInjection with applicationKey', () => { }); expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toEqual({ - condition: { not: { path: /next\/dist\/build\/polyfills/ } }, + condition: { all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }, loaders: [ { loader: '/mocked/path/to/moduleMetadataInjectionLoader.js', @@ -990,7 +990,7 @@ describe('moduleMetadataInjection with applicationKey', () => { expect(rule.loaders[0]!.options.applicationKey).toBe('my-top-level-key'); }); - it('should only exclude Next.js polyfills, not all foreign modules', () => { + it('should only tag browser modules and exclude Next.js polyfills, not all foreign modules', () => { const userNextConfig: NextConfigObject = {}; const result = constructTurbopackConfig({ @@ -999,11 +999,11 @@ describe('moduleMetadataInjection with applicationKey', () => { nextJsVersion: '16.0.0', }); - const rule = result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}'] as { condition?: { not: unknown }; loaders: unknown[] }; + const rule = result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}'] as { condition?: unknown; loaders: unknown[] }; // Unlike component annotation (which uses { not: 'foreign' }), metadata injection // must cover node_modules to match the webpack plugin's BannerPlugin behavior. // Only Next.js build polyfills are excluded because they have non-standard syntax. - expect(rule.condition).toEqual({ not: { path: /next\/dist\/build\/polyfills/ } }); + expect(rule.condition).toEqual({ all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }); }); it('should NOT add metadata loader rule when Next.js < 16', () => { @@ -1097,7 +1097,7 @@ describe('moduleMetadataInjection with applicationKey', () => { }); expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toEqual({ - condition: { not: { path: /next\/dist\/build\/polyfills/ } }, + condition: { all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }, loaders: [ { loader: '/mocked/path/to/moduleMetadataInjectionLoader.js',