Skip to content

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

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

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
Copy Markdown
Contributor

@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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

@chargome, it's ready for review 👍

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 4, 2026

Codecov Results 📊


Generated by Codecov Action

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown
Contributor 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
Copy Markdown

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
Copy Markdown

@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
Copy Markdown

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

@chargome chargome self-assigned this Mar 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you apply the label PR: no-auto-close I will leave it alone ... forever!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support embeddings in langchain by nicohrubec in #20017
  • Support embedding APIs in google-genai by nicohrubec in #19797

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066
  • Bump OpenTelemetry dependencies by andreiborza in #20046
  • Bump babel-loader from 10.0.0 to 10.1.1 by dependabot in #19997
  • Bump handlebars from 4.7.7 to 4.7.9 by dependabot in #20008

Nuxt

  • Add middleware instrumentation compatibility for Nuxt 5 by s1gr1d in #19968
  • Support parametrized SSR routes in Nuxt 5 by s1gr1d in #19977

Other

  • (browser) Replace element timing spans with metrics by logaretm in #19869
  • (bun) Add bunRuntimeMetricsIntegration by chargome in #19979
  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node) Add nodeRuntimeMetricsIntegration by chargome in #19923
  • (node-core) Add OTLP integration for node-core/light by andreiborza in #19729
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068
  • (react-router) Add Experimental React Server Components (RSC) instrumentation by onurtemizkan in #18882
  • (solid) Add route parametrization for Solid Router by andreiborza in #20031

Bug Fixes 🐛

Ci

  • Update validate-pr action to remove draft enforcement by stephanie-anderson in #20037
  • Update validate-pr action to remove draft enforcement by stephanie-anderson in #20035

Core

  • Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • Guard nullish response in supabase PostgREST handler by antonis in #20033

Node

  • Deduplicate sentry-trace and baggage headers on outgoing requests by Lms24 in #19960
  • Ensure startNewTrace propagates traceId in OTel environments by logaretm in #19963

Other

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (browser-tests) Pin axios to 1.13.5 to avoid compromised 1.14.1 by andreiborza in #20047
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (e2e) Pin @opentelemetry/api to 1.9.0 in ts3.8 test app by logaretm in #19992
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048
  • (nuxt) Use virtual module for Nuxt pages data (SSR route parametrization) by s1gr1d in #20020
  • (opentelemetry) Convert seconds timestamps in span.end() to milliseconds by logaretm in #19958
  • (profiling) Disable profiling in worker threads by chargome in #20040
  • (react-router) Disable debug ID injection in Vite plugin to prevent double injection by isaacs in #19890

Documentation 📚

  • (release) Update publishing-a-release.md by nicohrubec in #19982

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011
  • Introduce instrumented method registry for AI integrations by nicohrubec in #19981
  • Consolidate getOperationName into one shared utility by nicohrubec in #19971

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081
  • Bump amqplib from 0.10.7 to 0.10.9 by dependabot in #20000
  • Bump actions/upload-artifact from 6 to 7 by dependabot in #19569
  • Bump srvx from 0.11.12 to 0.11.13 by dependabot in #20001
  • Bump @apollo/server from 5.4.0 to 5.5.0 by dependabot in #20007

Deps Dev

  • Remove esbuild override in astro-5-cf-workers E2E test by isaacs in #20024
  • Bump node-forge from 1.3.2 to 1.4.0 by dependabot in #20012
  • Bump yaml from 2.8.2 to 2.8.3 by dependabot in #19985

Other

  • (browser) Reduce browser package bundle size by HazAT in #19856
  • (browser-tests) Add waitForMetricRequest helper by logaretm in #20002
  • (changelog) Update changelog for 10.47.0 by chargome in #20050
  • (deno) Expand Deno E2E test coverage by chargome in #19957
  • (e2e) Add e2e tests for nodeRuntimeMetricsIntegration by chargome in #19989
  • (gitflow) Sync master with develop by chargome in #20054
  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@chargome
Copy link
Copy Markdown
Member

chargome commented Apr 7, 2026

Will close this one for now until we get a clear signal from either the community that this feature is wanted or from the react router team that this feature will indeed be shipped with v8. Currently this is marked as "Nice to Have" in remix-run/react-router#14468. I'd rather keep the API surface slim for now, but if needed we can re-open.

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 Server Components

3 participants