From acf0082dbabf13fe391ba870c5428fabe9e73024 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 16 Jan 2025 22:14:32 +0000 Subject: [PATCH 1/3] fix(react): Wait for lazy-loaded pages on navigation --- .../.gitignore | 29 +++++++ .../.npmrc | 2 + .../package.json | 40 ++++++++++ .../playwright.config.mjs | 7 ++ .../public/index.html | 24 ++++++ .../src/globals.d.ts | 5 ++ .../src/index.tsx | 71 +++++++++++++++++ .../src/pages/Index.tsx | 23 ++++++ .../src/pages/User.tsx | 8 ++ .../src/react-app-env.d.ts | 1 + .../start-event-proxy.mjs | 6 ++ .../tests/errors.test.ts | 30 +++++++ .../tests/transactions.test.ts | 78 +++++++++++++++++++ .../tsconfig.json | 20 +++++ .../react/src/reactrouterv6-compat-utils.tsx | 19 +++-- 15 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore new file mode 100644 index 000000000000..84634c973eeb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json new file mode 100644 index 000000000000..1e061d9bc267 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-create-browser-router-lazy-routes-test", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/react": "latest || *", + "@types/node": "^18.19.1", + "@types/react": "18.0.0", + "@types/react-dom": "18.0.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "^6.4.1", + "react-scripts": "5.0.1", + "typescript": "~5.0.0" + }, + "scripts": { + "build": "react-scripts build", + "start": "serve -s build", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", + "test:assert": "pnpm test" + }, + "eslintConfig": { + "extends": ["react-app", "react-app/jest"] + }, + "browserslist": { + "production": [">0.2%", "not dead", "not op_mini all"], + "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "serve": "14.0.1" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html new file mode 100644 index 000000000000..39da76522bea --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + React App + + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts new file mode 100644 index 000000000000..ffa61ca49acc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts @@ -0,0 +1,5 @@ +interface Window { + recordedTransactions?: string[]; + capturedExceptionId?: string; + sentryReplayId?: string; +} diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx new file mode 100644 index 000000000000..b89e00b43f51 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx @@ -0,0 +1,71 @@ +import * as Sentry from '@sentry/react'; +import React, { Suspense, lazy } from 'react'; +import ReactDOM from 'react-dom/client'; +import { + RouterProvider, + createBrowserRouter, + createRoutesFromChildren, + matchRoutes, + useLocation, + useNavigationType, +} from 'react-router-dom'; +import Index from './pages/Index'; + +const replay = Sentry.replayIntegration(); + +Sentry.init({ + // environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.REACT_APP_E2E_TEST_DSN, + integrations: [ + Sentry.reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + replay, + ], + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + release: 'e2e-test', + + tunnel: 'http://localhost:3031', + + // Always capture replays, so we can test this properly + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + debug: !!process.env.DEBUG, +}); + +const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); + +const User = lazy(() => import('./pages/User')); + +const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + element: ( + Loading...}> + + + ), + path: '/user/:id', + }, + ], + { + // We're testing whether this option is avoided in the integration + // We expect this to be ignored + initialEntries: ['/user/1'], + }, +); + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); + +root.render(); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx new file mode 100644 index 000000000000..d6b71a1d1279 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx @@ -0,0 +1,23 @@ +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +const Index = () => { + return ( + <> + { + throw new Error('I am an error!'); + }} + /> + + navigate + + + ); +}; + +export default Index; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx new file mode 100644 index 000000000000..62f0c2d17533 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx @@ -0,0 +1,8 @@ +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; + +const User = () => { + return

I am a blank page :)

; +}; + +export default User; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts new file mode 100644 index 000000000000..6431bc5fc6b2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs new file mode 100644 index 000000000000..be93e129284f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-create-browser-router', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts new file mode 100644 index 000000000000..4a11f07410ab --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('Captures exception correctly', async ({ page }) => { + const errorEventPromise = waitForError('react-create-browser-router', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.request).toEqual({ + headers: expect.any(Object), + url: 'http://localhost:3030/', + }); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts new file mode 100644 index 000000000000..5ecd098daf94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Captures a pageload transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(transactionEvent.contexts?.trace).toEqual( + expect.objectContaining({ + data: expect.objectContaining({ + deviceMemory: expect.any(String), + effectiveConnectionType: expect.any(String), + hardwareConcurrency: expect.any(String), + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'pageload', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.pageload.react.reactrouter_v6', + }), + ); +}); + +test('Captures a navigation transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'navigation'; + }); + + await page.goto('/'); + const linkElement = page.locator('id=navigation'); + await linkElement.click(); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'navigation', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.navigation.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/user/:id', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(transactionEvent.spans).toEqual([]); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json new file mode 100644 index 000000000000..4cc95dc2689a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src", "tests"] +} diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 9ca96a03b0de..2dc4bf29bd20 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -94,14 +94,19 @@ export function createV6CompatibleWrapCreateBrowserRouter< } router.subscribe((state: RouterState) => { - const location = state.location; if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - handleNavigation({ - location, - routes, - navigationType: state.historyAction, - version, - basename, + // Use requestAnimationFrame to wait for Suspense boundaries to settle + requestAnimationFrame(() => { + // Small delay to allow for Suspense transitions + setTimeout(() => { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + }); + }, 100); }); } }); From 1e023fb3b8d16b3df3e9e4255cfcafc91c6ccebb Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 17 Jan 2025 12:49:07 +0000 Subject: [PATCH 2/3] Conditionally wait for the render depending on the router state. --- .../tests/transactions.test.ts | 7 ++++++- .../react/src/reactrouterv6-compat-utils.tsx | 19 +++++++++++++------ packages/react/src/types.ts | 11 ++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts index 5ecd098daf94..5843d94abac8 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts @@ -74,5 +74,10 @@ test('Captures a navigation transaction', async ({ page }) => { }), ); - expect(transactionEvent.spans).toEqual([]); + expect(transactionEvent.spans).toEqual([ + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]); }); diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 2dc4bf29bd20..07249caa33db 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -95,10 +95,9 @@ export function createV6CompatibleWrapCreateBrowserRouter< router.subscribe((state: RouterState) => { if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - // Use requestAnimationFrame to wait for Suspense boundaries to settle - requestAnimationFrame(() => { - // Small delay to allow for Suspense transitions - setTimeout(() => { + // Wait for the next render if loading an unsettled route + if (state.navigation.state !== 'idle') { + requestAnimationFrame(() => { handleNavigation({ location: state.location, routes, @@ -106,8 +105,16 @@ export function createV6CompatibleWrapCreateBrowserRouter< version, basename, }); - }, 100); - }); + }); + } else { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + }); + } } }); diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 1a40ec4fce91..ce6f1b50e0c8 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -182,10 +182,14 @@ export interface RouterInit { hydrationData?: HydrationState; } +export type NavigationState = { + state: 'idle' | 'loading' | 'submitting'; +} + export type NavigationStates = { - Idle: any; - Loading: any; - Submitting: any; + Idle: NavigationState; + Loading: NavigationState; + Submitting: NavigationState; }; export type Navigation = NavigationStates[keyof NavigationStates]; @@ -202,6 +206,7 @@ export declare enum HistoryAction { export interface RouterState { historyAction: Action | HistoryAction | any; location: Location; + navigation: Navigation; } export interface Router { state: TState; From 2a8888d981ef7d582ae50b34730b5dda77b98218 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 20 Jan 2025 16:42:10 +0000 Subject: [PATCH 3/3] Support lazy-loaded descendant routes. --- .../.gitignore | 29 ----- .../.npmrc | 2 - .../package.json | 40 ------- .../playwright.config.mjs | 7 -- .../public/index.html | 24 ----- .../src/globals.d.ts | 5 - .../src/index.tsx | 71 ------------ .../src/pages/Index.tsx | 23 ---- .../src/pages/User.tsx | 8 -- .../src/react-app-env.d.ts | 1 - .../start-event-proxy.mjs | 6 -- .../tests/errors.test.ts | 30 ------ .../tests/transactions.test.ts | 83 -------------- .../tsconfig.json | 20 ---- .../react-create-browser-router/src/index.tsx | 11 +- .../src/pages/Index.tsx | 3 + .../src/pages/LazyLoadedInnerRoute.tsx | 14 +++ .../src/pages/LazyLoadedUser.tsx | 23 ++++ .../tests/transactions.test.ts | 102 ++++++++++++++++++ .../react/src/reactrouterv6-compat-utils.tsx | 34 ++++-- packages/react/src/types.ts | 2 +- 21 files changed, 178 insertions(+), 360 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore deleted file mode 100644 index 84634c973eeb..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -/test-results/ -/playwright-report/ -/playwright/.cache/ - -!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc deleted file mode 100644 index 070f80f05092..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -@sentry:registry=http://127.0.0.1:4873 -@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json deleted file mode 100644 index 1e061d9bc267..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "react-create-browser-router-lazy-routes-test", - "version": "0.1.0", - "private": true, - "dependencies": { - "@sentry/react": "latest || *", - "@types/node": "^18.19.1", - "@types/react": "18.0.0", - "@types/react-dom": "18.0.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-router-dom": "^6.4.1", - "react-scripts": "5.0.1", - "typescript": "~5.0.0" - }, - "scripts": { - "build": "react-scripts build", - "start": "serve -s build", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", - "test:assert": "pnpm test" - }, - "eslintConfig": { - "extends": ["react-app", "react-app/jest"] - }, - "browserslist": { - "production": [">0.2%", "not dead", "not op_mini all"], - "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] - }, - "devDependencies": { - "@playwright/test": "^1.44.1", - "@sentry-internal/test-utils": "link:../../../test-utils", - "serve": "14.0.1" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs deleted file mode 100644 index 31f2b913b58b..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/playwright.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: `pnpm start`, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html deleted file mode 100644 index 39da76522bea..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/public/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - React App - - - -
- - - diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts deleted file mode 100644 index ffa61ca49acc..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/globals.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface Window { - recordedTransactions?: string[]; - capturedExceptionId?: string; - sentryReplayId?: string; -} diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx deleted file mode 100644 index b89e00b43f51..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as Sentry from '@sentry/react'; -import React, { Suspense, lazy } from 'react'; -import ReactDOM from 'react-dom/client'; -import { - RouterProvider, - createBrowserRouter, - createRoutesFromChildren, - matchRoutes, - useLocation, - useNavigationType, -} from 'react-router-dom'; -import Index from './pages/Index'; - -const replay = Sentry.replayIntegration(); - -Sentry.init({ - // environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.REACT_APP_E2E_TEST_DSN, - integrations: [ - Sentry.reactRouterV6BrowserTracingIntegration({ - useEffect: React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - }), - replay, - ], - // We recommend adjusting this value in production, or using tracesSampler - // for finer control - tracesSampleRate: 1.0, - release: 'e2e-test', - - tunnel: 'http://localhost:3031', - - // Always capture replays, so we can test this properly - replaysSessionSampleRate: 1.0, - replaysOnErrorSampleRate: 0.0, - - debug: !!process.env.DEBUG, -}); - -const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); - -const User = lazy(() => import('./pages/User')); - -const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - element: ( - Loading...}> - - - ), - path: '/user/:id', - }, - ], - { - // We're testing whether this option is avoided in the integration - // We expect this to be ignored - initialEntries: ['/user/1'], - }, -); - -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); - -root.render(); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx deleted file mode 100644 index d6b71a1d1279..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/Index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX -import * as React from 'react'; -import { Link } from 'react-router-dom'; - -const Index = () => { - return ( - <> - { - throw new Error('I am an error!'); - }} - /> - - navigate - - - ); -}; - -export default Index; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx deleted file mode 100644 index 62f0c2d17533..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/pages/User.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX -import * as React from 'react'; - -const User = () => { - return

I am a blank page :)

; -}; - -export default User; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc6b2..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs deleted file mode 100644 index be93e129284f..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'react-create-browser-router', -}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts deleted file mode 100644 index 4a11f07410ab..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/errors.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -test('Captures exception correctly', async ({ page }) => { - const errorEventPromise = waitForError('react-create-browser-router', event => { - return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; - }); - - await page.goto('/'); - - const exceptionButton = page.locator('id=exception-button'); - await exceptionButton.click(); - - const errorEvent = await errorEventPromise; - - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); - - expect(errorEvent.request).toEqual({ - headers: expect.any(Object), - url: 'http://localhost:3030/', - }); - - expect(errorEvent.transaction).toEqual('/'); - - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts deleted file mode 100644 index 5843d94abac8..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tests/transactions.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Captures a pageload transaction', async ({ page }) => { - const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { - return event.contexts?.trace?.op === 'pageload'; - }); - - await page.goto('/'); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - transaction: '/', - type: 'transaction', - transaction_info: { - source: 'route', - }, - }), - ); - - expect(transactionEvent.contexts?.trace).toEqual( - expect.objectContaining({ - data: expect.objectContaining({ - deviceMemory: expect.any(String), - effectiveConnectionType: expect.any(String), - hardwareConcurrency: expect.any(String), - 'sentry.idle_span_finish_reason': 'idleTimeout', - 'sentry.op': 'pageload', - 'sentry.origin': 'auto.pageload.react.reactrouter_v6', - 'sentry.sample_rate': 1, - 'sentry.source': 'route', - }), - op: 'pageload', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'auto.pageload.react.reactrouter_v6', - }), - ); -}); - -test('Captures a navigation transaction', async ({ page }) => { - const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { - return event.contexts?.trace?.op === 'navigation'; - }); - - await page.goto('/'); - const linkElement = page.locator('id=navigation'); - await linkElement.click(); - - const transactionEvent = await transactionEventPromise; - expect(transactionEvent.contexts?.trace).toEqual({ - data: expect.objectContaining({ - 'sentry.idle_span_finish_reason': 'idleTimeout', - 'sentry.op': 'navigation', - 'sentry.origin': 'auto.navigation.react.reactrouter_v6', - 'sentry.sample_rate': 1, - 'sentry.source': 'route', - }), - op: 'navigation', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'auto.navigation.react.reactrouter_v6', - }); - - expect(transactionEvent).toEqual( - expect.objectContaining({ - transaction: '/user/:id', - type: 'transaction', - transaction_info: { - source: 'route', - }, - }), - ); - - expect(transactionEvent.spans).toEqual([ - expect.objectContaining({ - op: 'resource.script', - origin: 'auto.resource.browser.metrics', - }), - ]); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json deleted file mode 100644 index 4cc95dc2689a..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router-lazy-routes/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" - }, - "include": ["src", "tests"] -} diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx index 88f8cfa502ec..c7ad16eebcf7 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/react'; -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider, @@ -42,6 +42,7 @@ Sentry.init({ }); const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); +const LazyLoadedUser = lazy(() => import('./pages/LazyLoadedUser')); const router = sentryCreateBrowserRouter( [ @@ -49,6 +50,14 @@ const router = sentryCreateBrowserRouter( path: '/', element: , }, + { + path: '/lazy-loaded-user/*', + element: ( + Loading...}> + + + ), + }, { path: '/user/:id', element: , diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx index d6b71a1d1279..12bfb12ec3a9 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx @@ -16,6 +16,9 @@ const Index = () => { navigate + + lazy navigate + ); }; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx new file mode 100644 index 000000000000..1410df69124b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/react'; +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); + +const InnerRoute = () => ( + + I am a lazy loaded user

} /> +
+); + +export default InnerRoute; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx new file mode 100644 index 000000000000..636f99d9c8cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx @@ -0,0 +1,23 @@ +import * as Sentry from '@sentry/react'; +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); +const InnerRoute = React.lazy(() => import('./LazyLoadedInnerRoute')); + +const LazyLoadedUser = () => { + return ( + + Loading...

}> + + + } + /> +
+ ); +}; + +export default LazyLoadedUser; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts index 5ecd098daf94..c35d731915d6 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts @@ -76,3 +76,105 @@ test('Captures a navigation transaction', async ({ page }) => { expect(transactionEvent.spans).toEqual([]); }); + +test('Captures a lazy pageload transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/lazy-loaded-user/5/foo'); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'pageload', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.pageload.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); + +test('Captures a lazy navigation transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'navigation'; + }); + + await page.goto('/'); + const linkElement = page.locator('id=lazy-navigation'); + await linkElement.click(); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'navigation', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.navigation.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 07249caa33db..db65c32cee99 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -61,6 +61,9 @@ export interface ReactRouterOptions { type V6CompatibleVersion = '6' | '7'; +// Keeping as a global variable for cross-usage in multiple functions +const allRoutes = new Set(); + /** * Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions. */ @@ -81,6 +84,10 @@ export function createV6CompatibleWrapCreateBrowserRouter< } return function (routes: RouteObject[], opts?: Record & { basename?: string }): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -90,7 +97,14 @@ export function createV6CompatibleWrapCreateBrowserRouter< // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename); + updatePageloadTransaction( + activeRootSpan, + router.state.location, + routes, + undefined, + basename, + Array.from(allRoutes), + ); } router.subscribe((state: RouterState) => { @@ -104,6 +118,7 @@ export function createV6CompatibleWrapCreateBrowserRouter< navigationType: state.historyAction, version, basename, + allRoutes: Array.from(allRoutes), }); }); } else { @@ -113,6 +128,7 @@ export function createV6CompatibleWrapCreateBrowserRouter< navigationType: state.historyAction, version, basename, + allRoutes: Array.from(allRoutes), }); } } @@ -149,6 +165,10 @@ export function createV6CompatibleWrapCreateMemoryRouter< initialIndex?: number; }, ): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -174,7 +194,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< : router.state.location; if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename); + updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes)); } router.subscribe((state: RouterState) => { @@ -186,6 +206,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< navigationType: state.historyAction, version, basename, + allRoutes: Array.from(allRoutes), }); } }); @@ -260,8 +281,6 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio return origUseRoutes; } - const allRoutes: Set = new Set(); - const SentryRoutes: React.FC<{ children?: React.ReactNode; routes: RouteObject[]; @@ -331,7 +350,6 @@ export function handleNavigation(opts: { allRoutes?: RouteObject[]; }): void { const { location, routes, navigationType, version, matches, basename, allRoutes } = opts; - const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); const client = getClient(); @@ -565,7 +583,7 @@ function updatePageloadTransaction( ): void { const branches = Array.isArray(matches) ? matches - : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]); + : (_matchRoutes(allRoutes || routes, location, basename) as unknown as RouteMatch[]); if (branches) { let name, @@ -581,7 +599,7 @@ function updatePageloadTransaction( [name, source] = getNormalizedName(routes, location, branches, basename); } - getCurrentScope().setTransactionName(name); + getCurrentScope().setTransactionName(name || '/'); if (activeRootSpan) { activeRootSpan.updateName(name); @@ -604,8 +622,6 @@ export function createV6CompatibleWithSentryReactRouterRouting

= new Set(); - const SentryRoutes: React.FC

= (props: P) => { const isMountRenderPass = React.useRef(true); diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index ce6f1b50e0c8..b29a2dbd1cad 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -184,7 +184,7 @@ export interface RouterInit { export type NavigationState = { state: 'idle' | 'loading' | 'submitting'; -} +}; export type NavigationStates = { Idle: NavigationState;