diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 81ee686ce205..b29fbb6881af 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -486,6 +486,18 @@ export type SentryBuildOptions = { */ disableManifestInjection?: boolean; + /** + * Disables automatic injection of Sentry's Webpack configuration. + * + * By default, the Sentry Next.js SDK injects its own Webpack configuration to enable features such as + * source map upload and automatic instrumentation. Set this option to `true` if you want to prevent + * the SDK from modifying your Webpack config (for example, if you want to handle Sentry integration manually + * or if you are on an older version of Next.js while using Turbopack). + * + * @default false + */ + disableSentryWebpackConfig?: boolean; + /** * Contains a set of experimental flags that might change in future releases. These flags enable * features that are still in development and may be modified, renamed, or removed without notice. diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 5ba768f3d0b7..85ade8e682de 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -311,9 +311,10 @@ function getFinalConfigObject( ], }, }), - webpack: !isTurbopack - ? constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName, routeManifest) - : undefined, + webpack: + isTurbopack || userSentryOptions.disableSentryWebpackConfig + ? incomingUserNextConfigObject.webpack // just return the original webpack config + : constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName, routeManifest), ...(isTurbopackSupported && isTurbopack ? { turbopack: constructTurbopackConfig({ diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index f1f1ae13f9fa..3b872d810c49 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -145,6 +145,130 @@ describe('withSentryConfig', () => { }); }); + describe('webpack configuration behavior', () => { + const originalTurbopack = process.env.TURBOPACK; + + afterEach(() => { + vi.restoreAllMocks(); + process.env.TURBOPACK = originalTurbopack; + }); + + it('uses constructed webpack function when Turbopack is disabled and disableSentryWebpackConfig is false/undefined', () => { + delete process.env.TURBOPACK; + + // default behavior + const finalConfigUndefined = materializeFinalNextConfig(exportedNextConfig); + expect(finalConfigUndefined.webpack).toBeInstanceOf(Function); + + const sentryOptions = { + disableSentryWebpackConfig: false, + }; + const finalConfigFalse = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + expect(finalConfigFalse.webpack).toBeInstanceOf(Function); + }); + + it('preserves original webpack config when disableSentryWebpackConfig is true (regardless of Turbopack)', () => { + const originalWebpackFunction = vi.fn(); + const configWithWebpack = { + ...exportedNextConfig, + webpack: originalWebpackFunction, + }; + + const sentryOptions = { + disableSentryWebpackConfig: true, + }; + + delete process.env.TURBOPACK; + const finalConfigWithoutTurbopack = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptions); + expect(finalConfigWithoutTurbopack.webpack).toBe(originalWebpackFunction); + + process.env.TURBOPACK = '1'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + const finalConfigWithTurbopack = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptions); + expect(finalConfigWithTurbopack.webpack).toBe(originalWebpackFunction); + }); + + it('preserves original webpack config when Turbopack is enabled (ignores disableSentryWebpackConfig flag)', () => { + process.env.TURBOPACK = '1'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + + const originalWebpackFunction = vi.fn(); + const configWithWebpack = { + ...exportedNextConfig, + webpack: originalWebpackFunction, + }; + + const sentryOptionsWithFalse = { + disableSentryWebpackConfig: false, + }; + const finalConfigWithFalse = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptionsWithFalse); + expect(finalConfigWithFalse.webpack).toBe(originalWebpackFunction); + + const finalConfigWithUndefined = materializeFinalNextConfig(configWithWebpack); + expect(finalConfigWithUndefined.webpack).toBe(originalWebpackFunction); + + const sentryOptionsWithTrue = { + disableSentryWebpackConfig: true, + }; + const finalConfigWithTrue = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptionsWithTrue); + expect(finalConfigWithTrue.webpack).toBe(originalWebpackFunction); + }); + + it('preserves original webpack config when Turbopack is enabled and disableSentryWebpackConfig is true', () => { + process.env.TURBOPACK = '1'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + + const sentryOptions = { + disableSentryWebpackConfig: true, + }; + + const originalWebpackFunction = vi.fn(); + const configWithWebpack = { + ...exportedNextConfig, + webpack: originalWebpackFunction, + }; + + const finalConfig = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptions); + + expect(finalConfig.webpack).toBe(originalWebpackFunction); + }); + + it('preserves undefined webpack when Turbopack is enabled, disableSentryWebpackConfig is true, and no original webpack config exists', () => { + process.env.TURBOPACK = '1'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + + const sentryOptions = { + disableSentryWebpackConfig: true, + }; + + const configWithoutWebpack = { + ...exportedNextConfig, + }; + delete configWithoutWebpack.webpack; + + const finalConfig = materializeFinalNextConfig(configWithoutWebpack, undefined, sentryOptions); + + expect(finalConfig.webpack).toBeUndefined(); + }); + + it('includes turbopack config when Turbopack is supported and enabled', () => { + process.env.TURBOPACK = '1'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + + const finalConfig = materializeFinalNextConfig(exportedNextConfig); + + expect(finalConfig.turbopack).toBeDefined(); + }); + + it('does not include turbopack config when Turbopack is not enabled', () => { + delete process.env.TURBOPACK; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig); + + expect(finalConfig.turbopack).toBeUndefined(); + }); + }); + describe('release injection behavior', () => { afterEach(() => { vi.restoreAllMocks();