From 7418e0241d535a2b4bfe564ecb697877b1a43d8d Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 12 Aug 2026 16:41:48 +0200 Subject: [PATCH] feat(nextjs)!: Remove unstable_sentryWebpackPluginOptions Co-Authored-By: Claude Opus 5 (1M context) --- .../src/config/getBuildPluginOptions.ts | 6 +- packages/nextjs/src/config/types.ts | 66 +++++++++---------- .../withSentryConfig/getFinalConfigObject.ts | 2 + .../getFinalConfigObjectBundlerUtils.ts | 19 ++++++ .../src/config/withSentryConfig/index.ts | 5 +- .../test/config/getBuildPluginOptions.test.ts | 49 ++++++-------- .../test/config/withSentryConfig.test.ts | 66 +++++++++++++++++++ 7 files changed, 144 insertions(+), 69 deletions(-) diff --git a/packages/nextjs/src/config/getBuildPluginOptions.ts b/packages/nextjs/src/config/getBuildPluginOptions.ts index ae3086b5f389..8646833b4278 100644 --- a/packages/nextjs/src/config/getBuildPluginOptions.ts +++ b/packages/nextjs/src/config/getBuildPluginOptions.ts @@ -234,7 +234,6 @@ function createReleaseConfig( vcsRemote: sentryBuildOptions.release?.vcsRemote, setCommits: sentryBuildOptions.release?.setCommits, deploy: sentryBuildOptions.release?.deploy, - ...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.release, }; } @@ -335,7 +334,6 @@ export function getBuildPluginOptions({ ...sentryBuildOptions.reactComponentAnnotation, // eslint-disable-next-line typescript/no-deprecated ...sentryBuildOptions.webpack?.reactComponentAnnotation, - ...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation, }, silent: sentryBuildOptions.silent, url: sentryBuildOptions.sentryUrl, @@ -345,18 +343,18 @@ export function getBuildPluginOptions({ assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets, ignore: finalIgnorePatterns, filesToDeleteAfterUpload, - ...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.sourcemaps, + resolveSourceMap: sentryBuildOptions.sourcemaps?.resolveSourceMap, }, release: createReleaseConfig(releaseName, sentryBuildOptions), bundleSizeOptimizations: { ...sentryBuildOptions.bundleSizeOptimizations, }, + moduleMetadata: sentryBuildOptions.moduleMetadata, _metaOptions: { loggerPrefixOverride: loggerPrefix, telemetry: { metaFramework: 'nextjs', }, }, - ...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions, }; } diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 85df6b74404d..1786b90849eb 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -1,5 +1,10 @@ -import type { GLOBAL_OBJ } from '@sentry/core'; -import type { SentryWebpackPluginOptions } from '@sentry/bundler-plugins/webpack'; +import type { + GLOBAL_OBJ, + ModuleMetadata, + ModuleMetadataCallback, + ReactComponentAnnotationOptions, + ResolveSourceMapHook, +} from '@sentry/core'; // The first argument to `withSentryConfig` (which is the user's next config). export type ExportedNextConfig = NextConfigObject | NextConfigFunction; @@ -146,14 +151,6 @@ export type SentryBuildWebpackOptions = { excludeReplayCompressionWorker?: boolean; }; - /** - * Options to be passed directly to the Sentry Webpack Plugin (`@sentry/bundler-plugins/webpack`) that ships with the Sentry SDK. - * You can use this option to override any options the SDK passes to the Webpack plugin. - * - * Please note that this option is unstable and may change in a breaking way in any release. - */ - unstable_sentryWebpackPluginOptions?: SentryWebpackPluginOptions; - /** * Options related to react component name annotations. * Disabled by default, unless a value is set for this option. @@ -163,21 +160,7 @@ export type SentryBuildWebpackOptions = { * * @deprecated Use the top-level `reactComponentAnnotation` option instead, which works for both webpack and Turbopack builds. */ - reactComponentAnnotation?: { - /** - * Whether the component name annotate plugin should be enabled or not. - * - * @deprecated Use the top-level `reactComponentAnnotation` option instead, which works for both webpack and Turbopack builds. - */ - enabled?: boolean; - - /** - * A list of strings representing the names of components to ignore. The plugin will not apply `data-sentry` annotations on the DOM element for these components. - * - * @deprecated Use the top-level `reactComponentAnnotation` option instead, which works for both webpack and Turbopack builds. - */ - ignoredComponents?: string[]; - }; // TODO(v12): remove this option + reactComponentAnnotation?: ReactComponentAnnotationOptions; // TODO(v12): remove this option }; export type SentryBuildOptions = { @@ -317,6 +300,16 @@ export type SentryBuildOptions = { */ // oxlint-disable-next-line typescript-eslint/no-explicit-any -- matches the bundler plugin's RewriteSourcesHook type rewriteSources?: (source: string, map: any, context?: { mapDir: string }) => string; + + /** + * Hook to customize source map file resolution. + * + * Mostly helpful for complex builds with custom source map generation. For example, if source maps + * are written to a separate directory and the `//# sourceMappingURL=` comment is rewritten to + * something other than a relative path, Sentry is unable to locate the source map for a given + * build artifact. This hook lets you implement the resolution process yourself. + */ + resolveSourceMap?: ResolveSourceMapHook; }; /** @@ -503,17 +496,20 @@ export type SentryBuildOptions = { * For webpack builds, this is forwarded to `@sentry/bundler-plugins/webpack`. * For Turbopack builds, this applies the annotations via a custom loader and requires Next.js 16+. */ - reactComponentAnnotation?: { - /** - * Whether the component name annotate plugin should be enabled or not. - */ - enabled?: boolean; + reactComponentAnnotation?: ReactComponentAnnotationOptions; - /** - * A list of strings representing the names of components to ignore. The plugin will not apply `data-sentry` annotations on the DOM element for these components. - */ - ignoredComponents?: string[]; - }; + /** + * Metadata that should be associated with the built application. + * + * The metadata is serialized and can be looked up at runtime from within the SDK (for example in + * `beforeSend`, event processors, or the transport), allowing for custom event filtering logic or + * routing of events. Read it at runtime via `moduleMetadataIntegration`. + * + * Note: This currently only applies to webpack builds. On Turbopack builds it has no effect and + * the SDK warns at build time. For `thirdPartyErrorFilterIntegration` support use + * `applicationKey`, which works on both bundlers. + */ + moduleMetadata?: ModuleMetadata | ModuleMetadataCallback; /** * Options to configure various bundle size optimizations related to the Sentry SDK. diff --git a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts index 42ca76586619..d4ffa9fa901b 100644 --- a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts +++ b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts @@ -10,6 +10,7 @@ import { maybeEnableTurbopackSourcemaps, maybeSetUpRunAfterProductionCompileHook, maybeWarnAboutUnsupportedRunAfterProductionCompileHook, + maybeWarnAboutTurbopackModuleMetadata, maybeWarnAboutUnsupportedTurbopack, resolveBuildTimeInstrumentationOption, resolveUseRunAfterProductionCompileHookOption, @@ -56,6 +57,7 @@ export function getFinalConfigObject( const bundlerInfo = getBundlerInfo(nextJsVersion); maybeWarnAboutUnsupportedTurbopack(nextJsVersion, bundlerInfo); + maybeWarnAboutTurbopackModuleMetadata(userSentryOptions, bundlerInfo); maybeWarnAboutUnsupportedRunAfterProductionCompileHook(nextJsVersion, userSentryOptions, bundlerInfo); const turboPackConfig = maybeConstructTurbopackConfig( diff --git a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectBundlerUtils.ts b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectBundlerUtils.ts index 2204c0dd483f..90b00511c92a 100644 --- a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectBundlerUtils.ts +++ b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectBundlerUtils.ts @@ -46,6 +46,25 @@ export function maybeWarnAboutUnsupportedTurbopack(nextJsVersion: string | undef } } +/** + * Warns if `moduleMetadata` is set on a Turbopack build, where it currently has no effect. + * + * The Turbopack metadata loader only injects `applicationKey`; arbitrary `moduleMetadata` is + * webpack-only for now. Without this warning the option would be a silent no-op on Next.js 16+, + * where Turbopack is the default. + */ +export function maybeWarnAboutTurbopackModuleMetadata( + userSentryOptions: SentryBuildOptions, + bundlerInfo: BundlerInfo, +): void { + if (bundlerInfo.isTurbopack && userSentryOptions.moduleMetadata) { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/nextjs] WARNING: `moduleMetadata` is currently only applied on webpack builds and has no effect on Turbopack builds. Use `applicationKey` if you need `thirdPartyErrorFilterIntegration` support, which works on both bundlers.', + ); + } +} + /** * Warns if `useRunAfterProductionCompileHook` is enabled in webpack mode but the Next.js version is unsupported. */ diff --git a/packages/nextjs/src/config/withSentryConfig/index.ts b/packages/nextjs/src/config/withSentryConfig/index.ts index 68a9d8769235..a0b2f04950e0 100644 --- a/packages/nextjs/src/config/withSentryConfig/index.ts +++ b/packages/nextjs/src/config/withSentryConfig/index.ts @@ -1,4 +1,4 @@ -import { isThenable } from '@sentry/core'; +import { isThenable, warnOnRemovedBuildOptions } from '@sentry/core'; import type { ExportedNextConfig as NextConfig, NextConfigFunction, SentryBuildOptions } from '../types'; import { DEFAULT_SERVER_EXTERNAL_PACKAGES } from './constants'; import { getFinalConfigObject } from './getFinalConfigObject'; @@ -15,6 +15,9 @@ export { DEFAULT_SERVER_EXTERNAL_PACKAGES }; * @returns The wrapped Next.js config (same shape as the input) */ export function withSentryConfig(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C { + warnOnRemovedBuildOptions(sentryBuildOptions, ['unstable_sentryWebpackPluginOptions']); + warnOnRemovedBuildOptions(sentryBuildOptions.webpack, ['unstable_sentryWebpackPluginOptions']); + const castNextConfig = (nextConfig as NextConfig) || {}; if (typeof castNextConfig === 'function') { return function (this: unknown, ...webpackConfigFunctionArgs: unknown[]): ReturnType { diff --git a/packages/nextjs/test/config/getBuildPluginOptions.test.ts b/packages/nextjs/test/config/getBuildPluginOptions.test.ts index fcb448cacc52..051edb4f8aec 100644 --- a/packages/nextjs/test/config/getBuildPluginOptions.test.ts +++ b/packages/nextjs/test/config/getBuildPluginOptions.test.ts @@ -813,24 +813,18 @@ describe('getBuildPluginOptions', () => { }); }); - it('merges webpack plugin release options correctly', () => { + it('passes release options through to the plugin', () => { const sentryBuildOptions: SentryBuildOptions = { org: 'test-org', project: 'test-project', release: { create: true, vcsRemote: 'origin', - }, - webpack: { - unstable_sentryWebpackPluginOptions: { - release: { - setCommits: { - auto: true, - }, - deploy: { - env: 'production', - }, - }, + setCommits: { + auto: true, + }, + deploy: { + env: 'production', }, }, }; @@ -842,9 +836,9 @@ describe('getBuildPluginOptions', () => { buildTool: 'webpack-client', }); - // The webpack.unstable_sentryWebpackPluginOptions.release is spread at the end and may override base properties expect(result.release).toHaveProperty('setCommits.auto', true); expect(result.release).toHaveProperty('deploy.env', 'production'); + expect(result.release).toHaveProperty('vcsRemote', 'origin'); }); }); @@ -901,18 +895,14 @@ describe('getBuildPluginOptions', () => { }); }); - it('merges react component annotation options correctly for webpack builds', () => { + it('passes react component annotation options through for webpack builds', () => { const sentryBuildOptions: SentryBuildOptions = { org: 'test-org', project: 'test-project', webpack: { reactComponentAnnotation: { enabled: true, - }, - unstable_sentryWebpackPluginOptions: { - reactComponentAnnotation: { - enabled: false, // This will override the base setting - }, + ignoredComponents: ['MyComponent'], }, }, }; @@ -924,8 +914,8 @@ describe('getBuildPluginOptions', () => { buildTool: 'webpack-client', }); - // The unstable options override the base options - in this case enabled should be false - expect(result.reactComponentAnnotation).toHaveProperty('enabled', false); + expect(result.reactComponentAnnotation).toHaveProperty('enabled', true); + expect(result.reactComponentAnnotation).toHaveProperty('ignoredComponents', ['MyComponent']); }); it('sets react component annotation to undefined for after-production-compile builds', () => { @@ -997,17 +987,16 @@ describe('getBuildPluginOptions', () => { }); }); - it('merges unstable webpack plugin options correctly', () => { + it('passes top-level applicationKey, moduleMetadata and sourcemaps through to the plugin', () => { + const resolveSourceMap = (artifactPath: string): string => `${artifactPath}.map`; const sentryBuildOptions: SentryBuildOptions = { org: 'test-org', project: 'test-project', - webpack: { - unstable_sentryWebpackPluginOptions: { - applicationKey: 'test-app-key', - sourcemaps: { - disable: false, - }, - }, + applicationKey: 'test-app-key', + moduleMetadata: { team: 'sdk' }, + sourcemaps: { + disable: false, + resolveSourceMap, }, }; @@ -1020,8 +1009,10 @@ describe('getBuildPluginOptions', () => { expect(result).toMatchObject({ applicationKey: 'test-app-key', + moduleMetadata: { team: 'sdk' }, sourcemaps: expect.objectContaining({ disable: false, + resolveSourceMap, }), }); }); diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index 717ad9a2e48f..15a3c33f5f5d 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -18,6 +18,34 @@ const EXPECTED_DEFAULT_EXTERNALS = [ ]; describe('withSentryConfig', () => { + // `next.config.js` / `next.config.mjs` get no type checking, so this warning is the only signal + // those users receive that the option is gone. + describe('removed `unstable_sentryWebpackPluginOptions`', () => { + it.each([ + ['top-level', { unstable_sentryWebpackPluginOptions: { applicationKey: 'my-app' } }], + ['nested under `webpack`', { webpack: { unstable_sentryWebpackPluginOptions: { applicationKey: 'my-app' } } }], + ])('warns when set %s', (_name, sentryBuildOptions) => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + // @ts-expect-error - removed in v11, but JS configs get no type checking + materializeFinalNextConfig(exportedNextConfig, undefined, sentryBuildOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('unstable_sentryWebpackPluginOptions')); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn for a config without removed options', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith(expect.stringContaining('unstable_')); + + consoleWarnSpy.mockRestore(); + }); + }); + it('includes expected properties', () => { const finalConfig = materializeFinalNextConfig(exportedNextConfig); @@ -1147,6 +1175,44 @@ describe('withSentryConfig', () => { }); }); + describe('moduleMetadata on Turbopack', () => { + const originalTurbopack = process.env.TURBOPACK; + + afterEach(() => { + vi.restoreAllMocks(); + process.env.TURBOPACK = originalTurbopack; + }); + + // The Turbopack metadata loader only injects `applicationKey`, so `moduleMetadata` silently did + // nothing on Next.js 16+ where Turbopack is the default. + it('warns that moduleMetadata has no effect on Turbopack builds', () => { + process.env.TURBOPACK = '1'; + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig, undefined, { moduleMetadata: { team: 'sdk' } }); + + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('`moduleMetadata`')); + }); + + it('does not warn about moduleMetadata on webpack builds', () => { + delete process.env.TURBOPACK; + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig, undefined, { moduleMetadata: { team: 'sdk' } }); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith(expect.stringContaining('`moduleMetadata`')); + }); + + it('does not warn on Turbopack when moduleMetadata is unset', () => { + process.env.TURBOPACK = '1'; + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith(expect.stringContaining('`moduleMetadata`')); + }); + }); + describe('turbopack version compatibility warnings', () => { const originalTurbopack = process.env.TURBOPACK; const originalNodeEnv = process.env.NODE_ENV;