Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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.

- 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.

- 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.

### 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
45 changes: 31 additions & 14 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,23 +336,40 @@ 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()`.",
// 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,
};
}

// `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';
// 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 ${
isServer ? 'server' : 'client'
} 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';
}
}
}

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