diff --git a/MIGRATION.md b/MIGRATION.md
index 05c9062c4ce4..bc2a88363b85 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -402,7 +402,10 @@ Sentry.init({
### `@sentry/react-router`
-- The React Router server request wrappers were removed.
+- The deprecated server wrappers `wrapServerLoader` and `wrapServerAction` were removed. Loaders and
+ actions are instrumented automatically via the instrumentation API - export
+ `instrumentations = [Sentry.createSentryServerInstrumentation()]` from your `entry.server.tsx`
+ instead of wrapping them individually.
### `@sentry/profiling-node`
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore
deleted file mode 100644
index ebb991370034..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore
+++ /dev/null
@@ -1,32 +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
-
-# react router
-.react-router
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css
deleted file mode 100644
index b31c3a9d0ddf..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css
+++ /dev/null
@@ -1,6 +0,0 @@
-html,
-body {
- @media (prefers-color-scheme: dark) {
- color-scheme: dark;
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx
deleted file mode 100644
index 005268b40ad0..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as Sentry from '@sentry/react-router';
-import { StrictMode, startTransition } from 'react';
-import { hydrateRoot } from 'react-dom/client';
-import { HydratedRouter } from 'react-router/dom';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- // todo: get this from env
- dsn: 'https://username@domain/123',
- tunnel: `http://localhost:3031/`, // proxy server
- integrations: [Sentry.reactRouterTracingIntegration()],
- tracesSampleRate: 1.0,
- tracePropagationTargets: [/^\//],
-});
-
-startTransition(() => {
- hydrateRoot(
- document,
-
-
- ,
- );
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx
deleted file mode 100644
index 738cd1515a4d..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { createReadableStreamFromReadable } from '@react-router/node';
-import * as Sentry from '@sentry/react-router';
-import { renderToPipeableStream } from 'react-dom/server';
-import { ServerRouter } from 'react-router';
-import { type HandleErrorFunction } from 'react-router';
-
-const ABORT_DELAY = 5_000;
-
-const handleRequest = Sentry.createSentryHandleRequest({
- streamTimeout: ABORT_DELAY,
- ServerRouter,
- renderToPipeableStream,
- createReadableStreamFromReadable,
-});
-
-export default handleRequest;
-
-export const handleError: HandleErrorFunction = Sentry.createSentryHandleError({ logErrors: true });
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx
deleted file mode 100644
index bc1b8f1236c0..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from 'react-router';
-import type { Route } from './+types/root';
-import stylesheet from './app.css?url';
-
-export const links: Route.LinksFunction = () => [
- { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
- {
- rel: 'preconnect',
- href: 'https://fonts.gstatic.com',
- crossOrigin: 'anonymous',
- },
- {
- rel: 'stylesheet',
- href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
- },
- { rel: 'stylesheet', href: stylesheet },
-];
-
-export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
- );
-}
-
-export default function App() {
- return ;
-}
-
-export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- let message = 'Oops!';
- let details = 'An unexpected error occurred.';
- let stack: string | undefined;
-
- if (isRouteErrorResponse(error)) {
- message = error.status === 404 ? '404' : 'Error';
- details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details;
- } else if (error && error instanceof Error) {
- if (import.meta.env.DEV) {
- details = error.message;
- stack = error.stack;
- }
- }
-
- return (
-
- {message}
- {details}
- {stack && (
-
- {stack}
-
- )}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts
deleted file mode 100644
index b412893def52..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { type RouteConfig, index, prefix, route } from '@react-router/dev/routes';
-
-export default [
- index('routes/home.tsx'),
- ...prefix('errors', [
- route('client', 'routes/errors/client.tsx'),
- route('client/:client-param', 'routes/errors/client-param.tsx'),
- route('client-loader', 'routes/errors/client-loader.tsx'),
- route('server-loader', 'routes/errors/server-loader.tsx'),
- route('client-action', 'routes/errors/client-action.tsx'),
- route('server-action', 'routes/errors/server-action.tsx'),
- ]),
- ...prefix('performance', [
- index('routes/performance/index.tsx'),
- route('ssr', 'routes/performance/ssr.tsx'),
- route('with/:param', 'routes/performance/dynamic-param.tsx'),
- route('static', 'routes/performance/static.tsx'),
- route('server-loader', 'routes/performance/server-loader.tsx'),
- route('server-action', 'routes/performance/server-action.tsx'),
- ]),
-] satisfies RouteConfig;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx
deleted file mode 100644
index d3b2d08eef2e..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Form } from 'react-router';
-
-export function clientAction() {
- throw new Error('Madonna mia! Che casino nella Client Action!');
-}
-
-export default function ClientActionErrorPage() {
- return (
-
-
Client Error Action Page
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx
deleted file mode 100644
index 72d9e62a99dc..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Route } from './+types/server-loader';
-
-export function clientLoader() {
- throw new Error('¡Madre mía del client loader!');
- return { data: 'sad' };
-}
-
-export default function ClientLoaderErrorPage({ loaderData }: Route.ComponentProps) {
- const { data } = loaderData;
- return (
-
-
Client Loader Error Page
-
{data}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx
deleted file mode 100644
index a2e423391f03..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Route } from './+types/client-param';
-
-export default function ClientErrorParamPage({ params }: Route.ComponentProps) {
- return (
-
-
Client Error Param Page
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx
deleted file mode 100644
index 190074a5ef09..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function ClientErrorPage() {
- return (
-
-
Client Error Page
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx
deleted file mode 100644
index 863c320f3557..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Form } from 'react-router';
-
-export function action() {
- throw new Error('Madonna mia! Che casino nella Server Action!');
-}
-
-export default function ServerActionErrorPage() {
- return (
-
-
Server Error Action Page
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx
deleted file mode 100644
index cb777686d540..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Route } from './+types/server-loader';
-
-export function loader() {
- throw new Error('¡Madre mía del server!');
- return { data: 'sad' };
-}
-
-export default function ServerLoaderErrorPage({ loaderData }: Route.ComponentProps) {
- const { data } = loaderData;
- return (
-
-
Server Error Page
-
{data}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx
deleted file mode 100644
index 4498e7a0d017..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { Route } from './+types/home';
-
-export function meta({}: Route.MetaArgs) {
- return [{ title: 'New React Router App' }, { name: 'description', content: 'Welcome to React Router!' }];
-}
-
-export default function Home() {
- return home
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx
deleted file mode 100644
index 1ac02775f2ff..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Route } from './+types/dynamic-param';
-
-export async function loader() {
- await new Promise(resolve => setTimeout(resolve, 500));
- return { data: 'burritos' };
-}
-
-export default function DynamicParamPage({ params }: Route.ComponentProps) {
- const { param } = params;
-
- return (
-
-
Dynamic Parameter Page
-
The parameter value is: {param}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx
deleted file mode 100644
index e5383306625a..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Link } from 'react-router';
-
-export default function PerformancePage() {
- return (
-
-
Performance Page
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx
deleted file mode 100644
index f149c5466b5a..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Form } from 'react-router';
-import type { Route } from './+types/server-action';
-import * as Sentry from '@sentry/react-router';
-
-export const action = Sentry.wrapServerAction({}, async ({ request }: Route.ActionArgs) => {
- let formData = await request.formData();
- let name = formData.get('name');
- await new Promise(resolve => setTimeout(resolve, 1000));
- return {
- greeting: `Hola ${name}`,
- };
-});
-
-export default function Project({ actionData }: Route.ComponentProps) {
- return (
-
-
Server action page
-
- {actionData ?
{actionData.greeting}
: null}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx
deleted file mode 100644
index da688d4dfe3e..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Route } from './+types/server-loader';
-import * as Sentry from '@sentry/react-router';
-
-export const loader = Sentry.wrapServerLoader({}, async ({}: Route.LoaderArgs) => {
- await new Promise(resolve => setTimeout(resolve, 500));
- return { data: 'burritos' };
-});
-
-export default function ServerLoaderPage({ loaderData }: Route.ComponentProps) {
- const { data } = loaderData;
- return (
-
-
Server Loader Page
-
{data}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx
deleted file mode 100644
index 253e964ff15d..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function SsrPage() {
- return (
-
-
SSR Page
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx
deleted file mode 100644
index 3dea24381fdc..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function StaticPage() {
- return Static Page
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs
deleted file mode 100644
index c16240141b6d..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as Sentry from '@sentry/react-router';
-
-Sentry.init({
- dsn: 'https://username@domain/123',
- environment: 'qa', // dynamic sampling bias to keep transactions
- tracesSampleRate: 1.0,
- tunnel: `http://localhost:3031/`, // proxy server
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json
deleted file mode 100644
index 20fdccf46f4c..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "name": "react-router-7-framework-custom",
- "version": "0.1.0",
- "type": "module",
- "private": true,
- "dependencies": {
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-router": "^7.13.0",
- "@react-router/node": "^7.13.0",
- "@react-router/serve": "^7.13.0",
- "@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz",
- "isbot": "^5.1.17"
- },
- "devDependencies": {
- "@types/react": "18.3.1",
- "@types/react-dom": "18.3.1",
- "@types/node": "^20",
- "@react-router/dev": "^7.13.0",
- "@playwright/test": "~1.56.0",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "typescript": "^5.6.3",
- "vite": "^5.4.11"
- },
- "scripts": {
- "build": "react-router build",
- "dev": "NODE_OPTIONS='--import ./instrument.mjs' react-router dev",
- "start": "NODE_OPTIONS='--import ./instrument.mjs' react-router-serve ./build/server/index.js",
- "proxy": "node start-event-proxy.mjs",
- "typecheck": "react-router typegen && tsc",
- "clean": "npx rimraf node_modules pnpm-lock.yaml",
- "test:build": "pnpm install && pnpm build",
- "test:assert": "pnpm test:ts && pnpm test:playwright",
- "test:ts": "pnpm typecheck",
- "test:playwright": "playwright 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"
- ]
- },
- "volta": {
- "extends": "../../package.json"
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs
deleted file mode 100644
index 3ed5721107a7..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs
+++ /dev/null
@@ -1,8 +0,0 @@
-import { getPlaywrightConfig } from '@sentry-internal/test-utils';
-
-const config = getPlaywrightConfig({
- startCommand: `PORT=3030 pnpm start`,
- port: 3030,
-});
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico
deleted file mode 100644
index 5dbdfcddcb14..000000000000
Binary files a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico and /dev/null differ
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts
deleted file mode 100644
index bb1f96469dd2..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Config } from '@react-router/dev/config';
-
-export default {
- ssr: true,
- prerender: ['/performance/static'],
-} satisfies Config;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs
deleted file mode 100644
index fb8dabc7fcfa..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'react-router-7-framework-custom',
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts
deleted file mode 100644
index 91653303b335..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const APP_NAME = 'react-router-7-framework-custom';
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts
deleted file mode 100644
index c1a7de46f1b6..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('client-side errors', () => {
- const errorMessage = '¡Madre mía!';
- test('captures error thrown on click', async ({ page }) => {
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto(`/errors/client`);
- await page.locator('#throw-on-click').click();
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: errorMessage,
- mechanism: {
- handled: false,
- },
- },
- ],
- },
- transaction: '/errors/client',
- request: {
- url: expect.stringContaining('errors/client'),
- headers: expect.any(Object),
- },
- level: 'error',
- platform: 'javascript',
- environment: 'qa',
- sdk: {
- integrations: expect.any(Array),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- },
- tags: { runtime: 'browser' },
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- },
- },
- breadcrumbs: [
- {
- category: 'ui.click',
- message: 'body > div > button#throw-on-click',
- },
- ],
- });
- });
-
- test('captures error thrown on click from a parameterized route', async ({ page }) => {
- const errorMessage = '¡Madre mía de churros!';
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto('/errors/client/churros');
- await page.locator('#throw-on-click').click();
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: '¡Madre mía de churros!',
- mechanism: {
- handled: false,
- },
- },
- ],
- },
- // todo: should be '/errors/client/:client-param'
- transaction: '/errors/client/churros',
- });
- });
-
- test('captures error thrown in a clientLoader', async ({ page }) => {
- const errorMessage = '¡Madre mía del client loader!';
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto('/errors/client-loader');
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: errorMessage,
- mechanism: {
- handled: false,
- type: 'auto.function.react_router.on_error',
- },
- },
- ],
- },
- transaction: '/errors/client-loader',
- });
- });
-
- test('captures error thrown in a clientAction', async ({ page }) => {
- const errorMessage = 'Madonna mia! Che casino nella Client Action!';
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto('/errors/client-action');
- await page.locator('#submit').click();
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: errorMessage,
- mechanism: {
- handled: false,
- type: 'auto.function.react_router.on_error',
- },
- },
- ],
- },
- transaction: '/errors/client-action',
- });
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts
deleted file mode 100644
index 2759bfecb67e..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('server-side errors', () => {
- test('captures error thrown in server loader', async ({ page }) => {
- const errorMessage = '¡Madre mía del server!';
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto(`/errors/server-loader`);
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: errorMessage,
- mechanism: {
- handled: false,
- type: 'react-router',
- },
- },
- ],
- },
- // todo: should be 'GET /errors/server-loader'
- transaction: 'GET *',
- request: {
- url: expect.stringContaining('errors/server-loader'),
- headers: expect.any(Object),
- },
- level: 'error',
- platform: 'node',
- environment: 'qa',
- sdk: {
- integrations: expect.any(Array),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- },
- tags: { runtime: 'node' },
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- },
- },
- });
- });
-
- test('captures error thrown in server action', async ({ page }) => {
- const errorMessage = 'Madonna mia! Che casino nella Server Action!';
- const errorPromise = waitForError(APP_NAME, async errorEvent => {
- return errorEvent?.exception?.values?.[0]?.value === errorMessage;
- });
-
- await page.goto(`/errors/server-action`);
- await page.locator('#submit').click();
-
- const error = await errorPromise;
-
- expect(error).toMatchObject({
- exception: {
- values: [
- {
- type: 'Error',
- value: errorMessage,
- mechanism: {
- handled: false,
- type: 'react-router',
- },
- },
- ],
- },
- // todo: should be 'POST /errors/server-action'
- transaction: 'POST *',
- request: {
- url: expect.stringContaining('errors/server-action'),
- headers: expect.any(Object),
- },
- level: 'error',
- platform: 'node',
- environment: 'qa',
- sdk: {
- integrations: expect.any(Array),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- },
- tags: { runtime: 'node' },
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- },
- },
- });
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts
deleted file mode 100644
index 3432b95ddae3..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('client - navigation performance', () => {
- test('should create navigation transaction', async ({ page }) => {
- const navigationPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return (
- transactionEvent.transaction === '/performance/ssr' && transactionEvent.contexts?.trace?.op === 'navigation'
- );
- });
-
- const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
- });
-
- await page.goto(`/performance`); // pageload
- await pageloadTxPromise;
- await page.getByRole('link', { name: 'SSR Page' }).click(); // navigation
-
- const transaction = await navigationPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.navigation.react_router',
- 'sentry.op': 'navigation',
- 'sentry.source': 'route',
- 'url.template': '/performance/ssr',
- 'url.path': '/performance/ssr',
- 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/ssr$/),
- },
- op: 'navigation',
- origin: 'auto.navigation.react_router',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: '/performance/ssr',
- type: 'transaction',
- transaction_info: { source: 'route' },
- platform: 'javascript',
- request: {
- url: expect.stringContaining('/performance/ssr'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/browser', version: expect.any(String) },
- ],
- },
- tags: { runtime: 'browser' },
- });
- });
-
- test('should update navigation transaction for dynamic routes', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return (
- transactionEvent.transaction === '/performance/with/:param' &&
- transactionEvent.contexts?.trace?.op === 'navigation'
- );
- });
-
- const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
- });
-
- await page.goto(`/performance`); // pageload
- await pageloadTxPromise;
- await page.getByRole('link', { name: 'With Param Page' }).click(); // navigation
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.navigation.react_router',
- 'sentry.op': 'navigation',
- 'sentry.source': 'route',
- 'url.template': '/performance/with/:param',
- 'url.path': '/performance/with/sentry',
- 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
- },
- op: 'navigation',
- origin: 'auto.navigation.react_router',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: '/performance/with/:param',
- type: 'transaction',
- transaction_info: { source: 'route' },
- platform: 'javascript',
- request: {
- url: expect.stringContaining('/performance/with/sentry'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/browser', version: expect.any(String) },
- ],
- },
- tags: { runtime: 'browser' },
- });
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts
deleted file mode 100644
index f996989ccbf5..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('client - pageload performance', () => {
- test('should send pageload transaction', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
- });
-
- await page.goto(`/performance`);
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.pageload.react_router',
- 'sentry.op': 'pageload',
- 'sentry.source': 'route',
- 'url.template': '/performance',
- // react-router-serve 301-redirects the bare index route to a trailing slash
- 'url.path': '/performance/',
- 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/$/),
- },
- op: 'pageload',
- origin: 'auto.pageload.react_router',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: '/performance',
- type: 'transaction',
- transaction_info: { source: 'route' },
- measurements: expect.any(Object),
- platform: 'javascript',
- request: {
- url: expect.stringContaining('/performance'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/browser', version: expect.any(String) },
- ],
- },
- tags: { runtime: 'browser' },
- });
- });
-
- test('should update pageload transaction for dynamic routes', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return (
- transactionEvent.transaction === '/performance/with/:param' &&
- transactionEvent.contexts?.trace?.op === 'pageload'
- );
- });
-
- await page.goto(`/performance/with/sentry`);
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.pageload.react_router',
- 'sentry.op': 'pageload',
- 'sentry.source': 'route',
- 'url.template': '/performance/with/:param',
- 'url.path': '/performance/with/sentry',
- 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
- },
- op: 'pageload',
- origin: 'auto.pageload.react_router',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: '/performance/with/:param',
- type: 'transaction',
- transaction_info: { source: 'route' },
- measurements: expect.any(Object),
- platform: 'javascript',
- request: {
- url: expect.stringContaining('/performance/with/sentry'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/browser', version: expect.any(String) },
- ],
- },
- tags: { runtime: 'browser' },
- });
- });
-
- test('should send pageload transaction for prerendered pages', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return (
- transactionEvent.transaction === '/performance/static' && transactionEvent.contexts?.trace?.op === 'pageload'
- );
- });
-
- await page.goto(`/performance/static`);
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- transaction: '/performance/static',
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.pageload.react_router',
- 'sentry.op': 'pageload',
- 'sentry.source': 'route',
- 'url.template': '/performance/static',
- // react-router-serve 301-redirects prerendered routes to a trailing slash
- 'url.path': '/performance/static/',
- 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/static\/$/),
- },
- op: 'pageload',
- origin: 'auto.pageload.react_router',
- },
- },
- });
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts
deleted file mode 100644
index 18b7ce9f3c6c..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('server - performance', () => {
- test('should send server transaction on pageload', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === 'GET /performance';
- });
-
- await page.goto(`/performance`);
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.op': 'http.server',
- 'sentry.origin': 'auto.http.react_router.request_handler',
- 'sentry.source': 'route',
- },
- op: 'http.server',
- origin: 'auto.http.react_router.request_handler',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: 'GET /performance',
- type: 'transaction',
- transaction_info: { source: 'route' },
- platform: 'node',
- request: {
- url: expect.stringContaining('/performance'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/node', version: expect.any(String) },
- ],
- },
- tags: {
- runtime: 'node',
- },
- });
- });
-
- test('should send server transaction on parameterized route', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === 'GET /performance/with/:param';
- });
-
- await page.goto(`/performance/with/some-param`);
-
- const transaction = await txPromise;
-
- expect(transaction).toMatchObject({
- contexts: {
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.op': 'http.server',
- 'sentry.origin': 'auto.http.react_router.request_handler',
- 'sentry.source': 'route',
- },
- op: 'http.server',
- origin: 'auto.http.react_router.request_handler',
- },
- },
- spans: expect.any(Array),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: 'GET /performance/with/:param',
- type: 'transaction',
- transaction_info: { source: 'route' },
- platform: 'node',
- request: {
- url: expect.stringContaining('/performance/with/some-param'),
- headers: expect.any(Object),
- },
- event_id: expect.any(String),
- environment: 'qa',
- sdk: {
- integrations: expect.arrayContaining([expect.any(String)]),
- name: 'sentry.javascript.react-router',
- version: expect.any(String),
- packages: [
- { name: 'npm:@sentry/react-router', version: expect.any(String) },
- { name: 'npm:@sentry/node', version: expect.any(String) },
- ],
- },
- tags: {
- runtime: 'node',
- },
- });
- });
-
- test('should instrument wrapped server loader', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === 'GET /performance/server-loader.data';
- });
-
- await page.goto(`/performance`);
- await page.getByRole('link', { name: 'Server Loader' }).click();
-
- const transaction = await txPromise;
-
- expect(transaction).toEqual(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- op: 'http.server',
- origin: 'auto.http.react_router.loader',
- parent_span_id: expect.any(String),
- status: 'ok',
- data: expect.objectContaining({
- 'http.method': 'GET',
- 'http.response.status_code': 200,
- 'http.status_code': 200,
- 'http.status_text': 'OK',
- 'http.target': '/performance/server-loader.data',
- 'http.url': 'http://localhost:3030/performance/server-loader.data',
- 'sentry.op': 'http.server',
- 'sentry.origin': 'auto.http.react_router.loader',
- 'sentry.source': 'url',
- url: 'http://localhost:3030/performance/server-loader.data',
- }),
- },
- }),
- transaction: 'GET /performance/server-loader.data',
- type: 'transaction',
- transaction_info: { source: 'url' },
- platform: 'node',
- }),
- );
- // ensure we do not have a stray, bogus route attribute
- expect(transaction.contexts?.trace?.data?.['http.route']).not.toBeDefined();
-
- expect(transaction?.spans).toContainEqual({
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.http.react_router.loader',
- 'sentry.op': 'function.react_router.loader',
- },
- description: 'Executing Server Loader',
- parent_span_id: expect.any(String),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- status: 'ok',
- op: 'function.react_router.loader',
- origin: 'auto.http.react_router.loader',
- });
- });
-
- test('should instrument a wrapped server action', async ({ page }) => {
- const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === 'POST /performance/server-action.data';
- });
-
- await page.goto(`/performance/server-action`);
- await page.getByRole('button', { name: 'Submit' }).click();
-
- const transaction = await txPromise;
-
- expect(transaction).toEqual(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: expect.any(String),
- trace_id: expect.any(String),
- op: 'http.server',
- origin: 'auto.http.react_router.action',
- parent_span_id: expect.any(String),
- status: 'ok',
- data: expect.objectContaining({
- 'http.method': 'POST',
- 'http.response.status_code': 200,
- 'http.status_code': 200,
- 'http.status_text': 'OK',
- 'http.target': '/performance/server-action.data',
- 'http.url': 'http://localhost:3030/performance/server-action.data',
- 'sentry.op': 'http.server',
- 'sentry.origin': 'auto.http.react_router.action',
- 'sentry.source': 'url',
- url: 'http://localhost:3030/performance/server-action.data',
- }),
- },
- }),
- transaction: 'POST /performance/server-action.data',
- type: 'transaction',
- transaction_info: { source: 'url' },
- platform: 'node',
- }),
- );
- // ensure we do not have a stray, bogus route attribute
- expect(transaction.contexts?.trace?.data?.['http.route']).not.toBeDefined();
-
- expect(transaction?.spans).toContainEqual({
- span_id: expect.any(String),
- trace_id: expect.any(String),
- data: {
- 'sentry.origin': 'auto.http.react_router.action',
- 'sentry.op': 'function.react_router.action',
- },
- description: 'Executing Server Action',
- parent_span_id: expect.any(String),
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- status: 'ok',
- op: 'function.react_router.action',
- origin: 'auto.http.react_router.action',
- });
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts
deleted file mode 100644
index e9b2c9409154..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-import { APP_NAME } from '../constants';
-
-test.describe('Trace propagation', () => {
- test('should inject metatags in ssr pageload', async ({ page }) => {
- await page.goto(`/`);
- const sentryTraceContent = await page.getAttribute('meta[name="sentry-trace"]', 'content');
- expect(sentryTraceContent).toBeDefined();
- expect(sentryTraceContent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}-[01]$/);
- const baggageContent = await page.getAttribute('meta[name="baggage"]', 'content');
- expect(baggageContent).toBeDefined();
- expect(baggageContent).toContain('sentry-environment=qa');
- expect(baggageContent).toContain('sentry-public_key=');
- expect(baggageContent).toContain('sentry-trace_id=');
- expect(baggageContent).toContain('sentry-transaction=');
- expect(baggageContent).toContain('sentry-sampled=');
- });
-
- test('should have trace connection', async ({ page }) => {
- const serverTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === 'GET *';
- });
-
- const clientTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/';
- });
-
- await page.goto(`/`);
- const serverTx = await serverTxPromise;
- const clientTx = await clientTxPromise;
-
- expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id);
-
- const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express');
-
- expect(requestHandlerSpan).toBeDefined();
- expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id);
- });
-
- test('should not have trace connection for prerendered pages', async ({ page }) => {
- await page.goto('/performance/static');
-
- const sentryTraceElement = await page.$('meta[name="sentry-trace"]');
- expect(sentryTraceElement).toBeNull();
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json
deleted file mode 100644
index 1b510b528de9..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
- "types": ["node", "vite/client"],
- "target": "ES2022",
- "module": "ES2022",
- "moduleResolution": "bundler",
- "jsx": "react-jsx",
- "rootDirs": [".", "./.react-router/types"],
- "baseUrl": ".",
-
- "esModuleInterop": true,
- "verbatimModuleSyntax": true,
- "noEmit": true,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "strict": true
- },
- "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
- "exclude": ["tests/**/*"]
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts
deleted file mode 100644
index 68ba30d69397..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { reactRouter } from '@react-router/dev/vite';
-import { defineConfig } from 'vite';
-
-export default defineConfig({
- plugins: [reactRouter()],
-});
diff --git a/packages/react-router/src/server/index.ts b/packages/react-router/src/server/index.ts
index 3f8b7c0e149a..43470cf2c870 100644
--- a/packages/react-router/src/server/index.ts
+++ b/packages/react-router/src/server/index.ts
@@ -7,10 +7,6 @@ export { init } from './sdk';
// eslint-disable-next-line typescript/no-deprecated
export { wrapSentryHandleRequest, sentryHandleRequest } from './wrapSentryHandleRequest';
export { createSentryHandleRequest, type SentryHandleRequestOptions } from './createSentryHandleRequest';
-// eslint-disable-next-line typescript/no-deprecated
-export { wrapServerAction } from './wrapServerAction';
-// eslint-disable-next-line typescript/no-deprecated
-export { wrapServerLoader } from './wrapServerLoader';
export { createSentryHandleError, type SentryHandleErrorOptions } from './createSentryHandleError';
export { getMetaTagTransformer } from './getMetaTagTransformer';
diff --git a/packages/react-router/src/server/wrapServerAction.ts b/packages/react-router/src/server/wrapServerAction.ts
deleted file mode 100644
index 0ebaeca9cdb5..000000000000
--- a/packages/react-router/src/server/wrapServerAction.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { HTTP_TARGET } from '@sentry/conventions/attributes';
-import type { SpanAttributes } from '@sentry/core';
-import {
- debug,
- flushIfServerless,
- getActiveSpan,
- getRootSpan,
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
- spanToJSON,
- startSpan,
- updateSpanName,
-} from '@sentry/core';
-import type { ActionFunctionArgs } from 'react-router';
-import { DEBUG_BUILD } from '../common/debug-build';
-import { isInstrumentationApiUsed } from './serverGlobals';
-
-type SpanOptions = {
- name?: string;
- attributes?: SpanAttributes;
-};
-
-// Track if we've already warned about duplicate instrumentation
-let hasWarnedAboutDuplicateActionInstrumentation = false;
-
-// todo(v11): Remove this deprecated wrapper in favor of the instrumentation API (`createSentryServerInstrumentation`).
-/**
- * Wraps a React Router server action function with Sentry performance monitoring.
- *
- * @deprecated Use React Router's instrumentation API instead: export
- * `instrumentations = [createSentryServerInstrumentation()]` from your `entry.server.tsx` to instrument all server
- * actions without wrapping them individually. This manual wrapper will be removed in a future major.
- *
- * @param options - Optional span configuration options including name, operation, description and attributes
- * @param actionFn - The server action function to wrap
- *
- * @example
- * ```ts
- * // Wrap an action function with custom span options
- * export const action = wrapServerAction(
- * {
- * name: 'Submit Form Data',
- * description: 'Processes form submission data',
- * },
- * async ({ request }) => {
- * // ... your action logic
- * }
- * );
- * ```
- */
-export function wrapServerAction(
- options: SpanOptions = {},
- actionFn: (args: ActionFunctionArgs) => Promise,
-): (args: ActionFunctionArgs) => Promise {
- return async function (args: ActionFunctionArgs): Promise {
- // Skip instrumentation if instrumentation API is already handling it
- if (isInstrumentationApiUsed()) {
- if (DEBUG_BUILD && !hasWarnedAboutDuplicateActionInstrumentation) {
- hasWarnedAboutDuplicateActionInstrumentation = true;
- debug.warn(
- 'wrapServerAction is redundant when using the instrumentation API. ' +
- 'The action is already instrumented automatically. You can safely remove wrapServerAction.',
- );
- }
- return actionFn(args);
- }
-
- const name = options.name || 'Executing Server Action';
- const active = getActiveSpan();
- if (active) {
- const root = getRootSpan(active);
- const spanData = spanToJSON(root);
- if (spanData.origin === 'auto.http.otel.http') {
- // eslint-disable-next-line typescript/no-deprecated
- const target = spanData.data[HTTP_TARGET];
-
- if (target) {
- // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route
- // So we force this to be a more sensible name here
- updateSpanName(root, `${args.request.method} ${target}`);
- root.setAttributes({
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action',
- });
- }
- }
- }
-
- try {
- return await startSpan(
- {
- name,
- ...options,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action',
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',
- ...options.attributes,
- },
- },
- () => actionFn(args),
- );
- } finally {
- await flushIfServerless();
- }
- };
-}
diff --git a/packages/react-router/src/server/wrapServerLoader.ts b/packages/react-router/src/server/wrapServerLoader.ts
deleted file mode 100644
index ec48746f363b..000000000000
--- a/packages/react-router/src/server/wrapServerLoader.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { HTTP_TARGET } from '@sentry/conventions/attributes';
-import type { SpanAttributes } from '@sentry/core';
-import {
- debug,
- flushIfServerless,
- getActiveSpan,
- getRootSpan,
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
- spanToJSON,
- startSpan,
- updateSpanName,
-} from '@sentry/core';
-import type { LoaderFunctionArgs } from 'react-router';
-import { DEBUG_BUILD } from '../common/debug-build';
-import { isInstrumentationApiUsed } from './serverGlobals';
-
-type SpanOptions = {
- name?: string;
- attributes?: SpanAttributes;
-};
-
-// Track if we've already warned about duplicate instrumentation
-let hasWarnedAboutDuplicateLoaderInstrumentation = false;
-
-// todo(v11): Remove this deprecated wrapper in favor of the instrumentation API (`createSentryServerInstrumentation`).
-/**
- * Wraps a React Router server loader function with Sentry performance monitoring.
- *
- * @deprecated Use React Router's instrumentation API instead: export
- * `instrumentations = [createSentryServerInstrumentation()]` from your `entry.server.tsx` to instrument all server
- * loaders without wrapping them individually. This manual wrapper will be removed in a future major.
- *
- * @param options - Optional span configuration options including name, operation, description and attributes
- * @param loaderFn - The server loader function to wrap
- *
- * @example
- * ```ts
- * // Wrap a loader function with custom span options
- * export const loader = wrapServerLoader(
- * {
- * name: 'Load Some Data',
- * description: 'Loads some data from the db',
- * },
- * async ({ params }) => {
- * // ... your loader logic
- * }
- * );
- * ```
- */
-export function wrapServerLoader(
- options: SpanOptions = {},
- loaderFn: (args: LoaderFunctionArgs) => Promise,
-): (args: LoaderFunctionArgs) => Promise {
- return async function (args: LoaderFunctionArgs): Promise {
- // Skip instrumentation if instrumentation API is already handling it
- if (isInstrumentationApiUsed()) {
- if (DEBUG_BUILD && !hasWarnedAboutDuplicateLoaderInstrumentation) {
- hasWarnedAboutDuplicateLoaderInstrumentation = true;
- debug.warn(
- 'wrapServerLoader is redundant when using the instrumentation API. ' +
- 'The loader is already instrumented automatically. You can safely remove wrapServerLoader.',
- );
- }
- return loaderFn(args);
- }
-
- const name = options.name || 'Executing Server Loader';
- const active = getActiveSpan();
-
- if (active) {
- const root = getRootSpan(active);
- const spanData = spanToJSON(root);
- if (spanData.origin === 'auto.http.otel.http') {
- // eslint-disable-next-line typescript/no-deprecated
- const target = spanData.data[HTTP_TARGET];
-
- if (target) {
- // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route
- // So we force this to be a more sensible name here
- updateSpanName(root, `${args.request.method} ${target}`);
- root.setAttributes({
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader',
- });
- }
- }
- }
- try {
- return await startSpan(
- {
- name,
- ...options,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader',
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',
- ...options.attributes,
- },
- },
- () => loaderFn(args),
- );
- } finally {
- await flushIfServerless();
- }
- };
-}
diff --git a/packages/react-router/test/server/createServerInstrumentation.test.ts b/packages/react-router/test/server/createServerInstrumentation.test.ts
index 80b1a4597901..6762be8e4e83 100644
--- a/packages/react-router/test/server/createServerInstrumentation.test.ts
+++ b/packages/react-router/test/server/createServerInstrumentation.test.ts
@@ -70,7 +70,7 @@ describe('createSentryServerInstrumentation', () => {
// Creating the instrumentation must not mark the API active. On React Router versions that
// don't support the instrumentations API, the registration callbacks are never invoked, so
- // the legacy OTel data-loader path and wrapServerLoader/wrapServerAction must stay active.
+ // the legacy OTel data-loader path must stay active.
expect((globalThis as any).__sentryReactRouterServerInstrumentationUsed).toBeUndefined();
});
diff --git a/packages/react-router/test/server/wrapServerAction.test.ts b/packages/react-router/test/server/wrapServerAction.test.ts
deleted file mode 100644
index 149b90d570c0..000000000000
--- a/packages/react-router/test/server/wrapServerAction.test.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import * as core from '@sentry/core';
-import type { ActionFunctionArgs } from 'react-router';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { wrapServerAction } from '../../src/server/wrapServerAction';
-
-vi.mock('@sentry/core', async () => {
- const actual = await vi.importActual('@sentry/core');
- return {
- ...actual,
- startSpan: vi.fn(),
- flushIfServerless: vi.fn(),
- debug: {
- warn: vi.fn(),
- },
- };
-});
-
-describe('wrapServerAction', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- // Reset the global flag and warning state
- delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed;
- });
-
- afterEach(() => {
- delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed;
- });
-
- it('should wrap an action function with default options', async () => {
- const mockActionFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedAction = wrapServerAction({}, mockActionFn);
- await wrappedAction(mockArgs);
-
- expect(core.startSpan).toHaveBeenCalledWith(
- {
- name: 'Executing Server Action',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action',
- [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',
- },
- },
- expect.any(Function),
- );
- expect(mockActionFn).toHaveBeenCalledWith(mockArgs);
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should wrap an action function with custom options', async () => {
- const customOptions = {
- name: 'Custom Action',
- attributes: {
- 'sentry.custom': 'value',
- },
- };
-
- const mockActionFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedAction = wrapServerAction(customOptions, mockActionFn);
- await wrappedAction(mockArgs);
-
- expect(core.startSpan).toHaveBeenCalledWith(
- {
- name: 'Custom Action',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action',
- [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',
- 'sentry.custom': 'value',
- },
- },
- expect.any(Function),
- );
- expect(mockActionFn).toHaveBeenCalledWith(mockArgs);
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should call flushIfServerless on successful execution', async () => {
- const mockActionFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedAction = wrapServerAction({}, mockActionFn);
- await wrappedAction(mockArgs);
-
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should call flushIfServerless even when action throws an error', async () => {
- const mockError = new Error('Action failed');
- const mockActionFn = vi.fn().mockRejectedValue(mockError);
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedAction = wrapServerAction({}, mockActionFn);
-
- await expect(wrappedAction(mockArgs)).rejects.toThrow('Action failed');
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should propagate errors from action function', async () => {
- const mockError = new Error('Test error');
- const mockActionFn = vi.fn().mockRejectedValue(mockError);
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedAction = wrapServerAction({}, mockActionFn);
-
- await expect(wrappedAction(mockArgs)).rejects.toBe(mockError);
- });
-
- it('should skip span creation and warn when instrumentation API is used', async () => {
- // Reset modules to get a fresh copy with unset warning flag
- vi.resetModules();
- const { wrapServerAction: freshWrapServerAction } = await import('../../src/server/wrapServerAction');
-
- // Set the global flag indicating instrumentation API is in use
- (globalThis as any).__sentryReactRouterServerInstrumentationUsed = true;
-
- const mockActionFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs;
-
- const wrappedAction = freshWrapServerAction({}, mockActionFn);
-
- // Call multiple times
- await wrappedAction(mockArgs);
- await wrappedAction(mockArgs);
- await wrappedAction(mockArgs);
-
- // Should warn about redundant wrapper via debug.warn, but only once
- expect(core.debug.warn).toHaveBeenCalledTimes(1);
- expect(core.debug.warn).toHaveBeenCalledWith(
- expect.stringContaining('wrapServerAction is redundant when using the instrumentation API'),
- );
-
- // Should not create spans (instrumentation API handles it)
- expect(core.startSpan).not.toHaveBeenCalled();
-
- // Should still execute the action function
- expect(mockActionFn).toHaveBeenCalledTimes(3);
- });
-});
diff --git a/packages/react-router/test/server/wrapServerLoader.test.ts b/packages/react-router/test/server/wrapServerLoader.test.ts
deleted file mode 100644
index ce3be0f4319a..000000000000
--- a/packages/react-router/test/server/wrapServerLoader.test.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import * as core from '@sentry/core';
-import type { LoaderFunctionArgs } from 'react-router';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { wrapServerLoader } from '../../src/server/wrapServerLoader';
-
-vi.mock('@sentry/core', async () => {
- const actual = await vi.importActual('@sentry/core');
- return {
- ...actual,
- startSpan: vi.fn(),
- flushIfServerless: vi.fn(),
- debug: {
- warn: vi.fn(),
- },
- };
-});
-
-describe('wrapServerLoader', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- // Reset the global flag and warning state
- delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed;
- });
-
- afterEach(() => {
- delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed;
- });
-
- it('should wrap a loader function with default options', async () => {
- const mockLoaderFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedLoader = wrapServerLoader({}, mockLoaderFn);
- await wrappedLoader(mockArgs);
-
- expect(core.startSpan).toHaveBeenCalledWith(
- {
- name: 'Executing Server Loader',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader',
- [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',
- },
- },
- expect.any(Function),
- );
- expect(mockLoaderFn).toHaveBeenCalledWith(mockArgs);
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should wrap a loader function with custom options', async () => {
- const customOptions = {
- name: 'Custom Loader',
- attributes: {
- 'sentry.custom': 'value',
- },
- };
-
- const mockLoaderFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedLoader = wrapServerLoader(customOptions, mockLoaderFn);
- await wrappedLoader(mockArgs);
-
- expect(core.startSpan).toHaveBeenCalledWith(
- {
- name: 'Custom Loader',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader',
- [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',
- 'sentry.custom': 'value',
- },
- },
- expect.any(Function),
- );
- expect(mockLoaderFn).toHaveBeenCalledWith(mockArgs);
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should call flushIfServerless on successful execution', async () => {
- const mockLoaderFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedLoader = wrapServerLoader({}, mockLoaderFn);
- await wrappedLoader(mockArgs);
-
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should call flushIfServerless even when loader throws an error', async () => {
- const mockError = new Error('Loader failed');
- const mockLoaderFn = vi.fn().mockRejectedValue(mockError);
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedLoader = wrapServerLoader({}, mockLoaderFn);
-
- await expect(wrappedLoader(mockArgs)).rejects.toThrow('Loader failed');
- expect(core.flushIfServerless).toHaveBeenCalled();
- });
-
- it('should propagate errors from loader function', async () => {
- const mockError = new Error('Test error');
- const mockLoaderFn = vi.fn().mockRejectedValue(mockError);
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- (core.startSpan as any).mockImplementation((_: any, fn: any) => fn());
-
- const wrappedLoader = wrapServerLoader({}, mockLoaderFn);
-
- await expect(wrappedLoader(mockArgs)).rejects.toBe(mockError);
- });
-
- it('should skip span creation and warn when instrumentation API is used', async () => {
- // Reset modules to get a fresh copy with unset warning flag
- vi.resetModules();
- const { wrapServerLoader: freshWrapServerLoader } = await import('../../src/server/wrapServerLoader');
-
- // Set the global flag indicating instrumentation API is in use
- (globalThis as any).__sentryReactRouterServerInstrumentationUsed = true;
-
- const mockLoaderFn = vi.fn().mockResolvedValue('result');
- const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs;
-
- const wrappedLoader = freshWrapServerLoader({}, mockLoaderFn);
-
- // Call multiple times
- await wrappedLoader(mockArgs);
- await wrappedLoader(mockArgs);
- await wrappedLoader(mockArgs);
-
- // Should warn about redundant wrapper via debug.warn, but only once
- expect(core.debug.warn).toHaveBeenCalledTimes(1);
- expect(core.debug.warn).toHaveBeenCalledWith(
- expect.stringContaining('wrapServerLoader is redundant when using the instrumentation API'),
- );
-
- // Should not create spans (instrumentation API handles it)
- expect(core.startSpan).not.toHaveBeenCalled();
-
- // Should still execute the loader function
- expect(mockLoaderFn).toHaveBeenCalledTimes(3);
- });
-});