@@ -48,6 +48,9 @@ let _useLocation: UseLocation;
4848let _useNavigationType : UseNavigationType ;
4949let _createRoutesFromChildren : CreateRoutesFromChildren ;
5050let _matchRoutes : MatchRoutes ;
51+
52+ // Track the last created navigation span to prevent duplicates when router.subscribe fires multiple times
53+ let _lastCreatedNavigationSpanName : string | null = null ;
5154let _enableAsyncRouteHandlers : boolean = false ;
5255
5356const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet < Client > ( ) ;
@@ -713,6 +716,7 @@ function wrapPatchRoutesOnNavigation(
713716 } ;
714717}
715718
719+ // eslint-disable-next-line complexity
716720export function handleNavigation ( opts : {
717721 location : Location ;
718722 routes : RouteObject [ ] ;
@@ -753,15 +757,21 @@ export function handleNavigation(opts: {
753757
754758 // If we're already in a navigation span, check if we should update its name
755759 if ( isAlreadyInNavigationSpan && activeSpan ) {
756- const currentSource = spanJson ?. data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] ;
757- const shouldUpdate = shouldUpdateWildcardSpanName ( currentName , currentSource , name , source ) ;
760+ // Only update if the new name is better (doesn't have wildcards or is more complete)
761+ const shouldUpdate = currentName && transactionNameHasWildcard ( currentName ) && ! transactionNameHasWildcard ( name ) ;
758762
759763 if ( shouldUpdate ) {
760764 activeSpan . updateName ( name ) ;
761765 activeSpan . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , source as 'route' | 'url' | 'custom' ) ;
762766 DEBUG_BUILD && debug . log ( `[Tracing] Updated navigation span name from "${ currentName } " to "${ name } "` ) ;
763767 }
764768 } else if ( ! isAlreadyInNavigationSpan ) {
769+ // Prevent duplicate navigation spans when router.subscribe fires multiple times
770+ // with the same route information after a span completes
771+ if ( _lastCreatedNavigationSpanName === name ) {
772+ return ;
773+ }
774+
765775 // Cross usage can result in multiple navigation spans being created without this check
766776 const navigationSpan = startBrowserTracingNavigationSpan ( client , {
767777 name,
@@ -772,6 +782,9 @@ export function handleNavigation(opts: {
772782 } ,
773783 } ) ;
774784
785+ // Track this navigation span to prevent immediate duplicates
786+ _lastCreatedNavigationSpanName = name ;
787+
775788 // Patch navigation span to handle early cancellation (e.g., document.hidden)
776789 if ( navigationSpan ) {
777790 patchNavigationSpanEnd ( navigationSpan , location , routes , basename , allRoutes ) ;
@@ -887,11 +900,6 @@ function shouldUpdateWildcardSpanName(
887900 return true ;
888901 }
889902
890- // Allow route-to-route updates if names are different (legitimate navigation)
891- if ( currentSource === 'route' && newSource === 'route' && currentName !== newName ) {
892- return true ;
893- }
894-
895903 // Otherwise, don't update (prevents route→url downgrade)
896904 return false ;
897905}
0 commit comments