From 69a799e8f809a234a65a2d64d8d1b334b1fe6439 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 8 Jan 2026 10:54:55 +0100 Subject: [PATCH] fix(react,solid,vue): Fix parametrization behavior for non-matched routes Our e2e tests started breaking around the `1.142.x` release of tanstack router, we ended up hard-pinning the version to `1.141.8`. The failures revealed a real behavioral change of matches tanstack router returns when attempting to match a non-existing route. Previously the matches would be an empty array, but we now get a `__root__` match. The fix involves checking if the last match is `__root__` and falling back to `url` for `sentry.source` attributes. Closes: #18672 --- .../solid-tanstack-router/package.json | 2 +- .../solid-tanstack-router/src/main.tsx | 2 +- .../tanstack-router/package.json | 2 +- .../tanstack-router/src/main.tsx | 2 +- .../vue-tanstack-router/package.json | 2 +- .../vue-tanstack-router/src/main.ts | 2 +- packages/react/src/tanstackrouter.ts | 31 ++++++++++++------- packages/solid/src/tanstackrouter.ts | 31 ++++++++++++------- packages/vue/src/tanstackrouter.ts | 26 ++++++++++------ 9 files changed, 60 insertions(+), 40 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json index 973ab3c5b921..8082add342d1 100644 --- a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json @@ -15,7 +15,7 @@ "dependencies": { "@sentry/solid": "latest || *", "@tailwindcss/vite": "^4.0.6", - "@tanstack/solid-router": "1.141.8", + "@tanstack/solid-router": "^1.141.8", "@tanstack/solid-router-devtools": "^1.132.25", "@tanstack/solid-start": "^1.132.25", "solid-js": "^1.9.5", diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx index 4580fa6e8a90..6561d96ce6b1 100644 --- a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx @@ -39,7 +39,7 @@ const indexRoute = createRoute({ const postsRoute = createRoute({ getParentRoute: () => rootRoute, - path: 'posts/', + path: 'posts', }); const postIdRoute = createRoute({ diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json index 64f92e662ae0..edb4a6cd6707 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@sentry/react": "latest || *", - "@tanstack/react-router": "1.64.0", + "@tanstack/react-router": "^1.64.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx b/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx index 3574d4ffb81a..01d867f63c65 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx @@ -41,7 +41,7 @@ const indexRoute = createRoute({ const postsRoute = createRoute({ getParentRoute: () => rootRoute, - path: 'posts/', + path: 'posts', }); const postIdRoute = createRoute({ diff --git a/dev-packages/e2e-tests/test-applications/vue-tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/package.json index 1e3d436e101c..448876ec6d2b 100644 --- a/dev-packages/e2e-tests/test-applications/vue-tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@sentry/vue": "latest || *", - "@tanstack/vue-router": "1.141.8", + "@tanstack/vue-router": "^1.141.8", "vue": "^3.4.15" }, "devDependencies": { diff --git a/dev-packages/e2e-tests/test-applications/vue-tanstack-router/src/main.ts b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/src/main.ts index cdeec524fb50..2b44a6297ca7 100644 --- a/dev-packages/e2e-tests/test-applications/vue-tanstack-router/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/src/main.ts @@ -19,7 +19,7 @@ const indexRoute = createRoute({ const postsRoute = createRoute({ getParentRoute: () => rootRoute, - path: 'posts/', + path: 'posts', }); const postIdRoute = createRoute({ diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts index 0eba31722819..d2424697d9d5 100644 --- a/packages/react/src/tanstackrouter.ts +++ b/packages/react/src/tanstackrouter.ts @@ -49,14 +49,17 @@ export function tanstackRouterBrowserTracingIntegration( ); const lastMatch = matchedRoutes[matchedRoutes.length - 1]; + // If we only match __root__, we ended up not matching any route at all, so + // we fall back to the pathname. + const routeMatch = lastMatch?.routeId !== '__root__' ? lastMatch : undefined; startBrowserTracingPageLoadSpan(client, { - name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname, + name: routeMatch ? routeMatch.routeId : initialWindowLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url', - ...routeMatchToParamSpanAttributes(lastMatch), + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeMatch ? 'route' : 'url', + ...routeMatchToParamSpanAttributes(routeMatch), }, }); } @@ -74,21 +77,23 @@ export function tanstackRouterBrowserTracingIntegration( return; } - const onResolvedMatchedRoutes = castRouterInstance.matchRoutes( + const matchedRoutesOnBeforeNavigate = castRouterInstance.matchRoutes( onBeforeNavigateArgs.toLocation.pathname, onBeforeNavigateArgs.toLocation.search, { preload: false, throwOnError: false }, ); - const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onBeforeNavigateLastMatch = matchedRoutesOnBeforeNavigate[matchedRoutesOnBeforeNavigate.length - 1]; + const onBeforeNavigateRouteMatch = + onBeforeNavigateLastMatch?.routeId !== '__root__' ? onBeforeNavigateLastMatch : undefined; const navigationLocation = WINDOW.location; const navigationSpan = startBrowserTracingNavigationSpan(client, { - name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname, + name: onBeforeNavigateRouteMatch ? onBeforeNavigateRouteMatch.routeId : navigationLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateRouteMatch ? 'route' : 'url', }, }); @@ -96,18 +101,20 @@ export function tanstackRouterBrowserTracingIntegration( const unsubscribeOnResolved = castRouterInstance.subscribe('onResolved', onResolvedArgs => { unsubscribeOnResolved(); if (navigationSpan) { - const onResolvedMatchedRoutes = castRouterInstance.matchRoutes( + const matchedRoutesOnResolved = castRouterInstance.matchRoutes( onResolvedArgs.toLocation.pathname, onResolvedArgs.toLocation.search, { preload: false, throwOnError: false }, ); - const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onResolvedLastMatch = matchedRoutesOnResolved[matchedRoutesOnResolved.length - 1]; + const onResolvedRouteMatch = + onResolvedLastMatch?.routeId !== '__root__' ? onResolvedLastMatch : undefined; - if (onResolvedLastMatch) { - navigationSpan.updateName(onResolvedLastMatch.routeId); + if (onResolvedRouteMatch) { + navigationSpan.updateName(onResolvedRouteMatch.routeId); navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch)); + navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedRouteMatch)); } } }); diff --git a/packages/solid/src/tanstackrouter.ts b/packages/solid/src/tanstackrouter.ts index 389ce9bb6e10..c589430eb615 100644 --- a/packages/solid/src/tanstackrouter.ts +++ b/packages/solid/src/tanstackrouter.ts @@ -48,14 +48,17 @@ export function tanstackRouterBrowserTracingIntegration( ); const lastMatch = matchedRoutes[matchedRoutes.length - 1]; + // If we only match __root__, we ended up not matching any route at all, so + // we fall back to the pathname. + const routeMatch = lastMatch?.routeId !== '__root__' ? lastMatch : undefined; startBrowserTracingPageLoadSpan(client, { - name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname, + name: routeMatch ? routeMatch.routeId : initialWindowLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.solid.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url', - ...routeMatchToParamSpanAttributes(lastMatch), + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeMatch ? 'route' : 'url', + ...routeMatchToParamSpanAttributes(routeMatch), }, }); } @@ -73,21 +76,23 @@ export function tanstackRouterBrowserTracingIntegration( return; } - const onResolvedMatchedRoutes = router.matchRoutes( + const matchedRoutesOnBeforeNavigate = router.matchRoutes( onBeforeNavigateArgs.toLocation.pathname, onBeforeNavigateArgs.toLocation.search, { preload: false, throwOnError: false }, ); - const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onBeforeNavigateLastMatch = matchedRoutesOnBeforeNavigate[matchedRoutesOnBeforeNavigate.length - 1]; + const onBeforeNavigateRouteMatch = + onBeforeNavigateLastMatch?.routeId !== '__root__' ? onBeforeNavigateLastMatch : undefined; const navigationLocation = WINDOW.location; const navigationSpan = startBrowserTracingNavigationSpan(client, { - name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname, + name: onBeforeNavigateRouteMatch ? onBeforeNavigateRouteMatch.routeId : navigationLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solid.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateRouteMatch ? 'route' : 'url', }, }); @@ -95,18 +100,20 @@ export function tanstackRouterBrowserTracingIntegration( const unsubscribeOnResolved = router.subscribe('onResolved', onResolvedArgs => { unsubscribeOnResolved(); if (navigationSpan) { - const onResolvedMatchedRoutes = router.matchRoutes( + const matchedRoutesOnResolved = router.matchRoutes( onResolvedArgs.toLocation.pathname, onResolvedArgs.toLocation.search, { preload: false, throwOnError: false }, ); - const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onResolvedLastMatch = matchedRoutesOnResolved[matchedRoutesOnResolved.length - 1]; + const onResolvedRouteMatch = + onResolvedLastMatch?.routeId !== '__root__' ? onResolvedLastMatch : undefined; - if (onResolvedLastMatch) { - navigationSpan.updateName(onResolvedLastMatch.routeId); + if (onResolvedRouteMatch) { + navigationSpan.updateName(onResolvedRouteMatch.routeId); navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch)); + navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedRouteMatch)); } } }); diff --git a/packages/vue/src/tanstackrouter.ts b/packages/vue/src/tanstackrouter.ts index a0ae76814c0c..d46f2a8c40c6 100644 --- a/packages/vue/src/tanstackrouter.ts +++ b/packages/vue/src/tanstackrouter.ts @@ -43,20 +43,22 @@ export function tanstackRouterBrowserTracingIntegration( if (instrumentPageLoad && initialWindowLocation) { const matchedRoutes = router.matchRoutes( initialWindowLocation.pathname, - router.options.parseSearch(initialWindowLocation.search), { preload: false, throwOnError: false }, ); const lastMatch = matchedRoutes[matchedRoutes.length - 1]; + // If we only match __root__, we ended up not matching any route at all, so + // we fall back to the pathname. + const routeMatch = lastMatch?.routeId !== '__root__' ? lastMatch : undefined; startBrowserTracingPageLoadSpan(client, { - name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname, + name: routeMatch ? routeMatch.routeId : initialWindowLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url', - ...routeMatchToParamSpanAttributes(lastMatch), + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeMatch ? 'route' : 'url', + ...routeMatchToParamSpanAttributes(routeMatch), }, }); } @@ -87,18 +89,20 @@ export function tanstackRouterBrowserTracingIntegration( ); const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onBeforeNavigateRouteMatch = + onBeforeNavigateLastMatch?.routeId !== '__root__' ? onBeforeNavigateLastMatch : undefined; const navigationLocation = WINDOW.location; const navigationSpan = startBrowserTracingNavigationSpan(client, { - name: onBeforeNavigateLastMatch - ? onBeforeNavigateLastMatch.routeId + name: onBeforeNavigateRouteMatch + ? onBeforeNavigateRouteMatch.routeId : // In SSR/non-browser contexts, WINDOW.location may be undefined, so fall back to the router's location // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access navigationLocation?.pathname || onBeforeNavigateArgs.toLocation.pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue.tanstack_router', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateRouteMatch ? 'route' : 'url', }, }); @@ -116,11 +120,13 @@ export function tanstackRouterBrowserTracingIntegration( ); const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + const onResolvedRouteMatch = + onResolvedLastMatch?.routeId !== '__root__' ? onResolvedLastMatch : undefined; - if (onResolvedLastMatch) { - navigationSpan.updateName(onResolvedLastMatch.routeId); + if (onResolvedRouteMatch) { + navigationSpan.updateName(onResolvedRouteMatch.routeId); navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch)); + navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedRouteMatch)); } } });