@@ -24,6 +24,10 @@ export function appRouterInstrumentPageLoad(client: Client): void {
2424 } ) ;
2525}
2626
27+ interface NavigationSpanRef {
28+ current : Span | undefined ;
29+ }
30+
2731interface NextRouter {
2832 back : ( ) => void ;
2933 forward : ( ) => void ;
@@ -57,14 +61,14 @@ const GLOBAL_OBJ_WITH_NEXT_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
5761
5862/** Instruments the Next.js app router for navigation. */
5963export function appRouterInstrumentNavigation ( client : Client ) : void {
60- let currentNavigationSpan : Span | undefined = undefined ;
64+ const currentNavigationSpanRef : NavigationSpanRef = { current : undefined } ;
6165
6266 WINDOW . addEventListener ( 'popstate' , ( ) => {
63- if ( currentNavigationSpan ?. isRecording ( ) ) {
64- currentNavigationSpan . updateName ( WINDOW . location . pathname ) ;
65- currentNavigationSpan . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , 'url' ) ;
67+ if ( currentNavigationSpanRef . current ?. isRecording ( ) ) {
68+ currentNavigationSpanRef . current . updateName ( WINDOW . location . pathname ) ;
69+ currentNavigationSpanRef . current . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , 'url' ) ;
6670 } else {
67- currentNavigationSpan = startBrowserTracingNavigationSpan ( client , {
71+ currentNavigationSpanRef . current = startBrowserTracingNavigationSpan ( client , {
6872 name : WINDOW . location . pathname ,
6973 attributes : {
7074 [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' ,
@@ -89,38 +93,22 @@ export function appRouterInstrumentNavigation(client: Client): void {
8993 } else if ( router ) {
9094 clearInterval ( checkForRouterAvailabilityInterval ) ;
9195 routerPatched = true ;
92- ( [ 'back' , 'forward' , 'push' , 'replace' ] as const ) . forEach ( routerFunctionName => {
93- if ( router ?. [ routerFunctionName ] ) {
94- // @ts -expect-error Weird type error related to not knowing how to associate return values with the individual functions - we can just ignore
95- router [ routerFunctionName ] = new Proxy ( router [ routerFunctionName ] , {
96- apply ( target , thisArg , argArray ) {
97- let transactionName = INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME ;
98- const transactionAttributes : Record < string , string > = {
99- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' ,
100- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.nextjs.app_router_instrumentation' ,
101- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' ,
102- } ;
103-
104- if ( routerFunctionName === 'push' ) {
105- transactionName = transactionNameifyRouterArgument ( argArray [ 0 ] ) ;
106- transactionAttributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] = 'url' ;
107- transactionAttributes [ 'navigation.type' ] = 'router.push' ;
108- } else if ( routerFunctionName === 'replace' ) {
109- transactionName = transactionNameifyRouterArgument ( argArray [ 0 ] ) ;
110- transactionAttributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] = 'url' ;
111- transactionAttributes [ 'navigation.type' ] = 'router.replace' ;
112- } else if ( routerFunctionName === 'back' ) {
113- transactionAttributes [ 'navigation.type' ] = 'router.back' ;
114- } else if ( routerFunctionName === 'forward' ) {
115- transactionAttributes [ 'navigation.type' ] = 'router.forward' ;
116- }
11796
118- currentNavigationSpan = startBrowserTracingNavigationSpan ( client , {
119- name : transactionName ,
120- attributes : transactionAttributes ,
121- } ) ;
97+ patchRouter ( client , router , currentNavigationSpanRef ) ;
98+
99+ // If the router at any point gets overridden - patch again
100+ ( [ 'nd' , 'next' ] as const ) . forEach ( globalValueName => {
101+ const globalValue = GLOBAL_OBJ_WITH_NEXT_ROUTER [ globalValueName ] ;
102+ if ( globalValue ) {
103+ GLOBAL_OBJ_WITH_NEXT_ROUTER [ globalValueName ] = new Proxy ( globalValue , {
104+ set ( target , p , newValue ) {
105+ if ( p === 'router' && typeof newValue === 'object' && newValue !== null ) {
106+ patchRouter ( client , newValue , currentNavigationSpanRef ) ;
107+ }
122108
123- return target . apply ( thisArg , argArray ) ;
109+ // @ts -expect-error we cannot possibly type this
110+ target [ p ] = newValue ;
111+ return true ;
124112 } ,
125113 } ) ;
126114 }
@@ -137,3 +125,49 @@ function transactionNameifyRouterArgument(target: string): string {
137125 return '/' ;
138126 }
139127}
128+
129+ const patchedRouters = new WeakSet < NextRouter > ( ) ;
130+
131+ function patchRouter ( client : Client , router : NextRouter , currentNavigationSpanRef : NavigationSpanRef ) : void {
132+ if ( patchedRouters . has ( router ) ) {
133+ return ;
134+ }
135+ patchedRouters . add ( router ) ;
136+
137+ ( [ 'back' , 'forward' , 'push' , 'replace' ] as const ) . forEach ( routerFunctionName => {
138+ if ( router ?. [ routerFunctionName ] ) {
139+ // @ts -expect-error Weird type error related to not knowing how to associate return values with the individual functions - we can just ignore
140+ router [ routerFunctionName ] = new Proxy ( router [ routerFunctionName ] , {
141+ apply ( target , thisArg , argArray ) {
142+ let transactionName = INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME ;
143+ const transactionAttributes : Record < string , string > = {
144+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' ,
145+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.nextjs.app_router_instrumentation' ,
146+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' ,
147+ } ;
148+
149+ if ( routerFunctionName === 'push' ) {
150+ transactionName = transactionNameifyRouterArgument ( argArray [ 0 ] ) ;
151+ transactionAttributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] = 'url' ;
152+ transactionAttributes [ 'navigation.type' ] = 'router.push' ;
153+ } else if ( routerFunctionName === 'replace' ) {
154+ transactionName = transactionNameifyRouterArgument ( argArray [ 0 ] ) ;
155+ transactionAttributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] = 'url' ;
156+ transactionAttributes [ 'navigation.type' ] = 'router.replace' ;
157+ } else if ( routerFunctionName === 'back' ) {
158+ transactionAttributes [ 'navigation.type' ] = 'router.back' ;
159+ } else if ( routerFunctionName === 'forward' ) {
160+ transactionAttributes [ 'navigation.type' ] = 'router.forward' ;
161+ }
162+
163+ currentNavigationSpanRef . current = startBrowserTracingNavigationSpan ( client , {
164+ name : transactionName ,
165+ attributes : transactionAttributes ,
166+ } ) ;
167+
168+ return target . apply ( thisArg , argArray ) ;
169+ } ,
170+ } ) ;
171+ }
172+ } ) ;
173+ }
0 commit comments