Skip to content

feat(react-router): Add Experimental React Server Components (RSC) instrumentation#18882

Open
onurtemizkan wants to merge 24 commits intodevelopfrom
onur/react-router-rsc-experimental
Open

feat(react-router): Add Experimental React Server Components (RSC) instrumentation#18882
onurtemizkan wants to merge 24 commits intodevelopfrom
onur/react-router-rsc-experimental

Conversation

@onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented Jan 19, 2026

Resolves: #17337

Adds experimental error capture and performance monitoring for React Router v7's React Server Components mode (v7.9.0+).

In RSC mode, entry.server.tsx is bypassed for server component renders, so wrapSentryHandleRequest never runs. HTTP spans are not parameterized, transaction names are not set, and errors in server components and server functions are not captured.

Server Components

Added wrapServerComponent() for error capture and span parameterization:

// app/routes/users.$id.tsx
import * as Sentry from '@sentry/react-router';

async function _UserPage({ params }: Route.ComponentProps) {
  const user = await getUser(params.id);
  return <UserProfile user={user} />;
}

export default Sentry.wrapServerComponent(_UserPage, {
  componentRoute: '/users/:id',
  componentType: 'Page',
});
  • Runs within the existing HTTP request span (does not create its own)
  • Sets transaction name on the isolation scope and parameterizes the root span (e.g., GET /users/:id)
  • Captures errors with mechanism type react_router.rsc
  • Redirect (3xx) and not-found responses are not captured as errors

Server Functions

Added wrapServerFunction() for "use server" functions:

// app/actions.ts
"use server";
import { wrapServerFunction } from '@sentry/react-router';

async function _updateUser(formData: FormData) {
  await db.users.update(formData.get('id'), { name: formData.get('name') });
}

export const updateUser = wrapServerFunction("updateUser", _updateUser);

Each invocation creates a dedicated span with op function.rsc.server_function and forceTransaction: true when no parent span exists.

Automatic Server Function Instrumentation

Added a Vite plugin that auto-wraps all exported functions in "use server" files. Opt-in only:

// vite.config.ts
sentryReactRouter({
  experimental_rscAutoInstrumentation: { enabled: true },
})

Uses AST parsing (recast + @babel/parser) to detect "use server" directives and exported functions. Skips files that already import wrapServerFunction. Server components are not auto-instrumented.

Also:

  • Added manifest-based route matching fallback in wrapSentryHandleRequest for RSC mode where staticHandlerContext.matches is not populated
  • Added no-op client-side stubs for wrapServerComponent and wrapServerFunction to prevent import errors
  • Added @babel/parser and recast as dependencies for AST parsing (Similar to SvelteKit)
  • Added E2E test application (react-router-7-rsc) covering server components, server functions, and performance spans

@github-actions
Copy link
Contributor

github-actions bot commented Jan 20, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,809 - 8,843 -0%
GET With Sentry 1,487 17% 1,637 -9%
GET With Sentry (error only) 5,618 64% 5,887 -5%
POST Baseline 1,156 - 1,163 -1%
POST With Sentry 472 41% 547 -14%
POST With Sentry (error only) 1,001 87% 1,023 -2%
MYSQL Baseline 3,137 - 3,218 -3%
MYSQL With Sentry 259 8% 336 -23%
MYSQL With Sentry (error only) 2,538 81% 2,653 -4%

View base workflow run

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch 2 times, most recently from 159fb80 to 07fc2d6 Compare January 23, 2026 15:42
@chargome
Copy link
Member

chargome commented Feb 2, 2026

@onurtemizkan sorry this one went under the radar. Can you resolve the conflict and ping me again?

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from 07fc2d6 to c94138a Compare February 4, 2026 00:53
@onurtemizkan onurtemizkan marked this pull request as ready for review February 4, 2026 00:53
@onurtemizkan
Copy link
Collaborator Author

@chargome, it's ready for review 👍

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Codecov Results 📊


Generated by Codecov Action

Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM to me but is there a way we could already wrap the server components with vite? Also initing the Client in a hook might happen quite late – any ideas if the client will eventually support an entry file?

Comment on lines 4 to 8
/**
* WeakSet to track errors that have been captured to avoid double-capture.
* Uses WeakSet so errors are automatically removed when garbage collected.
*/
const CAPTURED_ERRORS = new WeakSet<object>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Why do we need this in here? We have a dedupe integration that should take of this I think?

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from 7954e55 to 141002a Compare February 6, 2026 16:17
@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from e10f4e4 to e082621 Compare February 19, 2026 19:48
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.62 kB - -
@sentry/browser - with treeshaking flags 24.12 kB - -
@sentry/browser (incl. Tracing) 42.42 kB - -
@sentry/browser (incl. Tracing, Profiling) 47.09 kB - -
@sentry/browser (incl. Tracing, Replay) 81.24 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.86 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 85.94 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 98.2 kB - -
@sentry/browser (incl. Feedback) 42.43 kB - -
@sentry/browser (incl. sendFeedback) 30.29 kB - -
@sentry/browser (incl. FeedbackAsync) 35.34 kB - -
@sentry/browser (incl. Metrics) 26.79 kB - -
@sentry/browser (incl. Logs) 26.93 kB - -
@sentry/browser (incl. Metrics & Logs) 27.61 kB - -
@sentry/react 27.37 kB - -
@sentry/react (incl. Tracing) 44.76 kB - -
@sentry/vue 30.07 kB - -
@sentry/vue (incl. Tracing) 44.29 kB - -
@sentry/svelte 25.64 kB - -
CDN Bundle 28.16 kB - -
CDN Bundle (incl. Tracing) 43.25 kB - -
CDN Bundle (incl. Logs, Metrics) 29 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.09 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.08 kB - -
CDN Bundle (incl. Tracing, Replay) 80.13 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 80.99 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 85.64 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.53 kB - -
CDN Bundle - uncompressed 82.34 kB - -
CDN Bundle (incl. Tracing) - uncompressed 128.06 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.18 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 130.89 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 208.84 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.94 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 247.76 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.85 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 260.66 kB - -
@sentry/nextjs (client) 47.17 kB - -
@sentry/sveltekit (client) 42.88 kB - -
@sentry/node-core 52.18 kB +0.02% +9 B 🔺
@sentry/node 174.49 kB +0.01% +5 B 🔺
@sentry/node - without tracing 97.33 kB +0.02% +10 B 🔺
@sentry/aws-serverless 113.13 kB +0.01% +6 B 🔺

View base workflow run

// - minor import and export changes
// - merged the two files linked above into one for simplicity

// Date of access: 2025-03-04
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect date in source attribution comment

Low Severity

The comment states "Date of access: 2025-03-04" but today's date is 2026-02-19. This appears to be placeholder text or an incorrect timestamp that was accidentally left in the code during development. The date should either be corrected to reflect when the code was actually accessed, or removed if it's not meaningful.

Fix in Cursor Fix in Web

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from 239bc9f to 7feed8e Compare February 23, 2026 07:46
@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch 2 times, most recently from 59f2077 to 77e55c9 Compare February 24, 2026 12:13
Comment on lines 98 to 102
/**
* Enable debug logging to see which files are being instrumented.
* @default false
*/
debug?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason, why there is another debug option? Would it be possible to use the one from the client options?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated 👍

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from c0f9783 to 8f21d5c Compare February 27, 2026 12:20
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
'rsc.server_function.name': functionName,
...options.attributes,
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom attributes can overwrite internal Sentry span attributes

Low Severity

In wrapServerFunction, ...options.attributes is spread after the internal Sentry attributes (SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE), allowing user-provided attributes to silently overwrite them. This could break span categorization and origin tracking. The spread order could be reversed so Sentry-internal attributes take precedence.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

@onurtemizkan onurtemizkan force-pushed the onur/react-router-rsc-experimental branch from 267d969 to 98cd9d4 Compare February 27, 2026 14:19
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

else score += STATIC_SEGMENT_SCORE;
}
return score;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two iterations over segments array in computeScore

Low Severity

computeScore iterates segments twice: first with segments.includes('*') and then with the for...of loop. Since the * check within the loop already handles segment === '*' with continue, the splat penalty could be applied inside the same loop, consolidating both passes into one. This is on the request hot path in matchUrlToManifestRoute.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support React Router Server Components

3 participants