Skip to content

Commit 109b8d0

Browse files
committed
whoop whoop
1 parent 06be02e commit 109b8d0

File tree

3 files changed

+459
-10
lines changed

3 files changed

+459
-10
lines changed

packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
88
} from '@sentry/core';
99
import { startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, WINDOW } from '@sentry/react';
10+
import { maybeParameterizeRoute } from './parameterization';
1011

1112
export const INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME = 'incomplete-app-router-transaction';
1213

@@ -34,15 +35,16 @@ const currentRouterPatchingNavigationSpanRef: NavigationSpanRef = { current: und
3435

3536
/** Instruments the Next.js app router for pageloads. */
3637
export function appRouterInstrumentPageLoad(client: Client): void {
38+
const parameterizedPathname = maybeParameterizeRoute(WINDOW.location.pathname);
3739
const origin = browserPerformanceTimeOrigin();
3840
startBrowserTracingPageLoadSpan(client, {
39-
name: WINDOW.location.pathname,
41+
name: parameterizedPathname ?? WINDOW.location.pathname,
4042
// pageload should always start at timeOrigin (and needs to be in s, not ms)
4143
startTime: origin ? origin / 1000 : undefined,
4244
attributes: {
4345
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
4446
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation',
45-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
47+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
4648
},
4749
});
4850
}
@@ -85,7 +87,8 @@ const GLOBAL_OBJ_WITH_NEXT_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
8587
/** Instruments the Next.js app router for navigation. */
8688
export function appRouterInstrumentNavigation(client: Client): void {
8789
routerTransitionHandler = (href, navigationType) => {
88-
const pathname = new URL(href, WINDOW.location.href).pathname;
90+
const parameterizedPathname = maybeParameterizeRoute(href);
91+
const pathname = parameterizedPathname ?? new URL(href, WINDOW.location.href).pathname;
8992

9093
if (navigationRoutingMode === 'router-patch') {
9194
navigationRoutingMode = 'transition-start-hook';
@@ -104,23 +107,27 @@ export function appRouterInstrumentNavigation(client: Client): void {
104107
attributes: {
105108
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
106109
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.app_router_instrumentation',
107-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
110+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
108111
'navigation.type': `router.${navigationType}`,
109112
},
110113
});
111114
}
112115
};
113116

114117
WINDOW.addEventListener('popstate', () => {
118+
const parameterizedPathname = maybeParameterizeRoute(WINDOW.location.pathname);
115119
if (currentRouterPatchingNavigationSpanRef.current?.isRecording()) {
116-
currentRouterPatchingNavigationSpanRef.current.updateName(WINDOW.location.pathname);
117-
currentRouterPatchingNavigationSpanRef.current.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url');
120+
currentRouterPatchingNavigationSpanRef.current.updateName(parameterizedPathname ?? WINDOW.location.pathname);
121+
currentRouterPatchingNavigationSpanRef.current.setAttribute(
122+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
123+
parameterizedPathname ? 'route' : 'url',
124+
);
118125
} else {
119126
currentRouterPatchingNavigationSpanRef.current = startBrowserTracingNavigationSpan(client, {
120-
name: WINDOW.location.pathname,
127+
name: parameterizedPathname ?? WINDOW.location.pathname,
121128
attributes: {
122129
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.app_router_instrumentation',
123-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
130+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
124131
'navigation.type': 'browser.popstate',
125132
},
126133
});
@@ -209,9 +216,14 @@ function patchRouter(client: Client, router: NextRouter, currentNavigationSpanRe
209216
transactionAttributes['navigation.type'] = 'router.forward';
210217
}
211218

219+
const parameterizedPathname = maybeParameterizeRoute(transactionName);
220+
212221
currentNavigationSpanRef.current = startBrowserTracingNavigationSpan(client, {
213-
name: transactionName,
214-
attributes: transactionAttributes,
222+
name: parameterizedPathname ?? transactionName,
223+
attributes: {
224+
...transactionAttributes,
225+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
226+
},
215227
});
216228

217229
return target.apply(thisArg, argArray);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { GLOBAL_OBJ } from '@sentry/core';
2+
import type { RouteManifest } from '../../config/manifest/types';
3+
4+
const globalWithInjectedManifest = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
5+
_sentryRouteManifest: RouteManifest | undefined;
6+
};
7+
8+
export const maybeParameterizeRoute = (route: string): string | undefined => {
9+
const manifest = globalWithInjectedManifest._sentryRouteManifest;
10+
11+
if (!manifest) {
12+
return undefined;
13+
}
14+
15+
// Static path: no parameterization needed
16+
if (manifest.staticRoutes.some(r => r.path === route)) {
17+
return undefined;
18+
}
19+
20+
// Dynamic path: find the route pattern that matches the concrete route
21+
for (const dynamicRoute of manifest.dynamicRoutes) {
22+
if (dynamicRoute.regex) {
23+
try {
24+
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- regex patterns are from build-time route manifest, not user input
25+
const regex = new RegExp(dynamicRoute.regex);
26+
if (regex.test(route)) {
27+
return dynamicRoute.path;
28+
}
29+
} catch (error) {
30+
// Just skip this route in case of invalid regex
31+
continue;
32+
}
33+
}
34+
}
35+
36+
// We should never end up here
37+
return undefined;
38+
};

0 commit comments

Comments
 (0)