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);
+});