[0] & ReactRouterOptions,
version: V6CompatibleVersion,
): Integration {
- const integration = browserTracingIntegration({
- ...options,
- instrumentPageLoad: false,
- instrumentNavigation: false,
- });
+ const integration = browserTracingIntegration({ ...options, instrumentPageLoad: false, instrumentNavigation: false });
const {
useEffect,
@@ -532,13 +516,7 @@ function wrapPatchRoutesOnNavigation(
if (activeRootSpan && (spanToJSON(activeRootSpan) as { op?: string }).op === 'navigation') {
updateNavigationSpan(
activeRootSpan,
- {
- pathname: targetPath,
- search: '',
- hash: '',
- state: null,
- key: 'default',
- },
+ { pathname: targetPath, search: '', hash: '', state: null, key: 'default' },
Array.from(allRoutes),
true, // forceUpdate = true since we're loading lazy routes
_matchRoutes,
@@ -559,13 +537,7 @@ function wrapPatchRoutesOnNavigation(
if (pathname) {
updateNavigationSpan(
activeRootSpan,
- {
- pathname,
- search: '',
- hash: '',
- state: null,
- key: 'default',
- },
+ { pathname, search: '', hash: '', state: null, key: 'default' },
Array.from(allRoutes),
false, // forceUpdate = false since this is after lazy routes are loaded
_matchRoutes,
@@ -604,18 +576,20 @@ export function handleNavigation(opts: {
basename,
);
- // Check if this might be a lazy route context
- const isLazyRouteContext = isLikelyLazyRouteContext(allRoutes || routes, location);
-
const activeSpan = getActiveSpan();
const spanJson = activeSpan && spanToJSON(activeSpan);
const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
// Cross usage can result in multiple navigation spans being created without this check
- if (isAlreadyInNavigationSpan && activeSpan && spanJson) {
- handleExistingNavigationSpan(activeSpan, spanJson, name, source, isLazyRouteContext);
- } else {
- createNewNavigationSpan(client, name, source, version, isLazyRouteContext);
+ if (!isAlreadyInNavigationSpan) {
+ startBrowserTracingNavigationSpan(client, {
+ name,
+ attributes: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
+ },
+ });
}
}
}
@@ -726,13 +700,7 @@ export function createV6CompatibleWithSentryReactRouterRouting,
- name: string,
- source: TransactionSource,
- isLikelyLazyRoute: boolean,
-): void {
- // Check if we've already set the name for this span using a custom property
- const hasBeenNamed = (
- activeSpan as {
- __sentry_navigation_name_set__?: boolean;
- }
- )?.__sentry_navigation_name_set__;
-
- if (!hasBeenNamed) {
- // This is the first time we're setting the name for this span
- if (!spanJson.timestamp) {
- activeSpan?.updateName(name);
- }
-
- // For lazy routes, don't mark as named yet so it can be updated later
- if (!isLikelyLazyRoute) {
- addNonEnumerableProperty(
- activeSpan as { __sentry_navigation_name_set__?: boolean },
- '__sentry_navigation_name_set__',
- true,
- );
- }
- }
-
- // Always set the source attribute to keep it consistent with the current route
- activeSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
-}
-
-/**
- * Creates a new navigation span
- */
-export function createNewNavigationSpan(
- client: Client,
- name: string,
- source: TransactionSource,
- version: string,
- isLikelyLazyRoute: boolean,
-): void {
- const newSpan = startBrowserTracingNavigationSpan(client, {
- name,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
- },
- });
-
- // For lazy routes, don't mark as named yet so it can be updated later when the route loads
- if (!isLikelyLazyRoute && newSpan) {
- addNonEnumerableProperty(
- newSpan as { __sentry_navigation_name_set__?: boolean },
- '__sentry_navigation_name_set__',
- true,
- );
- }
-}
diff --git a/packages/react/src/reactrouter-compat-utils/utils.ts b/packages/react/src/reactrouter-compat-utils/utils.ts
index 8f7abb7d548e..c0750c17c57c 100644
--- a/packages/react/src/reactrouter-compat-utils/utils.ts
+++ b/packages/react/src/reactrouter-compat-utils/utils.ts
@@ -14,37 +14,6 @@ export function initializeRouterUtils(matchRoutes: MatchRoutes, stripBasename: b
_stripBasename = stripBasename;
}
-/**
- * Checks if the given routes or location context suggests this might be a lazy route scenario.
- * This helps determine if we should delay marking navigation spans as "named" to allow for updates
- * when lazy routes are loaded.
- */
-export function isLikelyLazyRouteContext(routes: RouteObject[], location: Location): boolean {
- // Check if any route in the current match has lazy properties
- const hasLazyRoute = routes.some(route => {
- return (
- // React Router lazy() route
- route.lazy ||
- // Route with async handlers that might load child routes
- (route.handle &&
- typeof route.handle === 'object' &&
- Object.values(route.handle).some(handler => typeof handler === 'function'))
- );
- });
-
- if (hasLazyRoute) {
- return true;
- }
-
- // Check if current route is unmatched, which might indicate a lazy route that hasn't loaded yet
- const currentMatches = _matchRoutes(routes, location);
- if (!currentMatches || currentMatches.length === 0) {
- return true;
- }
-
- return false;
-}
-
// Helper functions
function pickPath(match: RouteMatch): string {
return trimWildcard(match.route.path || '');
diff --git a/packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx b/packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx
index 840adbf0a816..0eeeeb342287 100644
--- a/packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx
+++ b/packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx
@@ -1,16 +1,13 @@
/**
* @vitest-environment jsdom
*/
-import { startBrowserTracingNavigationSpan } from '@sentry/browser';
import type { Client, Span } from '@sentry/core';
import { addNonEnumerableProperty } from '@sentry/core';
import * as React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
addResolvedRoutesToParent,
- createNewNavigationSpan,
createReactRouterV6CompatibleTracingIntegration,
- handleExistingNavigationSpan,
updateNavigationSpan,
} from '../../src/reactrouter-compat-utils';
import type { Location, RouteObject } from '../../src/types';
@@ -94,43 +91,6 @@ describe('reactrouter-compat-utils/instrumentation', () => {
});
});
- describe('handleExistingNavigationSpan', () => {
- it('should update span name when not already named', () => {
- const spanJson = { op: 'navigation', data: {}, span_id: 'test', start_timestamp: 0, trace_id: 'test' };
-
- handleExistingNavigationSpan(mockSpan, spanJson, 'Test Route', 'route', false);
-
- expect(mockUpdateName).toHaveBeenCalledWith('Test Route');
- expect(mockSetAttribute).toHaveBeenCalledWith('sentry.source', 'route');
- expect(addNonEnumerableProperty).toHaveBeenCalledWith(mockSpan, '__sentry_navigation_name_set__', true);
- });
-
- it('should not mark as named for lazy routes', () => {
- const spanJson = { op: 'navigation', data: {}, span_id: 'test', start_timestamp: 0, trace_id: 'test' };
-
- handleExistingNavigationSpan(mockSpan, spanJson, 'Test Route', 'route', true);
-
- expect(mockUpdateName).toHaveBeenCalledWith('Test Route');
- expect(mockSetAttribute).toHaveBeenCalledWith('sentry.source', 'route');
- expect(addNonEnumerableProperty).not.toHaveBeenCalled();
- });
- });
-
- describe('createNewNavigationSpan', () => {
- it('should create new navigation span with correct attributes', () => {
- createNewNavigationSpan(mockClient, 'Test Route', 'route', '6', false);
-
- expect(startBrowserTracingNavigationSpan).toHaveBeenCalledWith(mockClient, {
- name: 'Test Route',
- attributes: {
- 'sentry.source': 'route',
- 'sentry.op': 'navigation',
- 'sentry.origin': 'auto.navigation.react.reactrouter_v6',
- },
- });
- });
- });
-
describe('addResolvedRoutesToParent', () => {
it('should add new routes to parent with no existing children', () => {
const parentRoute: RouteObject = { path: '/parent', element:
Parent
};
diff --git a/packages/react/test/reactrouter-cross-usage.test.tsx b/packages/react/test/reactrouter-cross-usage.test.tsx
index 76cfe59f3df5..77d8e3d95b2e 100644
--- a/packages/react/test/reactrouter-cross-usage.test.tsx
+++ b/packages/react/test/reactrouter-cross-usage.test.tsx
@@ -36,10 +36,7 @@ import {
const mockStartBrowserTracingPageLoadSpan = vi.fn();
const mockStartBrowserTracingNavigationSpan = vi.fn();
-const mockNavigationSpan = {
- updateName: vi.fn(),
- setAttribute: vi.fn(),
-};
+const mockNavigationSpan = { updateName: vi.fn(), setAttribute: vi.fn() };
const mockRootSpan = {
updateName: vi.fn(),
@@ -126,65 +123,29 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'third-level/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'third-level/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const createSentryMemoryRouter = wrapCreateMemoryRouterV6(createMemoryRouter);
- const router = createSentryMemoryRouter(
- [
- {
- children: [
- {
- path: '/*',
- element: ,
- },
- ],
- },
- ],
- {
- initialEntries: ['/second-level/321/third-level/123'],
- },
- );
+ const router = createSentryMemoryRouter([{ children: [{ path: '/*', element: }] }], {
+ initialEntries: ['/second-level/321/third-level/123'],
+ });
const { container } = render(
@@ -218,42 +179,21 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'third-level/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'third-level/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '*', element: },
]);
const createSentryMemoryRouter = wrapCreateMemoryRouterV6(createMemoryRouter);
@@ -262,20 +202,12 @@ describe('React Router cross usage of wrappers', () => {
[
{
children: [
- {
- path: '/*',
- element: ,
- },
- {
- path: '/navigate',
- element: ,
- },
+ { path: '/*', element: },
+ { path: '/navigate', element: },
],
},
],
- {
- initialEntries: ['/navigate'],
- },
+ { initialEntries: ['/navigate'] },
);
const { container } = render(
@@ -319,46 +251,22 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'third-level/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'third-level/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const SentryRoutes = withSentryReactRouterV6Routing(Routes);
@@ -399,42 +307,21 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'third-level/*',
- element: ,
- },
- {
- path: '/',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'third-level/*', element: },
+ { path: '/', element: },
+ { path: '*', element: },
]);
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '*', element: },
]);
const SentryRoutes = withSentryReactRouterV6Routing(Routes);
@@ -500,21 +387,9 @@ describe('React Router cross usage of wrappers', () => {
);
- const router = createSentryMemoryRouter(
- [
- {
- children: [
- {
- path: '/*',
- element: ,
- },
- ],
- },
- ],
- {
- initialEntries: ['/second-level/321/third-level/123'],
- },
- );
+ const router = createSentryMemoryRouter([{ children: [{ path: '/*', element: }] }], {
+ initialEntries: ['/second-level/321/third-level/123'],
+ });
const { container } = render(
@@ -574,20 +449,12 @@ describe('React Router cross usage of wrappers', () => {
[
{
children: [
- {
- path: '/*',
- element: ,
- },
- {
- path: '/navigate',
- element: ,
- },
+ { path: '/*', element: },
+ { path: '/navigate', element: },
],
},
],
- {
- initialEntries: ['/navigate'],
- },
+ { initialEntries: ['/navigate'] },
);
const { container } = render(
@@ -633,14 +500,8 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () => (
@@ -653,33 +514,15 @@ describe('React Router cross usage of wrappers', () => {
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '*', element: },
]);
const createSentryMemoryRouter = wrapCreateMemoryRouterV6(createMemoryRouter);
- const router = createSentryMemoryRouter(
- [
- {
- children: [
- {
- path: '/*',
- element: ,
- },
- ],
- },
- ],
- {
- initialEntries: ['/second-level/321/third-level/123'],
- },
- );
+ const router = createSentryMemoryRouter([{ children: [{ path: '/*', element: }] }], {
+ initialEntries: ['/second-level/321/third-level/123'],
+ });
const { container } = render(
@@ -714,14 +557,8 @@ describe('React Router cross usage of wrappers', () => {
const ThirdLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: '/',
- element: ,
- },
- {
- path: ':id',
- element: ,
- },
+ { path: '/', element: },
+ { path: ':id', element: },
]);
const SecondLevelRoutes: React.FC = () => (
@@ -734,14 +571,8 @@ describe('React Router cross usage of wrappers', () => {
const TopLevelRoutes: React.FC = () =>
sentryUseRoutes([
- {
- path: 'second-level/:id/*',
- element: ,
- },
- {
- path: '*',
- element: ,
- },
+ { path: 'second-level/:id/*', element: },
+ { path: '*', element: },
]);
const createSentryMemoryRouter = wrapCreateMemoryRouterV6(createMemoryRouter);
@@ -750,20 +581,12 @@ describe('React Router cross usage of wrappers', () => {
[
{
children: [
- {
- path: '/*',
- element: ,
- },
- {
- path: '/navigate',
- element: ,
- },
+ { path: '/*', element: },
+ { path: '/navigate', element: },
],
},
],
- {
- initialEntries: ['/navigate'],
- },
+ { initialEntries: ['/navigate'] },
);
const { container } = render(
@@ -773,10 +596,15 @@ describe('React Router cross usage of wrappers', () => {
);
expect(container.innerHTML).toContain('Details');
-
- // It's called 1 time from the wrapped `MemoryRouter`
expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
- expect(mockNavigationSpan.setAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+ expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
+ name: '/second-level/:id/third-level/:id',
+ attributes: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
+ },
+ });
});
});
});