From 7c23a0c5889b6b0c27497041452d2ffb45ff15d4 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Fri, 21 Mar 2025 11:33:22 +0100
Subject: [PATCH 01/10] feat(nextjs): Use `afterProductionBuild` to upload
 sourcemaps and do release management

---
 .../nextjs/src/config/afterProductionBuild.ts | 21 +++++++++++++++++++
 packages/nextjs/src/config/util.ts            | 19 +++++++++++++++++
 packages/nextjs/src/config/webpack.ts         |  6 ++++--
 3 files changed, 44 insertions(+), 2 deletions(-)
 create mode 100644 packages/nextjs/src/config/afterProductionBuild.ts

diff --git a/packages/nextjs/src/config/afterProductionBuild.ts b/packages/nextjs/src/config/afterProductionBuild.ts
new file mode 100644
index 000000000000..df1478533488
--- /dev/null
+++ b/packages/nextjs/src/config/afterProductionBuild.ts
@@ -0,0 +1,21 @@
+import { getWebpackBuildFunctionCalled } from './util';
+
+/**
+ * TODO
+ */
+export async function afterProductionBuild(buildInfo: { distDir: string }, options: { debug: boolean }): Promise<void> {
+  // The afterProductionBuild function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic
+  if (getWebpackBuildFunctionCalled()) {
+    if (options.debug) {
+      // eslint-disable-next-line no-console
+      console.debug('[@sentry/nextjs] Not running afterProductionBuild logic because Webpack context was ran.');
+    }
+    return;
+  }
+
+  // Create release? Maybe before this hook? Add release info like env, commits etc.
+  // Finalize release?
+  // Upload everything in distDir (consider org, project, authToken, sentryUrl)
+  // Delete sourcemaps after upload?
+  // Emit telemetry?
+}
diff --git a/packages/nextjs/src/config/util.ts b/packages/nextjs/src/config/util.ts
index a88e68a57135..aa72e2151622 100644
--- a/packages/nextjs/src/config/util.ts
+++ b/packages/nextjs/src/config/util.ts
@@ -1,3 +1,4 @@
+import { GLOBAL_OBJ } from '@sentry/core';
 import * as fs from 'fs';
 import { sync as resolveSync } from 'resolve';
 
@@ -27,3 +28,21 @@ function resolveNextjsPackageJson(): string | undefined {
     return undefined;
   }
 }
+
+/**
+ * TODO
+ */
+export function setWebpackBuildFunctionCalled(): void {
+  // Let the rest of the execution context know that we are using Webpack to build.
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
+  (GLOBAL_OBJ as any)._sentryWebpackBuildFunctionCalled = true;
+}
+
+/**
+ * TODO
+ */
+export function getWebpackBuildFunctionCalled(): boolean {
+  // Let the rest of the execution context know that we are using Webpack to build.
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
+  return !!(GLOBAL_OBJ as any)._sentryWebpackBuildFunctionCalled;
+}
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index de047c0b8cf4..0aa0f7bbf46b 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -3,7 +3,7 @@
 
 import * as fs from 'fs';
 import * as path from 'path';
-import { escapeStringForRegex, loadModule, logger, parseSemver } from '@sentry/core';
+import { GLOBAL_OBJ, escapeStringForRegex, loadModule, logger, parseSemver } from '@sentry/core';
 import * as chalk from 'chalk';
 import { sync as resolveSync } from 'resolve';
 
@@ -22,7 +22,7 @@ import type {
   WebpackEntryProperty,
 } from './types';
 import { getWebpackPluginOptions } from './webpackPluginOptions';
-import { getNextjsVersion } from './util';
+import { getNextjsVersion, setWebpackBuildFunctionCalled } from './util';
 
 // Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain
 // warnings 3 times, we keep track of them here.
@@ -52,6 +52,8 @@ export function constructWebpackConfigFunction(
     incomingConfig: WebpackConfigObject,
     buildContext: BuildContext,
   ): WebpackConfigObject {
+    setWebpackBuildFunctionCalled();
+
     const { isServer, dev: isDev, dir: projectDir } = buildContext;
     const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'server') : 'client';
     // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161

From d2ec76919d0b9cfb537430366b1b82528fd1b845 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Wed, 2 Apr 2025 11:33:39 +0200
Subject: [PATCH 02/10] .

---
 packages/nextjs/src/config/afterProductionBuild.ts | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/packages/nextjs/src/config/afterProductionBuild.ts b/packages/nextjs/src/config/afterProductionBuild.ts
index df1478533488..4c445969fbb2 100644
--- a/packages/nextjs/src/config/afterProductionBuild.ts
+++ b/packages/nextjs/src/config/afterProductionBuild.ts
@@ -1,12 +1,16 @@
+import type { SentryBuildOptions } from './types';
 import { getWebpackBuildFunctionCalled } from './util';
 
 /**
  * TODO
  */
-export async function afterProductionBuild(buildInfo: { distDir: string }, options: { debug: boolean }): Promise<void> {
-  // The afterProductionBuild function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic
+export async function handleAfterProductionBuild(
+  buildInfo: { distDir: string; releaseName: string | undefined },
+  sentryBuildOptions: SentryBuildOptions,
+): Promise<void> {
+  // The handleAfterProductionBuild function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic
   if (getWebpackBuildFunctionCalled()) {
-    if (options.debug) {
+    if (sentryBuildOptions.debug) {
       // eslint-disable-next-line no-console
       console.debug('[@sentry/nextjs] Not running afterProductionBuild logic because Webpack context was ran.');
     }

From e1be0eb18bfc4c87e67f5defb7c6f9e978ba9a0b Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Wed, 2 Apr 2025 15:23:47 +0200
Subject: [PATCH 03/10] implement

---
 packages/nextjs/package.json                  |  4 +-
 .../nextjs/src/config/afterProductionBuild.ts | 33 +++++--
 packages/nextjs/src/config/types.ts           |  1 +
 packages/nextjs/src/config/webpack.ts         | 17 +++-
 .../nextjs/src/config/webpackPluginOptions.ts | 94 +++++++++++--------
 .../nextjs/src/config/withSentryConfig.ts     | 21 +++++
 yarn.lock                                     | 29 +++++-
 7 files changed, 147 insertions(+), 52 deletions(-)

diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 8938b89c14a9..c6ac6f8e139d 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -85,8 +85,10 @@
     "@sentry/opentelemetry": "9.7.0",
     "@sentry/react": "9.7.0",
     "@sentry/vercel-edge": "9.7.0",
-    "@sentry/webpack-plugin": "3.2.2",
+    "@sentry/webpack-plugin": "3.3.0-alpha.1",
+    "@sentry/bundler-plugin-core": "3.3.0-alpha.1",
     "chalk": "3.0.0",
+    "glob": "^9.3.2",
     "resolve": "1.22.8",
     "rollup": "4.35.0",
     "stacktrace-parser": "^0.1.10"
diff --git a/packages/nextjs/src/config/afterProductionBuild.ts b/packages/nextjs/src/config/afterProductionBuild.ts
index 4c445969fbb2..e03c17caf043 100644
--- a/packages/nextjs/src/config/afterProductionBuild.ts
+++ b/packages/nextjs/src/config/afterProductionBuild.ts
@@ -1,8 +1,11 @@
 import type { SentryBuildOptions } from './types';
 import { getWebpackBuildFunctionCalled } from './util';
+import { createSentryBuildPluginManager } from '@sentry/bundler-plugin-core';
+import { getBuildPluginOptions } from './webpackPluginOptions';
+import { glob } from 'glob';
 
 /**
- * TODO
+ * A function to do Sentry stuff for the `afterProductionBuild` Next.js hook
  */
 export async function handleAfterProductionBuild(
   buildInfo: { distDir: string; releaseName: string | undefined },
@@ -17,9 +20,27 @@ export async function handleAfterProductionBuild(
     return;
   }
 
-  // Create release? Maybe before this hook? Add release info like env, commits etc.
-  // Finalize release?
-  // Upload everything in distDir (consider org, project, authToken, sentryUrl)
-  // Delete sourcemaps after upload?
-  // Emit telemetry?
+  const sentryBuildPluginManager = createSentryBuildPluginManager(
+    getBuildPluginOptions(sentryBuildOptions, buildInfo.releaseName, 'after-production-build', buildInfo.distDir),
+    {
+      buildTool: 'turbopack',
+      loggerPrefix: '[@sentry/nextjs]',
+    },
+  );
+
+  const buildArtifactsPromise = glob(
+    ['/**/*.js', '/**/*.mjs', '/**/*.cjs', '/**/*.js.map', '/**/*.mjs.map', '/**/*.cjs.map'].map(
+      q => `${q}?(\\?*)?(#*)`,
+    ), // We want to allow query and hashes strings at the end of files
+    {
+      root: buildInfo.distDir,
+      absolute: true,
+      nodir: true,
+    },
+  );
+
+  await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal();
+  await sentryBuildPluginManager.createRelease();
+  await sentryBuildPluginManager.uploadSourcemaps(await buildArtifactsPromise);
+  await sentryBuildPluginManager.deleteArtifacts();
 }
diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts
index 965233d08b76..3fc39290590e 100644
--- a/packages/nextjs/src/config/types.ts
+++ b/packages/nextjs/src/config/types.ts
@@ -49,6 +49,7 @@ export type NextConfigObject = {
   productionBrowserSourceMaps?: boolean;
   // https://nextjs.org/docs/pages/api-reference/next-config-js/env
   env?: Record<string, string>;
+  afterProductionBuild?: (metadata: { projectDir: string; distDir: string }) => Promise<void>;
 };
 
 export type SentryBuildOptions = {
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index 0aa0f7bbf46b..f654499c3af9 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -3,7 +3,7 @@
 
 import * as fs from 'fs';
 import * as path from 'path';
-import { GLOBAL_OBJ, escapeStringForRegex, loadModule, logger, parseSemver } from '@sentry/core';
+import { escapeStringForRegex, loadModule, logger, parseSemver } from '@sentry/core';
 import * as chalk from 'chalk';
 import { sync as resolveSync } from 'resolve';
 
@@ -21,7 +21,7 @@ import type {
   WebpackConfigObjectWithModuleRules,
   WebpackEntryProperty,
 } from './types';
-import { getWebpackPluginOptions } from './webpackPluginOptions';
+import { getBuildPluginOptions } from './webpackPluginOptions';
 import { getNextjsVersion, setWebpackBuildFunctionCalled } from './util';
 
 // Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain
@@ -385,8 +385,19 @@ export function constructWebpackConfigFunction(
         }
 
         newConfig.plugins = newConfig.plugins || [];
+
+        const mode = ({ client: 'webpack-client', server: 'webpack-nodejs', edge: 'webpack-edge' } as const)[runtime];
+
+        // We need to convert paths to posix because Glob patterns use `\` to escape
+        // glob characters. This clashes with Windows path separators.
+        // See: https://www.npmjs.com/package/glob
+        const projectDir = buildContext.dir.replace(/\\/g, '/');
+        // `.next` is the default directory
+        const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next';
+        const distDirAbsPath = path.posix.join(projectDir, distDir);
+
         const sentryWebpackPluginInstance = sentryWebpackPlugin(
-          getWebpackPluginOptions(buildContext, userSentryOptions, releaseName),
+          getBuildPluginOptions(userSentryOptions, releaseName, mode, distDirAbsPath),
         );
         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
         sentryWebpackPluginInstance._name = 'sentry-webpack-plugin'; // For tests and debugging. Serves no other purpose.
diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts
index 530659b2a703..f7b332a62132 100644
--- a/packages/nextjs/src/config/webpackPluginOptions.ts
+++ b/packages/nextjs/src/config/webpackPluginOptions.ts
@@ -1,54 +1,75 @@
 import * as path from 'path';
 import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin';
-import type { BuildContext, NextConfigObject, SentryBuildOptions } from './types';
+import type { SentryBuildOptions } from './types';
 
 /**
  * Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or
  * client files.
  */
-export function getWebpackPluginOptions(
-  buildContext: BuildContext,
+export function getBuildPluginOptions(
   sentryBuildOptions: SentryBuildOptions,
   releaseName: string | undefined,
+  mode: 'webpack-nodejs' | 'webpack-edge' | 'webpack-client' | 'after-production-build',
+  distDirAbsPath: string,
 ): SentryWebpackPluginOptions {
-  const { isServer, config: userNextConfig, dir, nextRuntime } = buildContext;
-
-  const prefixInsert = !isServer ? 'Client' : nextRuntime === 'edge' ? 'Edge' : 'Node.js';
-
-  // We need to convert paths to posix because Glob patterns use `\` to escape
-  // glob characters. This clashes with Windows path separators.
-  // See: https://www.npmjs.com/package/glob
-  const projectDir = dir.replace(/\\/g, '/');
-  // `.next` is the default directory
-  const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next';
-  const distDirAbsPath = path.posix.join(projectDir, distDir);
+  const loggerPrefixOverride = {
+    'webpack-nodejs': '[@sentry/nextjs - Node.js]',
+    'webpack-edge': '[@sentry/nextjs - Edge]',
+    'webpack-client': '[@sentry/nextjs - Client]',
+    'after-production-build': '[@sentry/nextjs]',
+  }[mode];
 
   const sourcemapUploadAssets: string[] = [];
   const sourcemapUploadIgnore: string[] = [];
+  const filesToDeleteAfterUpload: string[] = [];
 
-  if (isServer) {
+  if (mode === 'after-production-build') {
     sourcemapUploadAssets.push(
-      path.posix.join(distDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things
-      path.posix.join(distDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js
+      path.posix.join(distDirAbsPath, '**'), // This is normally where Next.js outputs things
     );
+    if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
+      filesToDeleteAfterUpload.push(
+        path.posix.join(distDirAbsPath, '**', '*.js.map'),
+        path.posix.join(distDirAbsPath, '**', '*.mjs.map'),
+        path.posix.join(distDirAbsPath, '**', '*.cjs.map'),
+      );
+    }
   } else {
-    if (sentryBuildOptions.widenClientFileUpload) {
-      sourcemapUploadAssets.push(path.posix.join(distDirAbsPath, 'static', 'chunks', '**'));
-    } else {
+    if (mode === 'webpack-nodejs' || mode === 'webpack-edge') {
       sourcemapUploadAssets.push(
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'pages', '**'),
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'app', '**'),
+        path.posix.join(distDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things
+        path.posix.join(distDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js
+      );
+    } else {
+      if (sentryBuildOptions.widenClientFileUpload) {
+        sourcemapUploadAssets.push(path.posix.join(distDirAbsPath, 'static', 'chunks', '**'));
+      } else {
+        sourcemapUploadAssets.push(
+          path.posix.join(distDirAbsPath, 'static', 'chunks', 'pages', '**'),
+          path.posix.join(distDirAbsPath, 'static', 'chunks', 'app', '**'),
+        );
+      }
+
+      // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations.
+      sourcemapUploadIgnore.push(
+        path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework-*'),
+        path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework.*'),
+        path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'),
+        path.posix.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'),
+        path.posix.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'),
       );
     }
 
-    // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations.
-    sourcemapUploadIgnore.push(
-      path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework-*'),
-      path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework.*'),
-      path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'),
-      path.posix.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'),
-      path.posix.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'),
-    );
+    if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
+      filesToDeleteAfterUpload.push(
+        // We only care to delete client bundle source maps because they would be the ones being served.
+        // Removing the server source maps crashes Vercel builds for (thus far) unknown reasons:
+        // https://github.com/getsentry/sentry-javascript/issues/13099
+        path.posix.join(distDirAbsPath, 'static', '**', '*.js.map'),
+        path.posix.join(distDirAbsPath, 'static', '**', '*.mjs.map'),
+        path.posix.join(distDirAbsPath, 'static', '**', '*.cjs.map'),
+      );
+    }
   }
 
   return {
@@ -77,16 +98,7 @@ export function getWebpackPluginOptions(
       },
       assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets,
       ignore: sentryBuildOptions.sourcemaps?.ignore ?? sourcemapUploadIgnore,
-      filesToDeleteAfterUpload: sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload
-        ? [
-            // We only care to delete client bundle source maps because they would be the ones being served.
-            // Removing the server source maps crashes Vercel builds for (thus far) unknown reasons:
-            // https://github.com/getsentry/sentry-javascript/issues/13099
-            path.posix.join(distDirAbsPath, 'static', '**', '*.js.map'),
-            path.posix.join(distDirAbsPath, 'static', '**', '*.mjs.map'),
-            path.posix.join(distDirAbsPath, 'static', '**', '*.cjs.map'),
-          ]
-        : undefined,
+      filesToDeleteAfterUpload: filesToDeleteAfterUpload,
       ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps,
     },
     release:
@@ -111,7 +123,7 @@ export function getWebpackPluginOptions(
       ...sentryBuildOptions.bundleSizeOptimizations,
     },
     _metaOptions: {
-      loggerPrefixOverride: `[@sentry/nextjs - ${prefixInsert}]`,
+      loggerPrefixOverride,
       telemetry: {
         metaFramework: 'nextjs',
       },
diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts
index e811697cbe86..2e2c0af8fa44 100644
--- a/packages/nextjs/src/config/withSentryConfig.ts
+++ b/packages/nextjs/src/config/withSentryConfig.ts
@@ -12,6 +12,7 @@ import type {
 } from './types';
 import { constructWebpackConfigFunction } from './webpack';
 import { getNextjsVersion } from './util';
+import { handleAfterProductionBuild } from './afterProductionBuild';
 
 let showedExportModeTunnelWarning = false;
 
@@ -166,6 +167,26 @@ function getFinalConfigObject(
 
   const releaseName = userSentryOptions.release?.name ?? getSentryRelease() ?? getGitRevision();
 
+  // Used for turbopack. Runs sourcemaps upload & release management via the `afterProductionBuild` hook.
+  if (incomingUserNextConfigObject.afterProductionBuild === undefined) {
+    incomingUserNextConfigObject.afterProductionBuild = async ({ distDir }) => {
+      await handleAfterProductionBuild({ releaseName, distDir }, userSentryOptions);
+    };
+  } else if (typeof incomingUserNextConfigObject.afterProductionBuild === 'function') {
+    incomingUserNextConfigObject.afterProductionBuild = new Proxy(incomingUserNextConfigObject.afterProductionBuild, {
+      async apply(target, thisArg, argArray) {
+        const { distDir }: { distDir: string } = argArray[0] ?? { distDir: '.next' }; // should never be undefined but to be defensive
+        await target.apply(thisArg, argArray);
+        await handleAfterProductionBuild({ releaseName, distDir }, userSentryOptions);
+      },
+    });
+  } else {
+    // eslint-disable-next-line no-console
+    console.warn(
+      '[@sentry/nextjs] The configured `afterProductionBuild` option is not a function. Will not run source map and release management logic.',
+    );
+  }
+
   return {
     ...incomingUserNextConfigObject,
     webpack: constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName),
diff --git a/yarn.lock b/yarn.lock
index 73b52e4425eb..09263c00ce86 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6707,6 +6707,11 @@
   resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.2.tgz#0c5f26e417b8f524924fa4531b82ad5603216e90"
   integrity sha512-D+SKQ266ra/wo87s9+UI/rKQi3qhGPCR8eSCDe0VJudhjHsqyNU+JJ5lnIGCgmZaWFTXgdBP/gdr1Iz1zqGs4Q==
 
+"@sentry/babel-plugin-component-annotate@3.3.0-alpha.1":
+  version "3.3.0-alpha.1"
+  resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.0-alpha.1.tgz#4d839cfb7f313d93be88ccfce0a54b7c66fc1080"
+  integrity sha512-QtiIig59zHL1cVRoNaBikjF92UBpXxWq8tBe+OupP+W8U63IbLVv2C8pUPzz3QngtzzjnTY+lDFGi6LmQrmFJw==
+
 "@sentry/bundler-plugin-core@2.22.6":
   version "2.22.6"
   resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d"
@@ -6763,6 +6768,20 @@
     magic-string "0.30.8"
     unplugin "1.0.1"
 
+"@sentry/bundler-plugin-core@3.3.0-alpha.1":
+  version "3.3.0-alpha.1"
+  resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.3.0-alpha.1.tgz#356d343ec9c399416cf0c193621bc1871135279c"
+  integrity sha512-dIbwiiuTq9RJDbltl3jrPZRVyQ/BePuEhNiP2cZ0oiQPeM9SRiZcGRZ7nmG+DBlTPp+IM51R1hhskndrVg9R1Q==
+  dependencies:
+    "@babel/core" "^7.18.5"
+    "@sentry/babel-plugin-component-annotate" "3.3.0-alpha.1"
+    "@sentry/cli" "2.42.2"
+    dotenv "^16.3.1"
+    find-up "^5.0.0"
+    glob "^9.3.2"
+    magic-string "0.30.8"
+    unplugin "1.0.1"
+
 "@sentry/cli-darwin@2.41.1":
   version "2.41.1"
   resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.41.1.tgz#ca7e12bf1ad59bc2df35868ae98abc8869108efa"
@@ -6958,6 +6977,15 @@
     unplugin "1.0.1"
     uuid "^9.0.0"
 
+"@sentry/webpack-plugin@3.3.0-alpha.1":
+  version "3.3.0-alpha.1"
+  resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.3.0-alpha.1.tgz#6151b01ae51dfd0a7e724ea05b48c7e0090f70b0"
+  integrity sha512-173XxkdjL5La9KtdKxem1z2b+p6vK/ADbXy/yRIJc0q4Ayu9AVKnIQZ1GAQ4lGFxuEHgzc6eVSY+eDE6omxUxQ==
+  dependencies:
+    "@sentry/bundler-plugin-core" "3.3.0-alpha.1"
+    unplugin "1.0.1"
+    uuid "^9.0.0"
+
 "@sigstore/protobuf-specs@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4"
@@ -28153,7 +28181,6 @@ stylus@0.59.0, stylus@^0.59.0:
 
 sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills:
   version "3.36.0"
-  uid fd682f6129e507c00bb4e6319cc5d6b767e36061
   resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061"
   dependencies:
     "@jridgewell/gen-mapping" "^0.3.2"

From 79e191f8afb1fed5de0abaeaa4ffecacb85aa0f6 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Thu, 3 Apr 2025 12:19:28 +0200
Subject: [PATCH 04/10] .

---
 packages/nextjs/src/config/util.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/nextjs/src/config/util.ts b/packages/nextjs/src/config/util.ts
index aa72e2151622..688f9858b1fb 100644
--- a/packages/nextjs/src/config/util.ts
+++ b/packages/nextjs/src/config/util.ts
@@ -30,7 +30,7 @@ function resolveNextjsPackageJson(): string | undefined {
 }
 
 /**
- * TODO
+ * Leaves a mark on the global scope in the Next.js build context that webpack has been executed.
  */
 export function setWebpackBuildFunctionCalled(): void {
   // Let the rest of the execution context know that we are using Webpack to build.
@@ -39,7 +39,7 @@ export function setWebpackBuildFunctionCalled(): void {
 }
 
 /**
- * TODO
+ * Checks whether webpack has been executed fot the current Next.js build.
  */
 export function getWebpackBuildFunctionCalled(): boolean {
   // Let the rest of the execution context know that we are using Webpack to build.

From 2d015b4dbf3e68906caf70422d15cee1f3d03f3e Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Wed, 9 Apr 2025 14:14:04 +0200
Subject: [PATCH 05/10] Rename

---
 ...nBuild.ts => runAfterProductionCompile.ts} |  8 ++---
 packages/nextjs/src/config/types.ts           |  4 ++-
 .../nextjs/src/config/withSentryConfig.ts     | 31 +++++++++++--------
 3 files changed, 25 insertions(+), 18 deletions(-)
 rename packages/nextjs/src/config/{afterProductionBuild.ts => runAfterProductionCompile.ts} (77%)

diff --git a/packages/nextjs/src/config/afterProductionBuild.ts b/packages/nextjs/src/config/runAfterProductionCompile.ts
similarity index 77%
rename from packages/nextjs/src/config/afterProductionBuild.ts
rename to packages/nextjs/src/config/runAfterProductionCompile.ts
index e03c17caf043..22619ab4ce95 100644
--- a/packages/nextjs/src/config/afterProductionBuild.ts
+++ b/packages/nextjs/src/config/runAfterProductionCompile.ts
@@ -5,17 +5,17 @@ import { getBuildPluginOptions } from './webpackPluginOptions';
 import { glob } from 'glob';
 
 /**
- * A function to do Sentry stuff for the `afterProductionBuild` Next.js hook
+ * A function to do Sentry stuff for the `runAfterProductionCompile` Next.js hook
  */
-export async function handleAfterProductionBuild(
+export async function handleAfterProductionCompile(
   buildInfo: { distDir: string; releaseName: string | undefined },
   sentryBuildOptions: SentryBuildOptions,
 ): Promise<void> {
-  // The handleAfterProductionBuild function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic
+  // The handleAfterProductionCompile function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic
   if (getWebpackBuildFunctionCalled()) {
     if (sentryBuildOptions.debug) {
       // eslint-disable-next-line no-console
-      console.debug('[@sentry/nextjs] Not running afterProductionBuild logic because Webpack context was ran.');
+      console.debug('[@sentry/nextjs] Not running runAfterProductionCompile logic because Webpack context was ran.');
     }
     return;
   }
diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts
index 74363751e747..6126f3efccd3 100644
--- a/packages/nextjs/src/config/types.ts
+++ b/packages/nextjs/src/config/types.ts
@@ -50,7 +50,9 @@ export type NextConfigObject = {
   productionBrowserSourceMaps?: boolean;
   // https://nextjs.org/docs/pages/api-reference/next-config-js/env
   env?: Record<string, string>;
-  afterProductionBuild?: (metadata: { projectDir: string; distDir: string }) => Promise<void>;
+  compiler?: {
+    runAfterProductionCompile?: (metadata: { projectDir: string; distDir: string }) => Promise<void>;
+  };
 };
 
 export type SentryBuildOptions = {
diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts
index f2872d1934d1..70a1a82dbd10 100644
--- a/packages/nextjs/src/config/withSentryConfig.ts
+++ b/packages/nextjs/src/config/withSentryConfig.ts
@@ -1,3 +1,4 @@
+/* eslint-disable max-lines */
 /* eslint-disable complexity */
 import { isThenable, parseSemver } from '@sentry/core';
 
@@ -12,7 +13,7 @@ import type {
 } from './types';
 import { constructWebpackConfigFunction } from './webpack';
 import { getNextjsVersion } from './util';
-import { handleAfterProductionBuild } from './afterProductionBuild';
+import { handleAfterProductionCompile } from './runAfterProductionCompile';
 
 let showedExportModeTunnelWarning = false;
 
@@ -210,23 +211,27 @@ function getFinalConfigObject(
     );
   }
 
-  // Used for turbopack. Runs sourcemaps upload & release management via the `afterProductionBuild` hook.
-  if (incomingUserNextConfigObject.afterProductionBuild === undefined) {
-    incomingUserNextConfigObject.afterProductionBuild = async ({ distDir }) => {
-      await handleAfterProductionBuild({ releaseName, distDir }, userSentryOptions);
+  // Used for turbopack. Runs sourcemaps upload & release management via the `runAfterProductionCompile` hook.
+  if (incomingUserNextConfigObject?.compiler?.runAfterProductionCompile === undefined) {
+    incomingUserNextConfigObject.compiler ??= {};
+    incomingUserNextConfigObject.compiler.runAfterProductionCompile = async ({ distDir }) => {
+      await handleAfterProductionCompile({ releaseName, distDir }, userSentryOptions);
     };
-  } else if (typeof incomingUserNextConfigObject.afterProductionBuild === 'function') {
-    incomingUserNextConfigObject.afterProductionBuild = new Proxy(incomingUserNextConfigObject.afterProductionBuild, {
-      async apply(target, thisArg, argArray) {
-        const { distDir }: { distDir: string } = argArray[0] ?? { distDir: '.next' }; // should never be undefined but to be defensive
-        await target.apply(thisArg, argArray);
-        await handleAfterProductionBuild({ releaseName, distDir }, userSentryOptions);
+  } else if (typeof incomingUserNextConfigObject.compiler.runAfterProductionCompile === 'function') {
+    incomingUserNextConfigObject.compiler.runAfterProductionCompile = new Proxy(
+      incomingUserNextConfigObject.compiler.runAfterProductionCompile,
+      {
+        async apply(target, thisArg, argArray) {
+          const { distDir }: { distDir: string } = argArray[0] ?? { distDir: '.next' }; // should never be undefined but to be defensive
+          await target.apply(thisArg, argArray);
+          await handleAfterProductionCompile({ releaseName, distDir }, userSentryOptions);
+        },
       },
-    });
+    );
   } else {
     // eslint-disable-next-line no-console
     console.warn(
-      '[@sentry/nextjs] The configured `afterProductionBuild` option is not a function. Will not run source map and release management logic.',
+      '[@sentry/nextjs] The configured `compiler.runAfterProductionCompile` option is not a function. Will not run source map and release management logic.',
     );
   }
 

From 7c37103ddd2ae08aa262d13ff3b587135b5f0a89 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Wed, 9 Apr 2025 15:59:16 +0200
Subject: [PATCH 06/10] .

---
 .../src/config/runAfterProductionCompile.ts     | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/packages/nextjs/src/config/runAfterProductionCompile.ts b/packages/nextjs/src/config/runAfterProductionCompile.ts
index 22619ab4ce95..90cba0efe407 100644
--- a/packages/nextjs/src/config/runAfterProductionCompile.ts
+++ b/packages/nextjs/src/config/runAfterProductionCompile.ts
@@ -1,8 +1,9 @@
 import type { SentryBuildOptions } from './types';
 import { getWebpackBuildFunctionCalled } from './util';
-import { createSentryBuildPluginManager } from '@sentry/bundler-plugin-core';
 import { getBuildPluginOptions } from './webpackPluginOptions';
 import { glob } from 'glob';
+import { loadModule } from '@sentry/core';
+import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core';
 
 /**
  * A function to do Sentry stuff for the `runAfterProductionCompile` Next.js hook
@@ -20,6 +21,20 @@ export async function handleAfterProductionCompile(
     return;
   }
 
+  const { createSentryBuildPluginManager } =
+    loadModule<{ createSentryBuildPluginManager: typeof createSentryBuildPluginManagerType }>(
+      '@sentry/bundler-plugin-core',
+      module,
+    ) ?? {};
+
+  if (!createSentryBuildPluginManager) {
+    // eslint-disable-next-line no-console
+    console.warn(
+      '[@sentry/nextjs] Could not load build manager package. Will not run runAfterProductionCompile logic.',
+    );
+    return;
+  }
+
   const sentryBuildPluginManager = createSentryBuildPluginManager(
     getBuildPluginOptions(sentryBuildOptions, buildInfo.releaseName, 'after-production-build', buildInfo.distDir),
     {

From 545eade5a8275d434ef24361a52a162a26384fd2 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Wed, 9 Apr 2025 18:21:50 +0200
Subject: [PATCH 07/10] fix tests and rename

---
 ...PluginOptions.ts => buildPluginOptions.ts} | 39 +++++-----
 .../src/config/runAfterProductionCompile.ts   |  2 +-
 packages/nextjs/src/config/webpack.ts         |  2 +-
 ...ons.test.ts => buildPluginOptions.test.ts} | 74 ++++++++-----------
 .../webpack/constructWebpackConfig.test.ts    | 13 ++--
 5 files changed, 59 insertions(+), 71 deletions(-)
 rename packages/nextjs/src/config/{webpackPluginOptions.ts => buildPluginOptions.ts} (71%)
 rename packages/nextjs/test/config/webpack/{webpackPluginOptions.test.ts => buildPluginOptions.test.ts} (73%)

diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/buildPluginOptions.ts
similarity index 71%
rename from packages/nextjs/src/config/webpackPluginOptions.ts
rename to packages/nextjs/src/config/buildPluginOptions.ts
index f7b332a62132..c670344b4caf 100644
--- a/packages/nextjs/src/config/webpackPluginOptions.ts
+++ b/packages/nextjs/src/config/buildPluginOptions.ts
@@ -23,40 +23,45 @@ export function getBuildPluginOptions(
   const sourcemapUploadIgnore: string[] = [];
   const filesToDeleteAfterUpload: string[] = [];
 
+  // We need to convert paths to posix because Glob patterns use `\` to escape
+  // glob characters. This clashes with Windows path separators.
+  // See: https://www.npmjs.com/package/glob
+  const normalizedDistDirAbsPath = distDirAbsPath.replace(/\\/g, '/');
+
   if (mode === 'after-production-build') {
     sourcemapUploadAssets.push(
-      path.posix.join(distDirAbsPath, '**'), // This is normally where Next.js outputs things
+      path.posix.join(normalizedDistDirAbsPath, '**'), // This is normally where Next.js outputs things
     );
     if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
       filesToDeleteAfterUpload.push(
-        path.posix.join(distDirAbsPath, '**', '*.js.map'),
-        path.posix.join(distDirAbsPath, '**', '*.mjs.map'),
-        path.posix.join(distDirAbsPath, '**', '*.cjs.map'),
+        path.posix.join(normalizedDistDirAbsPath, '**', '*.js.map'),
+        path.posix.join(normalizedDistDirAbsPath, '**', '*.mjs.map'),
+        path.posix.join(normalizedDistDirAbsPath, '**', '*.cjs.map'),
       );
     }
   } else {
     if (mode === 'webpack-nodejs' || mode === 'webpack-edge') {
       sourcemapUploadAssets.push(
-        path.posix.join(distDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things
-        path.posix.join(distDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js
+        path.posix.join(normalizedDistDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things
+        path.posix.join(normalizedDistDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js
       );
     } else {
       if (sentryBuildOptions.widenClientFileUpload) {
-        sourcemapUploadAssets.push(path.posix.join(distDirAbsPath, 'static', 'chunks', '**'));
+        sourcemapUploadAssets.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', '**'));
       } else {
         sourcemapUploadAssets.push(
-          path.posix.join(distDirAbsPath, 'static', 'chunks', 'pages', '**'),
-          path.posix.join(distDirAbsPath, 'static', 'chunks', 'app', '**'),
+          path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'pages', '**'),
+          path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'app', '**'),
         );
       }
 
       // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations.
       sourcemapUploadIgnore.push(
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework-*'),
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework.*'),
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'),
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'),
-        path.posix.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework-*'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework.*'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'main-*'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'polyfills-*'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'webpack-*'),
       );
     }
 
@@ -65,9 +70,9 @@ export function getBuildPluginOptions(
         // We only care to delete client bundle source maps because they would be the ones being served.
         // Removing the server source maps crashes Vercel builds for (thus far) unknown reasons:
         // https://github.com/getsentry/sentry-javascript/issues/13099
-        path.posix.join(distDirAbsPath, 'static', '**', '*.js.map'),
-        path.posix.join(distDirAbsPath, 'static', '**', '*.mjs.map'),
-        path.posix.join(distDirAbsPath, 'static', '**', '*.cjs.map'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.js.map'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.mjs.map'),
+        path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.cjs.map'),
       );
     }
   }
diff --git a/packages/nextjs/src/config/runAfterProductionCompile.ts b/packages/nextjs/src/config/runAfterProductionCompile.ts
index 90cba0efe407..c2e60b2513af 100644
--- a/packages/nextjs/src/config/runAfterProductionCompile.ts
+++ b/packages/nextjs/src/config/runAfterProductionCompile.ts
@@ -1,6 +1,6 @@
 import type { SentryBuildOptions } from './types';
 import { getWebpackBuildFunctionCalled } from './util';
-import { getBuildPluginOptions } from './webpackPluginOptions';
+import { getBuildPluginOptions } from './buildPluginOptions';
 import { glob } from 'glob';
 import { loadModule } from '@sentry/core';
 import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core';
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index 100d65587d00..51bc98b7ac32 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -21,7 +21,7 @@ import type {
   WebpackConfigObjectWithModuleRules,
   WebpackEntryProperty,
 } from './types';
-import { getBuildPluginOptions } from './webpackPluginOptions';
+import { getBuildPluginOptions } from './buildPluginOptions';
 import { getNextjsVersion, setWebpackBuildFunctionCalled } from './util';
 
 // Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain
diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/buildPluginOptions.test.ts
similarity index 73%
rename from packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
rename to packages/nextjs/test/config/webpack/buildPluginOptions.test.ts
index d2aad8fb19a0..419c00635a46 100644
--- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
+++ b/packages/nextjs/test/config/webpack/buildPluginOptions.test.ts
@@ -1,33 +1,10 @@
 import { describe, it, expect } from 'vitest';
 
-import type { BuildContext, NextConfigObject } from '../../../src/config/types';
-import { getWebpackPluginOptions } from '../../../src/config/webpackPluginOptions';
+import { getBuildPluginOptions } from '../../../src/config/buildPluginOptions';
 
-function generateBuildContext(overrides: {
-  dir?: string;
-  isServer: boolean;
-  nextjsConfig?: NextConfigObject;
-}): BuildContext {
-  return {
-    dev: false, // The plugin is not included in dev mode
-    isServer: overrides.isServer,
-    buildId: 'test-build-id',
-    dir: overrides.dir ?? '/my/project/dir',
-    config: overrides.nextjsConfig ?? {},
-    totalPages: 2,
-    defaultLoaders: true,
-    webpack: {
-      version: '4.0.0',
-      DefinePlugin: {} as any,
-    },
-  };
-}
-
-describe('getWebpackPluginOptions()', () => {
+describe('getBuildPluginOptions()', () => {
   it('forwards relevant options', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(
-      buildContext,
+    const generatedPluginOptions = getBuildPluginOptions(
       {
         authToken: 'my-auth-token',
         headers: { 'my-test-header': 'test' },
@@ -60,6 +37,8 @@ describe('getWebpackPluginOptions()', () => {
         },
       },
       'my-release',
+      'webpack-client',
+      '/my/project/dir/.next',
     );
 
     expect(generatedPluginOptions.authToken).toBe('my-auth-token');
@@ -119,9 +98,7 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('forwards bundleSizeOptimization options', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(
-      buildContext,
+    const generatedPluginOptions = getBuildPluginOptions(
       {
         bundleSizeOptimizations: {
           excludeTracing: true,
@@ -129,6 +106,8 @@ describe('getWebpackPluginOptions()', () => {
         },
       },
       undefined,
+      'webpack-client',
+      '/my/project/dir/.next',
     );
 
     expect(generatedPluginOptions).toMatchObject({
@@ -140,8 +119,7 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('returns the right `assets` and `ignore` values during the server build', () => {
-    const buildContext = generateBuildContext({ isServer: true });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined);
+    const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-nodejs', '/my/project/dir/.next');
     expect(generatedPluginOptions.sourcemaps).toMatchObject({
       assets: ['/my/project/dir/.next/server/**', '/my/project/dir/.next/serverless/**'],
       ignore: [],
@@ -149,8 +127,7 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('returns the right `assets` and `ignore` values during the client build', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined);
+    const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-client', '/my/project/dir/.next');
     expect(generatedPluginOptions.sourcemaps).toMatchObject({
       assets: ['/my/project/dir/.next/static/chunks/pages/**', '/my/project/dir/.next/static/chunks/app/**'],
       ignore: [
@@ -164,8 +141,12 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('returns the right `assets` and `ignore` values during the client build with `widenClientFileUpload`', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, { widenClientFileUpload: true }, undefined);
+    const generatedPluginOptions = getBuildPluginOptions(
+      { widenClientFileUpload: true },
+      undefined,
+      'webpack-client',
+      '/my/project/dir/.next',
+    );
     expect(generatedPluginOptions.sourcemaps).toMatchObject({
       assets: ['/my/project/dir/.next/static/chunks/**'],
       ignore: [
@@ -179,20 +160,24 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('sets `sourcemaps.disable` plugin options to true when `sourcemaps.disable` is true', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, { sourcemaps: { disable: true } }, undefined);
+    const generatedPluginOptions = getBuildPluginOptions(
+      { sourcemaps: { disable: true } },
+      undefined,
+      'webpack-client',
+      '/my/project/dir/.next',
+    );
     expect(generatedPluginOptions.sourcemaps).toMatchObject({
       disable: true,
     });
   });
 
   it('passes posix paths to the plugin', () => {
-    const buildContext = generateBuildContext({
-      dir: 'C:\\my\\windows\\project\\dir',
-      nextjsConfig: { distDir: '.dist\\v1' },
-      isServer: false,
-    });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, { widenClientFileUpload: true }, undefined);
+    const generatedPluginOptions = getBuildPluginOptions(
+      { widenClientFileUpload: true },
+      undefined,
+      'webpack-client',
+      'C:\\my\\windows\\project\\dir\\.dist\\v1',
+    );
     expect(generatedPluginOptions.sourcemaps).toMatchObject({
       assets: ['C:/my/windows/project/dir/.dist/v1/static/chunks/**'],
       ignore: [
@@ -206,8 +191,7 @@ describe('getWebpackPluginOptions()', () => {
   });
 
   it('sets options to not create a release or do any release operations when releaseName is undefined', () => {
-    const buildContext = generateBuildContext({ isServer: false });
-    const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined);
+    const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-client', '/my/project/dir/.next');
 
     expect(generatedPluginOptions).toMatchObject({
       release: {
diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
index aaedb3d729df..2d8c4cc92ce1 100644
--- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
+++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
 // mock helper functions not tested directly in this file
 import '../mocks';
 
-import * as getWebpackPluginOptionsModule from '../../../src/config/webpackPluginOptions';
+import * as getBuildPluginOptionsModule from '../../../src/config/buildPluginOptions';
 import {
   CLIENT_SDK_CONFIG_FILE,
   clientBuildContext,
@@ -55,7 +55,7 @@ describe('constructWebpackConfigFunction()', () => {
   });
 
   it('automatically enables deleteSourcemapsAfterUpload for client builds when not explicitly set', async () => {
-    const getWebpackPluginOptionsSpy = vi.spyOn(getWebpackPluginOptionsModule, 'getWebpackPluginOptions');
+    const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions');
     vi.spyOn(core, 'loadModule').mockImplementation(() => ({
       sentryWebpackPlugin: () => ({
         _name: 'sentry-webpack-plugin',
@@ -71,19 +71,18 @@ describe('constructWebpackConfigFunction()', () => {
       },
     });
 
-    expect(getWebpackPluginOptionsSpy).toHaveBeenCalledWith(
-      expect.objectContaining({
-        isServer: false,
-      }),
+    expect(getBuildPluginOptionsSpy).toHaveBeenCalledWith(
       expect.objectContaining({
         sourcemaps: {
           deleteSourcemapsAfterUpload: true,
         },
       }),
       undefined,
+      expect.any(String),
+      expect.any(String),
     );
 
-    getWebpackPluginOptionsSpy.mockRestore();
+    getBuildPluginOptionsSpy.mockRestore();
   });
 
   it('preserves unrelated webpack config options', async () => {

From b009386c60e7d8a651d7389d05b58493bb447602 Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Mon, 5 May 2025 13:33:23 +0200
Subject: [PATCH 08/10] undo yarn

---
 yarn.lock | 28 ----------------------------
 1 file changed, 28 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index c97a869ce7fe..346dae607107 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6464,11 +6464,6 @@
   resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.4.tgz#c0877df6e5ce227bf51754bf27da2fa5227af847"
   integrity sha512-yBzRn3GEUSv1RPtE4xB4LnuH74ZxtdoRJ5cmQ9i6mzlmGDxlrnKuvem5++AolZTE9oJqAD3Tx2rd1PqmpWnLoA==
 
-"@sentry/babel-plugin-component-annotate@3.3.0-alpha.1":
-  version "3.3.0-alpha.1"
-  resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.0-alpha.1.tgz#4d839cfb7f313d93be88ccfce0a54b7c66fc1080"
-  integrity sha512-QtiIig59zHL1cVRoNaBikjF92UBpXxWq8tBe+OupP+W8U63IbLVv2C8pUPzz3QngtzzjnTY+lDFGi6LmQrmFJw==
-
 "@sentry/bundler-plugin-core@2.22.6":
   version "2.22.6"
   resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d"
@@ -6497,20 +6492,6 @@
     magic-string "0.30.8"
     unplugin "1.0.1"
 
-"@sentry/bundler-plugin-core@3.3.0-alpha.1":
-  version "3.3.0-alpha.1"
-  resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.3.0-alpha.1.tgz#356d343ec9c399416cf0c193621bc1871135279c"
-  integrity sha512-dIbwiiuTq9RJDbltl3jrPZRVyQ/BePuEhNiP2cZ0oiQPeM9SRiZcGRZ7nmG+DBlTPp+IM51R1hhskndrVg9R1Q==
-  dependencies:
-    "@babel/core" "^7.18.5"
-    "@sentry/babel-plugin-component-annotate" "3.3.0-alpha.1"
-    "@sentry/cli" "2.42.2"
-    dotenv "^16.3.1"
-    find-up "^5.0.0"
-    glob "^9.3.2"
-    magic-string "0.30.8"
-    unplugin "1.0.1"
-
 "@sentry/cli-darwin@2.42.2":
   version "2.42.2"
   resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz#a32a4f226e717122b37d9969e8d4d0e14779f720"
@@ -6658,15 +6639,6 @@
     unplugin "1.0.1"
     uuid "^9.0.0"
 
-"@sentry/webpack-plugin@3.3.0-alpha.1":
-  version "3.3.0-alpha.1"
-  resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.3.0-alpha.1.tgz#6151b01ae51dfd0a7e724ea05b48c7e0090f70b0"
-  integrity sha512-173XxkdjL5La9KtdKxem1z2b+p6vK/ADbXy/yRIJc0q4Ayu9AVKnIQZ1GAQ4lGFxuEHgzc6eVSY+eDE6omxUxQ==
-  dependencies:
-    "@sentry/bundler-plugin-core" "3.3.0-alpha.1"
-    unplugin "1.0.1"
-    uuid "^9.0.0"
-
 "@sigstore/protobuf-specs@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4"

From 2f3cc111889f92c7e659c2d0b255b8efa41e17ee Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Mon, 5 May 2025 13:33:45 +0200
Subject: [PATCH 09/10] bump

---
 packages/nextjs/package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index c7a1f7225865..d577580a2f81 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -85,8 +85,8 @@
     "@sentry/opentelemetry": "9.12.0",
     "@sentry/react": "9.12.0",
     "@sentry/vercel-edge": "9.12.0",
-    "@sentry/webpack-plugin": "3.3.0-alpha.1",
-    "@sentry/bundler-plugin-core": "3.3.0-alpha.1",
+    "@sentry/webpack-plugin": "3.4.0",
+    "@sentry/bundler-plugin-core": "3.4.0",
     "chalk": "3.0.0",
     "glob": "^9.3.2",
     "resolve": "1.22.8",

From 9a2b03007ba3da8377c266872d00cd7340867d1b Mon Sep 17 00:00:00 2001
From: Luca Forstner <luca.forstner@sentry.io>
Date: Mon, 5 May 2025 13:39:06 +0200
Subject: [PATCH 10/10] lint

---
 packages/nextjs/src/config/buildPluginOptions.ts        | 2 +-
 packages/nextjs/src/config/runAfterProductionCompile.ts | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/packages/nextjs/src/config/buildPluginOptions.ts b/packages/nextjs/src/config/buildPluginOptions.ts
index c670344b4caf..683127b21601 100644
--- a/packages/nextjs/src/config/buildPluginOptions.ts
+++ b/packages/nextjs/src/config/buildPluginOptions.ts
@@ -1,5 +1,5 @@
-import * as path from 'path';
 import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin';
+import * as path from 'path';
 import type { SentryBuildOptions } from './types';
 
 /**
diff --git a/packages/nextjs/src/config/runAfterProductionCompile.ts b/packages/nextjs/src/config/runAfterProductionCompile.ts
index c2e60b2513af..e4a403561be8 100644
--- a/packages/nextjs/src/config/runAfterProductionCompile.ts
+++ b/packages/nextjs/src/config/runAfterProductionCompile.ts
@@ -1,9 +1,9 @@
+import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core';
+import { loadModule } from '@sentry/core';
+import { glob } from 'glob';
+import { getBuildPluginOptions } from './buildPluginOptions';
 import type { SentryBuildOptions } from './types';
 import { getWebpackBuildFunctionCalled } from './util';
-import { getBuildPluginOptions } from './buildPluginOptions';
-import { glob } from 'glob';
-import { loadModule } from '@sentry/core';
-import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core';
 
 /**
  * A function to do Sentry stuff for the `runAfterProductionCompile` Next.js hook