@@ -104,6 +104,15 @@ interface ReactNavigationIntegrationOptions {
104104 * @default false
105105 */
106106 useFullPathsForNavigationRoutes : boolean ;
107+
108+ /**
109+ * Track performance of route prefetching operations.
110+ * Creates separate spans for PRELOAD actions to measure prefetch performance.
111+ * This is useful for Expo Router apps that use the prefetch functionality.
112+ *
113+ * @default false
114+ */
115+ enablePrefetchTracking : boolean ;
107116}
108117
109118/**
@@ -121,6 +130,7 @@ export const reactNavigationIntegration = ({
121130 enableTimeToInitialDisplayForPreloadedRoutes = false ,
122131 useDispatchedActionData = false ,
123132 useFullPathsForNavigationRoutes = false ,
133+ enablePrefetchTracking = false ,
124134} : Partial < ReactNavigationIntegrationOptions > = { } ) : Integration & {
125135 /**
126136 * Pass the ref to the navigation container to register it to the instrumentation
@@ -253,12 +263,48 @@ export const reactNavigationIntegration = ({
253263 }
254264
255265 const navigationActionType = useDispatchedActionData ? event ?. data . action . type : undefined ;
266+
267+ // Handle PRELOAD actions separately if prefetch tracking is enabled
268+ if ( enablePrefetchTracking && navigationActionType === 'PRELOAD' ) {
269+ const preloadData = event ?. data . action ;
270+ const payload = preloadData ?. payload ;
271+ const targetRoute =
272+ payload && typeof payload === 'object' && 'name' in payload && typeof payload . name === 'string'
273+ ? payload . name
274+ : 'Unknown Route' ;
275+
276+ debug . log ( `${ INTEGRATION_NAME } Starting prefetch span for route: ${ targetRoute } ` ) ;
277+
278+ const prefetchSpan = startInactiveSpan ( {
279+ op : 'navigation.prefetch' ,
280+ name : `Prefetch ${ targetRoute } ` ,
281+ attributes : {
282+ 'route.name' : targetRoute ,
283+ } ,
284+ } ) ;
285+
286+ // Store prefetch span to end it when state changes or timeout
287+ navigationProcessingSpan = prefetchSpan ;
288+
289+ // Set timeout to ensure we don't leave hanging spans
290+ stateChangeTimeout = setTimeout ( ( ) => {
291+ if ( navigationProcessingSpan === prefetchSpan ) {
292+ debug . log ( `${ INTEGRATION_NAME } Prefetch span timed out for route: ${ targetRoute } ` ) ;
293+ prefetchSpan ?. setStatus ( { code : SPAN_STATUS_OK } ) ;
294+ prefetchSpan ?. end ( ) ;
295+ navigationProcessingSpan = undefined ;
296+ }
297+ } , routeChangeTimeoutMs ) ;
298+
299+ return ;
300+ }
301+
256302 if (
257303 useDispatchedActionData &&
258304 navigationActionType &&
259305 [
260306 // Process common actions
261- 'PRELOAD' ,
307+ 'PRELOAD' , // Still filter PRELOAD when enablePrefetchTracking is false
262308 'SET_PARAMS' ,
263309 // Drawer actions
264310 'OPEN_DRAWER' ,
@@ -447,6 +493,7 @@ export const reactNavigationIntegration = ({
447493 enableTimeToInitialDisplayForPreloadedRoutes,
448494 useDispatchedActionData,
449495 useFullPathsForNavigationRoutes,
496+ enablePrefetchTracking,
450497 } ,
451498 } ;
452499} ;
0 commit comments