diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ab67237668e..b737ba882ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **test(tanstackstart-react): Move initialization to client entry point ([#21161](https://github.com/getsentry/sentry-javascript/pull/21161))** + + Change the recommended setup for the SDK to do `Sentry.init()` in the client entry file to capture telemetry that is emitted ahead of page hydration. + ## 10.54.0 ### Important Changes diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/client.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/client.tsx new file mode 100644 index 000000000000..5cc8b2a9c7f7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/client.tsx @@ -0,0 +1,27 @@ +import * as Sentry from '@sentry/browser'; +import { StartClient } from '@tanstack/react-start/client'; +import { StrictMode, startTransition } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +Sentry.init({ + environment: 'qa', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + release: 'e2e-test', + tunnel: 'http://localhost:3031/', +}); + +console.log('early-breadcrumb-from-client-entry'); + +if (window.location.pathname === '/crash-before-hydration') { + throw new Error('Client Entry Crash'); +} + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/router.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/router.tsx index c18a1a0b8167..386bebc8c363 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/router.tsx +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/router.tsx @@ -9,14 +9,7 @@ export const getRouter = () => { }); if (!router.isServer) { - Sentry.init({ - environment: 'qa', - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], - tracesSampleRate: 1.0, - release: 'e2e-test', - tunnel: 'http://localhost:3031/', - }); + Sentry.addIntegration(Sentry.browserTracingIntegration()); } return router; diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/routes/crash-before-hydration.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/routes/crash-before-hydration.tsx new file mode 100644 index 000000000000..772a045a7e33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/src/routes/crash-before-hydration.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/crash-before-hydration')({ + component: CrashPage, +}); + +function CrashPage() { + return ( +
+

This page crashes in client.tsx before hydration

+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/tests/early-init.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/tests/early-init.test.ts new file mode 100644 index 000000000000..2b58f0bf594e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/tests/early-init.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('should capture errors thrown in client entry before hydration', async ({ page }) => { + const errorEventPromise = waitForError('tanstackstart-react-cloudflare', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Client Entry Crash'; + }); + + await page.goto('/crash-before-hydration'); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values?.[0]).toMatchObject({ + type: 'Error', + value: 'Client Entry Crash', + }); + + const consoleBreadcrumbs = errorEvent.breadcrumbs?.filter( + b => b.category === 'console' && b.message?.includes('early-breadcrumb-from-client-entry'), + ); + + expect(consoleBreadcrumbs?.length).toBeGreaterThanOrEqual(1); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/client.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/client.tsx new file mode 100644 index 000000000000..318bfbdf2285 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/client.tsx @@ -0,0 +1,29 @@ +import * as Sentry from '@sentry/tanstackstart-react'; +import { StartClient } from '@tanstack/react-start/client'; +import { StrictMode, startTransition } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: __APP_DSN__, + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + release: 'e2e-test', + tunnel: __APP_TUNNEL__, +}); + +console.log('early-breadcrumb-from-client-entry'); + +if (window.location.pathname === '/crash-before-hydration') { + throw new Error('Client Entry Crash'); +} + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/router.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/router.tsx index 9a39b6f35c42..670b22bc688d 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/router.tsx +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/router.tsx @@ -9,16 +9,7 @@ export const getRouter = () => { }); if (!router.isServer) { - Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: __APP_DSN__, - integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], - // We recommend adjusting this value in production, or using tracesSampler - // for finer control - tracesSampleRate: 1.0, - release: 'e2e-test', - tunnel: __APP_TUNNEL__, - }); + Sentry.addIntegration(Sentry.tanstackRouterBrowserTracingIntegration(router)); } return router; diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/crash-before-hydration.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/crash-before-hydration.tsx new file mode 100644 index 000000000000..772a045a7e33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/crash-before-hydration.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/crash-before-hydration')({ + component: CrashPage, +}); + +function CrashPage() { + return ( +
+

This page crashes in client.tsx before hydration

+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/early-init.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/early-init.test.ts new file mode 100644 index 000000000000..ca2c7e44ee14 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/early-init.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +const usesManagedTunnelRoute = + (process.env.E2E_TEST_TUNNEL_ROUTE_MODE ?? 'off') !== 'off' || process.env.E2E_TEST_CUSTOM_TUNNEL_ROUTE === '1'; + +test.skip(usesManagedTunnelRoute, 'Default e2e suites run only in the proxy variant'); + +test('should capture errors thrown in client entry before hydration', async ({ page }) => { + const errorEventPromise = waitForError('tanstackstart-react', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Client Entry Crash'; + }); + + await page.goto('/crash-before-hydration'); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values?.[0]).toMatchObject({ + type: 'Error', + value: 'Client Entry Crash', + }); + + const consoleBreadcrumbs = errorEvent.breadcrumbs?.filter( + b => b.category === 'console' && b.message?.includes('early-breadcrumb-from-client-entry'), + ); + + expect(consoleBreadcrumbs?.length).toBeGreaterThanOrEqual(1); +});