Skip to content

Commit bac7387

Browse files
authored
feat(nextjs): Add experimental flag to not strip origin information from different origin stack frames (#15418)
1 parent 08569e6 commit bac7387

File tree

6 files changed

+118
-20
lines changed

6 files changed

+118
-20
lines changed

.size-limit.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ module.exports = [
210210
import: createImport('init'),
211211
ignore: ['next/router', 'next/constants'],
212212
gzip: true,
213-
limit: '40 KB',
213+
limit: '41 KB',
214214
},
215215
// SvelteKit SDK (ESM)
216216
{

packages/nextjs/src/client/clientNormalizationIntegration.ts

+70-16
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,84 @@ import { rewriteFramesIntegration } from '@sentry/browser';
22
import { defineIntegration } from '@sentry/core';
33

44
export const nextjsClientStackFrameNormalizationIntegration = defineIntegration(
5-
({ assetPrefixPath }: { assetPrefixPath: string }) => {
5+
({
6+
assetPrefix,
7+
basePath,
8+
rewriteFramesAssetPrefixPath,
9+
experimentalThirdPartyOriginStackFrames,
10+
}: {
11+
assetPrefix?: string;
12+
basePath?: string;
13+
rewriteFramesAssetPrefixPath: string;
14+
experimentalThirdPartyOriginStackFrames: boolean;
15+
}) => {
616
const rewriteFramesInstance = rewriteFramesIntegration({
717
// Turn `<origin>/<path>/_next/static/...` into `app:///_next/static/...`
818
iteratee: frame => {
9-
try {
10-
const { origin } = new URL(frame.filename as string);
11-
frame.filename = frame.filename?.replace(origin, 'app://').replace(assetPrefixPath, '');
12-
} catch (err) {
13-
// Filename wasn't a properly formed URL, so there's nothing we can do
19+
if (experimentalThirdPartyOriginStackFrames) {
20+
// Not sure why but access to global WINDOW from @sentry/Browser causes hideous ci errors
21+
// eslint-disable-next-line no-restricted-globals
22+
const windowOrigin = typeof window !== 'undefined' && window.location ? window.location.origin : '';
23+
// A filename starting with the local origin and not ending with JS is most likely JS in HTML which we do not want to rewrite
24+
if (frame.filename?.startsWith(windowOrigin) && !frame.filename.endsWith('.js')) {
25+
return frame;
26+
}
27+
28+
if (assetPrefix) {
29+
// If the user defined an asset prefix, we need to strip it so that we can match it with uploaded sourcemaps.
30+
// assetPrefix always takes priority over basePath.
31+
if (frame.filename?.startsWith(assetPrefix)) {
32+
frame.filename = frame.filename.replace(assetPrefix, 'app://');
33+
}
34+
} else if (basePath) {
35+
// If the user defined a base path, we need to strip it to match with uploaded sourcemaps.
36+
// We should only do this for same-origin filenames though, so that third party assets are not rewritten.
37+
try {
38+
const { origin: frameOrigin } = new URL(frame.filename as string);
39+
if (frameOrigin === windowOrigin) {
40+
frame.filename = frame.filename?.replace(frameOrigin, 'app://').replace(basePath, '');
41+
}
42+
} catch (err) {
43+
// Filename wasn't a properly formed URL, so there's nothing we can do
44+
}
45+
}
46+
} else {
47+
try {
48+
const { origin } = new URL(frame.filename as string);
49+
frame.filename = frame.filename?.replace(origin, 'app://').replace(rewriteFramesAssetPrefixPath, '');
50+
} catch (err) {
51+
// Filename wasn't a properly formed URL, so there's nothing we can do
52+
}
1453
}
1554

1655
// We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces.
1756
// The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works.
18-
if (frame.filename?.startsWith('app:///_next')) {
19-
frame.filename = decodeURI(frame.filename);
20-
}
57+
if (experimentalThirdPartyOriginStackFrames) {
58+
if (frame.filename?.includes('/_next')) {
59+
frame.filename = decodeURI(frame.filename);
60+
}
61+
62+
if (
63+
frame.filename?.match(
64+
/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
65+
)
66+
) {
67+
// We don't care about these frames. It's Next.js internal code.
68+
frame.in_app = false;
69+
}
70+
} else {
71+
if (frame.filename?.startsWith('app:///_next')) {
72+
frame.filename = decodeURI(frame.filename);
73+
}
2174

22-
if (
23-
frame.filename?.match(
24-
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
25-
)
26-
) {
27-
// We don't care about these frames. It's Next.js internal code.
28-
frame.in_app = false;
75+
if (
76+
frame.filename?.match(
77+
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
78+
)
79+
) {
80+
// We don't care about these frames. It's Next.js internal code.
81+
frame.in_app = false;
82+
}
2983
}
3084

3185
return frame;

packages/nextjs/src/client/index.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export { browserTracingIntegration } from './browserTracingIntegration';
1717

1818
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
1919
_sentryRewriteFramesAssetPrefixPath: string;
20+
_sentryAssetPrefix?: string;
21+
_sentryBasePath?: string;
22+
_experimentalThirdPartyOriginStackFrames?: string;
2023
};
2124

2225
// Treeshakable guard to remove all code related to tracing
@@ -67,13 +70,25 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] {
6770
customDefaultIntegrations.push(browserTracingIntegration());
6871
}
6972

70-
// This value is injected at build time, based on the output directory specified in the build config. Though a default
73+
// These values are injected at build time, based on the output directory specified in the build config. Though a default
7174
// is set there, we set it here as well, just in case something has gone wrong with the injection.
72-
const assetPrefixPath =
75+
const rewriteFramesAssetPrefixPath =
7376
process.env._sentryRewriteFramesAssetPrefixPath ||
7477
globalWithInjectedValues._sentryRewriteFramesAssetPrefixPath ||
7578
'';
76-
customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath }));
79+
const assetPrefix = process.env._sentryAssetPrefix || globalWithInjectedValues._sentryAssetPrefix;
80+
const basePath = process.env._sentryBasePath || globalWithInjectedValues._sentryBasePath;
81+
const experimentalThirdPartyOriginStackFrames =
82+
process.env._experimentalThirdPartyOriginStackFrames === 'true' ||
83+
globalWithInjectedValues._experimentalThirdPartyOriginStackFrames === 'true';
84+
customDefaultIntegrations.push(
85+
nextjsClientStackFrameNormalizationIntegration({
86+
assetPrefix,
87+
basePath,
88+
rewriteFramesAssetPrefixPath,
89+
experimentalThirdPartyOriginStackFrames,
90+
}),
91+
);
7792

7893
return customDefaultIntegrations;
7994
}

packages/nextjs/src/config/types.ts

+9
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,15 @@ export type SentryBuildOptions = {
439439
* Defaults to `false`.
440440
*/
441441
automaticVercelMonitors?: boolean;
442+
443+
/**
444+
* Contains a set of experimental flags that might change in future releases. These flags enable
445+
* features that are still in development and may be modified, renamed, or removed without notice.
446+
* Use with caution in production environments.
447+
*/
448+
_experimental?: Partial<{
449+
thirdPartyOriginStackFrames: boolean;
450+
}>;
442451
};
443452

444453
export type NextConfigFunction = (

packages/nextjs/src/config/webpack.ts

+4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ function addValueInjectionLoader(
624624
_sentryRewriteFramesAssetPrefixPath: assetPrefix
625625
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
626626
: '',
627+
_sentryAssetPrefix: userNextConfig.assetPrefix,
628+
_sentryExperimentalThirdPartyOriginStackFrames: userSentryOptions._experimental?.thirdPartyOriginStackFrames
629+
? 'true'
630+
: undefined,
627631
};
628632

629633
if (buildContext.isServer) {

packages/nextjs/src/config/withSentryConfig.ts

+16
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,14 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt
268268
: '',
269269
};
270270

271+
if (userNextConfig.assetPrefix) {
272+
buildTimeVariables._assetsPrefix = userNextConfig.assetPrefix;
273+
}
274+
275+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
276+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
277+
}
278+
271279
if (rewritesTunnelPath) {
272280
buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath;
273281
}
@@ -276,6 +284,14 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt
276284
buildTimeVariables._sentryBasePath = basePath;
277285
}
278286

287+
if (userNextConfig.assetPrefix) {
288+
buildTimeVariables._sentryAssetPrefix = userNextConfig.assetPrefix;
289+
}
290+
291+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
292+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
293+
}
294+
279295
if (typeof userNextConfig.env === 'object') {
280296
userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env };
281297
} else if (userNextConfig.env === undefined) {

0 commit comments

Comments
 (0)