Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/nextjs/src/config/getBuildPluginOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ function createReleaseConfig(
vcsRemote: sentryBuildOptions.release?.vcsRemote,
setCommits: sentryBuildOptions.release?.setCommits,
deploy: sentryBuildOptions.release?.deploy,
...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.release,
};
}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
};
}
66 changes: 31 additions & 35 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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 = {
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: why do we have this new option now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was not hoisted before, we'll move nextjs to the shared type

};

/**
Expand Down Expand Up @@ -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;
Comment thread
chargome marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: same for this one

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this, we only have applicationKey, which calls this behind the scenes but we'll make it available top level


/**
* Options to configure various bundle size optimizations related to the Sentry SDK.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
maybeEnableTurbopackSourcemaps,
maybeSetUpRunAfterProductionCompileHook,
maybeWarnAboutUnsupportedRunAfterProductionCompileHook,
maybeWarnAboutTurbopackModuleMetadata,
maybeWarnAboutUnsupportedTurbopack,
resolveBuildTimeInstrumentationOption,
resolveUseRunAfterProductionCompileHookOption,
Expand Down Expand Up @@ -56,6 +57,7 @@ export function getFinalConfigObject(

const bundlerInfo = getBundlerInfo(nextJsVersion);
maybeWarnAboutUnsupportedTurbopack(nextJsVersion, bundlerInfo);
maybeWarnAboutTurbopackModuleMetadata(userSentryOptions, bundlerInfo);
maybeWarnAboutUnsupportedRunAfterProductionCompileHook(nextJsVersion, userSentryOptions, bundlerInfo);

const turboPackConfig = maybeConstructTurbopackConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
5 changes: 4 additions & 1 deletion packages/nextjs/src/config/withSentryConfig/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,6 +15,9 @@ export { DEFAULT_SERVER_EXTERNAL_PACKAGES };
* @returns The wrapped Next.js config (same shape as the input)
*/
export function withSentryConfig<C>(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<NextConfigFunction> {
Expand Down
49 changes: 20 additions & 29 deletions packages/nextjs/test/config/getBuildPluginOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
};
Expand All @@ -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');
});
});

Expand Down Expand Up @@ -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'],
},
},
};
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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,
},
};

Expand All @@ -1020,8 +1009,10 @@ describe('getBuildPluginOptions', () => {

expect(result).toMatchObject({
applicationKey: 'test-app-key',
moduleMetadata: { team: 'sdk' },
sourcemaps: expect.objectContaining({
disable: false,
resolveSourceMap,
}),
});
});
Expand Down
66 changes: 66 additions & 0 deletions packages/nextjs/test/config/withSentryConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Comment thread
chargome marked this conversation as resolved.

it('includes expected properties', () => {
const finalConfig = materializeFinalNextConfig(exportedNextConfig);

Expand Down Expand Up @@ -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;
Expand Down
Loading