Skip to content

feat(nextjs)!: Respect user-provided source map generation settings #14956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 10, 2025
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
4 changes: 4 additions & 0 deletions docs/migration/v8-to-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ In v9, an `undefined` value will be treated the same as if the value is not defi

This behavior was changed because the Next.js Build ID is non-deterministic and the release name is injected into client bundles, causing build artifacts to be non-deterministic. This caused issues for some users. Additionally, because it is uncertain whether it will be possible to rely on a Build ID when Turbopack becomes stable, we decided to pull the plug now instead of introducing confusing behavior in the future.

- Source maps are now automatically enabled for both client and server builds unless explicitly disabled via `sourcemaps.disable`. Client builds use `hidden-source-map` while server builds use `source-map` as their webpack `devtool` setting unless any other value than `false` or `undefined` has been assigned already.

- By default, source maps will now be automatically deleted after being uploaded to Sentry for client-side builds. You can opt out of this behavior by explicitly setting `sourcemaps.deleteSourcemapsAfterUpload` to `false` in your Sentry config.

### All Meta-Framework SDKs (`@sentry/astro`, `@sentry/nuxt`)

- Updated source map generation to respect the user-provided value of your build config, such as `vite.build.sourcemap`:
Expand Down
43 changes: 28 additions & 15 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,23 +336,36 @@ export function constructWebpackConfigFunction(

if (sentryWebpackPlugin) {
if (!userSentryOptions.sourcemaps?.disable) {
// TODO(v9): Remove this warning and print warning in case source map deletion is auto configured
if (!isServer && !userSentryOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
// eslint-disable-next-line no-console
console.warn(
"[@sentry/nextjs] The Sentry SDK has enabled source map generation for your Next.js app. If you don't want to serve Source Maps to your users, either set the `sourcemaps.deleteSourcemapsAfterUpload` option to true, or manually delete the source maps after the build. In future Sentry SDK versions `sourcemaps.deleteSourcemapsAfterUpload` will default to `true`. If you do not want to generate and upload sourcemaps, set the `sourcemaps.disable` option in `withSentryConfig()`.",
);
// Source maps can be configured in 3 ways:
// 1. (next config): productionBrowserSourceMaps
// 2. (next config): experimental.serverSourceMaps
// 3. custom webpack configuration
//
// We only update this if no explicit value is set
// (Next.js defaults to `false`: https://github.com/vercel/next.js/blob/5f4f96c133bd6b10954812cc2fef6af085b82aa5/packages/next/src/build/webpack/config/blocks/base.ts#L61)
if (!newConfig.devtool) {
logger.info(`[@sentry/nextjs] Automatically enabling source map generation for ${runtime} build.`);
// `hidden-source-map` produces the same sourcemaps as `source-map`, but doesn't include the `sourceMappingURL`
// comment at the bottom. For folks who aren't publicly hosting their sourcemaps, this is helpful because then
// the browser won't look for them and throw errors into the console when it can't find them. Because this is a
// front-end-only problem, and because `sentry-cli` handles sourcemaps more reliably with the comment than
// without, the option to use `hidden-source-map` only applies to the client-side build.
if (isServer) {
newConfig.devtool = 'source-map';
} else {
newConfig.devtool = 'hidden-source-map';
}
}

// `hidden-source-map` produces the same sourcemaps as `source-map`, but doesn't include the `sourceMappingURL`
// comment at the bottom. For folks who aren't publicly hosting their sourcemaps, this is helpful because then
// the browser won't look for them and throw errors into the console when it can't find them. Because this is a
// front-end-only problem, and because `sentry-cli` handles sourcemaps more reliably with the comment than
// without, the option to use `hidden-source-map` only applies to the client-side build.
if (isServer || userNextConfig.productionBrowserSourceMaps) {
newConfig.devtool = 'source-map';
} else {
newConfig.devtool = 'hidden-source-map';
// enable source map deletion if not explicitly disabled
if (!isServer && userSentryOptions.sourcemaps?.deleteSourcemapsAfterUpload === undefined) {
logger.warn(
'[@sentry/nextjs] Source maps will be automatically deleted after being uploaded to Sentry. If you want to keep the source maps, set the `sourcemaps.deleteSourcemapsAfterUpload` option to false in `withSentryConfig()`. If you do not want to generate and upload sourcemaps at all, set the `sourcemaps.disable` option to true.',
);
userSentryOptions.sourcemaps = {
...userSentryOptions.sourcemaps,
deleteSourcemapsAfterUpload: true,
};
}
}

Expand Down
43 changes: 43 additions & 0 deletions packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// mock helper functions not tested directly in this file
import '../mocks';

import * as getWebpackPluginOptionsModule from '../../../src/config/webpackPluginOptions';
import {
CLIENT_SDK_CONFIG_FILE,
clientBuildContext,
Expand Down Expand Up @@ -29,6 +30,48 @@ describe('constructWebpackConfigFunction()', () => {
);
});

it('preserves existing devtool setting', async () => {
const customDevtool = 'eval-source-map';
const finalWebpackConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: {
...serverWebpackConfig,
devtool: customDevtool,
},
incomingWebpackBuildContext: serverBuildContext,
sentryBuildTimeOptions: {},
});

expect(finalWebpackConfig.devtool).toEqual(customDevtool);
});

it('automatically enables deleteSourcemapsAfterUpload for client builds when not explicitly set', async () => {
const getWebpackPluginOptionsSpy = jest.spyOn(getWebpackPluginOptionsModule, 'getWebpackPluginOptions');

await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: clientWebpackConfig,
incomingWebpackBuildContext: clientBuildContext,
sentryBuildTimeOptions: {
sourcemaps: {},
},
});

expect(getWebpackPluginOptionsSpy).toHaveBeenCalledWith(
expect.objectContaining({
isServer: false,
}),
expect.objectContaining({
sourcemaps: {
deleteSourcemapsAfterUpload: true,
},
}),
undefined,
);

getWebpackPluginOptionsSpy.mockRestore();
});

it('preserves unrelated webpack config options', async () => {
const finalWebpackConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
Expand Down
Loading