Skip to content

Commit 3750914

Browse files
author
Luca Forstner
authored
ref(nextjs): Make build-time value injection turbopack compatible (#14081)
Ref: #8105 To inject build-time variables, in addition to doing so via a custom loader, we will be injecting them via the `env` option. Caveat: We are currently using the Next.js build ID as a release name. This build id is passed to the `webpack` option. Since the `webpack` option doesn't exist for turbopack we don't have access to the build ID. For now we will simply not inject a release name, which may be better anyhow since turbopack is currently only stable for dev.
1 parent 0c36564 commit 3750914

File tree

14 files changed

+82
-34
lines changed

14 files changed

+82
-34
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ module.exports = [
7979
path: 'packages/browser/build/npm/esm/index.js',
8080
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
8181
gzip: true,
82-
limit: '78.1 KB',
82+
limit: '78.2 KB',
8383
},
8484
{
8585
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',

packages/browser-utils/src/metrics/browserMetrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable max-lines */
2-
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/core';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan } from '@sentry/core';
33
import { setMeasurement } from '@sentry/core';
44
import type { Measurements, Span, SpanAttributes, StartSpanOptions } from '@sentry/types';
55
import { browserPerformanceTimeOrigin, getComponentName, htmlTreeAsString, logger, parseUrl } from '@sentry/utils';

packages/nextjs/src/client/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export * from '@sentry/react';
1616
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
1717

1818
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
19-
__rewriteFramesAssetPrefixPath__: string;
19+
_sentryRewriteFramesAssetPrefixPath: string;
2020
};
2121

2222
// Treeshakable guard to remove all code related to tracing
@@ -64,7 +64,10 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] {
6464

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

7073
return customDefaultIntegrations;

packages/nextjs/src/client/tunnelRoute.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils';
44
import { DEBUG_BUILD } from '../common/debug-build';
55

66
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
7-
__sentryRewritesTunnelPath__?: string;
7+
_sentryRewritesTunnelPath?: string;
88
};
99

1010
/**
1111
* Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option.
1212
*/
1313
export function applyTunnelRouteOption(options: BrowserOptions): void {
14-
const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__;
14+
const tunnelRouteOption = process.env._sentryRewritesTunnelPath || globalWithInjectedValues._sentryRewritesTunnelPath;
1515
if (tunnelRouteOption && options.dsn) {
1616
const dsnComponents = dsnFromString(options.dsn);
1717
if (!dsnComponents) {

packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type OriginalStackFrameResponse = {
1111
};
1212

1313
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
14-
__sentryBasePath?: string;
14+
_sentryBasePath?: string;
1515
};
1616

1717
async function resolveStackFrame(
@@ -32,7 +32,7 @@ async function resolveStackFrame(
3232
params.append(key, (frame[key as keyof typeof frame] ?? '').toString());
3333
});
3434

35-
let basePath = globalWithInjectedValues.__sentryBasePath ?? '';
35+
let basePath = process.env._sentryBasePath ?? globalWithInjectedValues._sentryBasePath ?? '';
3636

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

packages/nextjs/src/config/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export type NextConfigObject = {
4747
clientTraceMetadata?: string[];
4848
};
4949
productionBrowserSourceMaps?: boolean;
50+
// https://nextjs.org/docs/pages/api-reference/next-config-js/env
51+
env?: Record<string, string>;
5052
};
5153

5254
export type SentryBuildOptions = {
@@ -548,7 +550,7 @@ export type ModuleRuleUseProperty = {
548550
* Global with values we add when we inject code into people's pages, for use at runtime.
549551
*/
550552
export type EnhancedGlobal = typeof GLOBAL_OBJ & {
551-
__rewriteFramesDistDir__?: string;
553+
_sentryRewriteFramesDistDir?: string;
552554
SENTRY_RELEASE?: { id: string };
553555
SENTRY_RELEASES?: { [key: string]: { id: string } };
554556
};

packages/nextjs/src/config/webpack.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi
562562
/**
563563
* Adds loaders to inject values on the global object based on user configuration.
564564
*/
565+
// 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.
566+
// 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.
565567
function addValueInjectionLoader(
566568
newConfig: WebpackConfigObjectWithModuleRules,
567569
userNextConfig: NextConfigObject,
@@ -572,7 +574,7 @@ function addValueInjectionLoader(
572574

573575
const isomorphicValues = {
574576
// `rewritesTunnel` set by the user in Next.js config
575-
__sentryRewritesTunnelPath__:
577+
_sentryRewritesTunnelPath:
576578
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
577579
? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}`
578580
: undefined,
@@ -582,21 +584,21 @@ function addValueInjectionLoader(
582584
SENTRY_RELEASE: buildContext.dev
583585
? undefined
584586
: { id: userSentryOptions.release?.name ?? getSentryRelease(buildContext.buildId) },
585-
__sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined,
587+
_sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined,
586588
};
587589

588590
const serverValues = {
589591
...isomorphicValues,
590592
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
591593
// characters)
592-
__rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
594+
_sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
593595
};
594596

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

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ let showedExportModeTunnelWarning = false;
2020
* @param sentryBuildOptions Additional options to configure instrumentation and
2121
* @returns The modified config to be exported
2222
*/
23+
// TODO(v9): Always return an async function here to allow us to do async things like grabbing a deterministic build ID.
2324
export function withSentryConfig<C>(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C {
2425
const castNextConfig = (nextConfig as NextConfig) || {};
2526
if (typeof castNextConfig === 'function') {
@@ -73,6 +74,8 @@ function getFinalConfigObject(
7374
}
7475
}
7576

77+
setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions);
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,43 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s
253256
};
254257
}
255258

259+
// TODO(v9): Inject the release into all the bundles. This is breaking because grabbing the build ID if the user provides
260+
// it in `generateBuildId` (https://nextjs.org/docs/app/api-reference/next-config-js/generateBuildId) is async but we do
261+
// not turn the next config function in the type it was passed.
262+
function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void {
263+
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
264+
const basePath = userNextConfig.basePath ?? '';
265+
const rewritesTunnelPath =
266+
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
267+
? `${basePath}${userSentryOptions.tunnelRoute}`
268+
: undefined;
269+
270+
const buildTimeVariables: Record<string, string> = {
271+
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
272+
// characters)
273+
_sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
274+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
275+
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
276+
_sentryRewriteFramesAssetPrefixPath: assetPrefix
277+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
278+
: '',
279+
};
280+
281+
if (rewritesTunnelPath) {
282+
buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath;
283+
}
284+
285+
if (basePath) {
286+
buildTimeVariables._sentryBasePath = basePath;
287+
}
288+
289+
if (typeof userNextConfig.env === 'object') {
290+
userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env };
291+
} else if (userNextConfig.env === undefined) {
292+
userNextConfig.env = buildTimeVariables;
293+
}
294+
}
295+
256296
function getNextjsVersion(): string | undefined {
257297
const nextjsPackageJsonPath = resolveNextjsPackageJson();
258298
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 = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir;
4040

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

packages/nextjs/src/edge/rewriteFramesIntegration.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types';
33
import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils';
44

55
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
6-
__rewriteFramesDistDir__?: string;
6+
_sentryRewriteFramesDistDir?: string;
77
};
88

99
type StackFrameIteratee = (frame: StackFrame) => StackFrame;
@@ -14,9 +14,8 @@ interface RewriteFramesOptions {
1414
}
1515

1616
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__;
17+
// This value is injected at build time, based on the output directory specified in the build config.
18+
const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir;
2019

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

0 commit comments

Comments
 (0)