Skip to content

Commit 106fa11

Browse files
author
Luca Forstner
committed
ref(nextjs): Make build-time value injection turbopack compatible
1 parent fe639f4 commit 106fa11

File tree

13 files changed

+68
-50
lines changed

13 files changed

+68
-50
lines changed

packages/nextjs/src/client/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { addEventProcessor, applySdkMetadata } from '@sentry/core';
22
import type { BrowserOptions } from '@sentry/react';
33
import { getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit } from '@sentry/react';
44
import type { Client, EventProcessor, Integration } from '@sentry/types';
5-
import { GLOBAL_OBJ } from '@sentry/utils';
65

76
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
87
import { getVercelEnv } from '../common/getVercelEnv';
@@ -15,10 +14,6 @@ export * from '@sentry/react';
1514

1615
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
1716

18-
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
19-
__rewriteFramesAssetPrefixPath__: string;
20-
};
21-
2217
// Treeshakable guard to remove all code related to tracing
2318
declare const __SENTRY_TRACING__: boolean;
2419

@@ -64,7 +59,7 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] {
6459

6560
// This value is injected at build time, based on the output directory specified in the build config. Though a default
6661
// is set there, we set it here as well, just in case something has gone wrong with the injection.
67-
const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || '';
62+
const assetPrefixPath = process.env.__sentryRewriteFramesAssetPrefixPath || '';
6863
customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath }));
6964

7065
return customDefaultIntegrations;

packages/nextjs/src/client/tunnelRoute.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import type { BrowserOptions } from '@sentry/react';
2-
import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils';
2+
import { dsnFromString, logger } from '@sentry/utils';
33

44
import { DEBUG_BUILD } from '../common/debug-build';
55

6-
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
7-
__sentryRewritesTunnelPath__?: string;
8-
};
9-
106
/**
117
* Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option.
128
*/
139
export function applyTunnelRouteOption(options: BrowserOptions): void {
14-
const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__;
10+
const tunnelRouteOption = process.env.__sentryRewritesTunnelPath;
1511
if (tunnelRouteOption && options.dsn) {
1612
const dsnComponents = dsnFromString(options.dsn);
1713
if (!dsnComponents) {

packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { suppressTracing } from '@sentry/core';
22
import type { Event, EventHint } from '@sentry/types';
3-
import { GLOBAL_OBJ } from '@sentry/utils';
43
import type { StackFrame } from 'stacktrace-parser';
54
import * as stackTraceParser from 'stacktrace-parser';
65

@@ -10,10 +9,6 @@ type OriginalStackFrameResponse = {
109
sourcePackage?: string;
1110
};
1211

13-
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
14-
__sentryBasePath?: string;
15-
};
16-
1712
async function resolveStackFrame(
1813
frame: StackFrame,
1914
error: Error,
@@ -32,7 +27,7 @@ async function resolveStackFrame(
3227
params.append(key, (frame[key as keyof typeof frame] ?? '').toString());
3328
});
3429

35-
let basePath = globalWithInjectedValues.__sentryBasePath ?? '';
30+
let basePath = process.env.__sentryBasePath ?? '';
3631

3732
// Prefix the basepath with a slash if it doesn't have one
3833
if (basePath !== '' && !basePath.match(/^\//)) {

packages/nextjs/src/config/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type NextConfigObject = {
4949
clientTraceMetadata?: string[];
5050
};
5151
productionBrowserSourceMaps?: boolean;
52+
env?: Record<string, string>;
5253
};
5354

5455
export type SentryBuildOptions = {
@@ -550,7 +551,7 @@ export type ModuleRuleUseProperty = {
550551
* Global with values we add when we inject code into people's pages, for use at runtime.
551552
*/
552553
export type EnhancedGlobal = typeof GLOBAL_OBJ & {
553-
__rewriteFramesDistDir__?: string;
554+
__sentryRewriteFramesDistDir?: string;
554555
SENTRY_RELEASE?: { id: string };
555556
SENTRY_RELEASES?: { [key: string]: { id: string } };
556557
};

packages/nextjs/src/config/webpack.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi
561561
/**
562562
* Adds loaders to inject values on the global object based on user configuration.
563563
*/
564+
// TODO(v9): Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach.
565+
// In order to remove this loader though we need to make sure the minimum supported Next.js version includes this PR (https://github.com/vercel/next.js/pull/61194), otherwise the nextConfig.env based approach will not work, as our SDK code is not processed by Next.js.
564566
function addValueInjectionLoader(
565567
newConfig: WebpackConfigObjectWithModuleRules,
566568
userNextConfig: NextConfigObject,
@@ -571,7 +573,7 @@ function addValueInjectionLoader(
571573

572574
const isomorphicValues = {
573575
// `rewritesTunnel` set by the user in Next.js config
574-
__sentryRewritesTunnelPath__:
576+
__sentryRewritesTunnelPath:
575577
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
576578
? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}`
577579
: undefined,
@@ -588,14 +590,14 @@ function addValueInjectionLoader(
588590
...isomorphicValues,
589591
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
590592
// characters)
591-
__rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
593+
__sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
592594
};
593595

594596
const clientValues = {
595597
...isomorphicValues,
596598
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
597599
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
598-
__rewriteFramesAssetPrefixPath__: assetPrefix
600+
__sentryRewriteFramesAssetPrefixPath: assetPrefix
599601
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
600602
: '',
601603
};

packages/nextjs/src/config/webpackPluginOptions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function getWebpackPluginOptions(
1111
buildContext: BuildContext,
1212
sentryBuildOptions: SentryBuildOptions,
1313
): SentryWebpackPluginOptions {
14-
const { buildId, isServer, config: userNextConfig, dir, nextRuntime } = buildContext;
14+
const { isServer, config: userNextConfig, dir, nextRuntime } = buildContext;
1515

1616
const prefixInsert = !isServer ? 'Client' : nextRuntime === 'edge' ? 'Edge' : 'Node.js';
1717

@@ -93,8 +93,8 @@ export function getWebpackPluginOptions(
9393
...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps,
9494
},
9595
release: {
96-
inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
97-
name: sentryBuildOptions.release?.name ?? getSentryRelease(buildId),
96+
inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject with nextConfig.env instead
97+
name: sentryBuildOptions.release?.name ?? getSentryRelease('TODO'),
9898
create: sentryBuildOptions.release?.create,
9999
finalize: sentryBuildOptions.release?.finalize,
100100
dist: sentryBuildOptions.release?.dist,

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { isThenable, parseSemver } from '@sentry/utils';
33

44
import * as fs from 'fs';
5+
import { getSentryRelease } from '@sentry/node';
56
import { sync as resolveSync } from 'resolve';
67
import type {
78
ExportedNextConfig as NextConfig,
@@ -73,6 +74,8 @@ function getFinalConfigObject(
7374
}
7475
}
7576

77+
setUpBuildTimeVariables(incomingUserNextConfigObject);
78+
7679
const nextJsVersion = getNextjsVersion();
7780

7881
// Add the `clientTraceMetadata` experimental option based on Next.js version. The option got introduced in Next.js version 15.0.0 (actually 14.3.0-canary.64).
@@ -253,6 +256,37 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s
253256
};
254257
}
255258

259+
function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void {
260+
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
261+
262+
const buildTimeVariables = {
263+
// `rewritesTunnel` set by the user in Next.js config
264+
__sentryRewritesTunnelPath:
265+
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
266+
? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}`
267+
: undefined,
268+
SENTRY_RELEASE:
269+
process.env.NODE_ENV === 'production'
270+
? undefined
271+
: { id: userSentryOptions.release?.name ?? getSentryRelease('TODO') },
272+
__sentryBasePath: userNextConfig.basePath,
273+
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
274+
// characters)
275+
__sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
276+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
277+
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
278+
__sentryRewriteFramesAssetPrefixPath: assetPrefix
279+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
280+
: '',
281+
};
282+
283+
if (typeof userNextConfig.env === 'object') {
284+
userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env };
285+
} else if (userNextConfig.env === undefined) {
286+
userNextConfig.env = buildTimeVariables;
287+
}
288+
}
289+
256290
function getNextjsVersion(): string | undefined {
257291
const nextjsPackageJsonPath = resolveNextjsPackageJson();
258292
if (nextjsPackageJsonPath) {

packages/nextjs/src/edge/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume
2121
export type EdgeOptions = VercelEdgeOptions;
2222

2323
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
24-
__rewriteFramesDistDir__?: string;
24+
__sentryRewriteFramesDistDir?: string;
2525
};
2626

2727
/** Inits the Sentry NextJS SDK on the Edge Runtime. */
@@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void {
3636

3737
// This value is injected at build time, based on the output directory specified in the build config. Though a default
3838
// is set there, we set it here as well, just in case something has gone wrong with the injection.
39-
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
39+
const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir;
4040

4141
if (distDirName) {
4242
customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName }));

packages/nextjs/src/edge/rewriteFramesIntegration.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { defineIntegration, rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/core';
22
import type { IntegrationFn, StackFrame } from '@sentry/types';
3-
import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils';
4-
5-
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
6-
__rewriteFramesDistDir__?: string;
7-
};
3+
import { escapeStringForRegex } from '@sentry/utils';
84

95
type StackFrameIteratee = (frame: StackFrame) => StackFrame;
106
interface RewriteFramesOptions {
@@ -14,9 +10,8 @@ interface RewriteFramesOptions {
1410
}
1511

1612
export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => {
17-
// This value is injected at build time, based on the output directory specified in the build config. Though a default
18-
// is set there, we set it here as well, just in case something has gone wrong with the injection.
19-
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
13+
// This value is injected at build time, based on the output directory specified in the build config.
14+
const distDirName = process.env.__sentryRewriteFramesDistDir;
2015

2116
if (distDirName) {
2217
const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one

packages/nextjs/src/server/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export * from '@sentry/node';
4242
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
4343

4444
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
45-
__rewriteFramesDistDir__?: string;
46-
__sentryRewritesTunnelPath__?: string;
45+
__sentryRewriteFramesDistDir?: string;
46+
__sentryRewritesTunnelPath?: string;
4747
};
4848

4949
/**
@@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined {
109109

110110
// This value is injected at build time, based on the output directory specified in the build config. Though a default
111111
// is set there, we set it here as well, just in case something has gone wrong with the injection.
112-
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
112+
const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir;
113113
if (distDirName) {
114114
customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName }));
115115
}
@@ -212,8 +212,8 @@ export function init(options: NodeOptions): NodeClient | undefined {
212212

213213
// Filter out transactions for requests to the tunnel route
214214
if (
215-
globalWithInjectedValues.__sentryRewritesTunnelPath__ &&
216-
event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath__}`
215+
globalWithInjectedValues.__sentryRewritesTunnelPath &&
216+
event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath}`
217217
) {
218218
return null;
219219
}

0 commit comments

Comments
 (0)