Skip to content

feat(remix): Server Timing Headers Trace Propagation#18653

Merged
Lms24 merged 6 commits intodevelopfrom
onur/remix-server-timing-headers
Mar 18, 2026
Merged

feat(remix): Server Timing Headers Trace Propagation#18653
Lms24 merged 6 commits intodevelopfrom
onur/remix-server-timing-headers

Conversation

@onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented Jan 1, 2026

Adds automatic trace propagation from server to client via the Server-Timing HTTP header for Remix applications. The client-side reading of Server-Timing headers via the Performance API was added in #18673.

Adds:

  • generateSentryServerTimingHeader(span) public utility that generates a Server-Timing header value containing Sentry trace context
  • Automatic injection in the document request handler for normal page responses
  • Automatic injection on redirect responses from loaders and actions, which bypass the document request handler entirely. This is an advantage over meta tag injection, which cannot work on redirect responses since they have no HTML body
  • For Cloudflare/Hydrogen apps: call generateSentryServerTimingHeader() manually and append the value to the response's Server-Timing header in entry.server.tsx (see remix-hydrogen e2e test for example)

Works on both Node.js and Cloudflare Workers environments.

Closes #18696

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a proof-of-concept for propagating Sentry trace context from server to client using the Server-Timing HTTP header and the browser Performance API. This provides an alternative to meta tag-based trace propagation, particularly useful for streaming SSR responses and edge runtimes.

Key changes:

  • Added utilities for generating and injecting Server-Timing headers with Sentry trace data
  • Implemented client-side parsing of Server-Timing headers via the Performance API
  • Updated server instrumentation to capture and propagate trace context via Server-Timing headers
  • Added comprehensive E2E test coverage for both Node.js and Cloudflare environments

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
packages/remix/src/server/serverTimingTracePropagation.ts New utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.ts New client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.ts Updated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsx Updated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.ts Exported new Server-Timing utilities for public API
packages/remix/src/client/index.ts Exported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.ts Exported Server-Timing utilities for Cloudflare runtime
dev-packages/e2e-tests/test-applications/remix-server-timing/* New E2E test application validating Server-Timing trace propagation
dev-packages/e2e-tests/test-applications/remix-hydrogen/* Updated Hydrogen test app to demonstrate Cloudflare support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468 Compare January 2, 2026 15:42
@onurtemizkan onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03dd Compare January 5, 2026 12:55
@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420 Compare January 5, 2026 15:31
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 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,958 - 9,206 -3%
GET With Sentry 1,719 19% 1,710 +1%
GET With Sentry (error only) 6,081 68% 6,099 -0%
POST Baseline 1,200 - 1,202 -0%
POST With Sentry 590 49% 579 +2%
POST With Sentry (error only) 1,039 87% 1,059 -2%
MYSQL Baseline 3,248 - 3,346 -3%
MYSQL With Sentry 427 13% 457 -7%
MYSQL With Sentry (error only) 2,591 80% 2,668 -3%

View base workflow run

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90 Compare January 8, 2026 09:25
@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6 Compare January 23, 2026 14:35
Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

@onurtemizkan sorry for the delay on this! I merged in #18673 to get support for server-timing trace continuation on the client side in the general browser SDK. Could you update the PR to use this instead of the remix-specific approach? The server side looks fine to me, though I'll give this a more proper look once the PR is updated.

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85c Compare February 2, 2026 13:59
@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

Codecov Results 📊


Generated by Codecov Action

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440 Compare February 20, 2026 19:17
@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.64 kB - -
@sentry/browser - with treeshaking flags 24.14 kB - -
@sentry/browser (incl. Tracing) 42.62 kB - -
@sentry/browser (incl. Tracing, Profiling) 47.28 kB - -
@sentry/browser (incl. Tracing, Replay) 81.42 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 86.12 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 98.37 kB - -
@sentry/browser (incl. Feedback) 42.45 kB - -
@sentry/browser (incl. sendFeedback) 30.31 kB - -
@sentry/browser (incl. FeedbackAsync) 35.36 kB - -
@sentry/browser (incl. Metrics) 26.92 kB - -
@sentry/browser (incl. Logs) 27.07 kB - -
@sentry/browser (incl. Metrics & Logs) 27.74 kB - -
@sentry/react 27.39 kB - -
@sentry/react (incl. Tracing) 44.95 kB - -
@sentry/vue 30.08 kB - -
@sentry/vue (incl. Tracing) 44.48 kB - -
@sentry/svelte 25.66 kB - -
CDN Bundle 28.28 kB - -
CDN Bundle (incl. Tracing) 43.51 kB - -
CDN Bundle (incl. Logs, Metrics) 29.14 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.36 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.21 kB - -
CDN Bundle (incl. Tracing, Replay) 80.33 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.23 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 85.87 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.77 kB - -
CDN Bundle - uncompressed 82.62 kB - -
CDN Bundle (incl. Tracing) - uncompressed 128.56 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.49 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.43 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.12 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 245.41 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.26 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 258.32 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.17 kB - -
@sentry/nextjs (client) 47.37 kB - -
@sentry/sveltekit (client) 43.07 kB - -
@sentry/node-core 56.38 kB +0.06% +32 B 🔺
@sentry/node 173.19 kB +0.02% +31 B 🔺
@sentry/node - without tracing 96.37 kB +0.03% +28 B 🔺
@sentry/aws-serverless 113.37 kB +0.03% +31 B 🔺

View base workflow run

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53e Compare February 23, 2026 14:36
}

return parts.join(', ');
} catch (e) {
Copy link

Choose a reason for hiding this comment

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

Unescaped quotes in Server-Timing desc

Medium Severity

generateSentryServerTimingHeader interpolates sentryTrace and baggage into Server-Timing desc="..." without escaping. If either value ever contains " or \ (for example via non-standard baggage values), the header becomes syntactically invalid and may be dropped or parsed incorrectly by proxies/browsers, breaking trace propagation.

Fix in Cursor Fix in Web

@onurtemizkan onurtemizkan force-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9 Compare February 24, 2026 07:18
@onurtemizkan onurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoC feat(remix): Server Timing Headers Trace Propagation Feb 24, 2026
@github-actions
Copy link
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!

@Lms24 Lms24 force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15fface Compare March 18, 2026 14:42
@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 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 ✨

  • (remix) Server Timing Headers Trace Propagation by onurtemizkan in #18653

Bug Fixes 🐛

Deps

  • Bump devalue 5.6.3 to 5.6.4 to fix CVE-2026-30226 by chargome in #19849
  • Bump file-type to 21.3.2 and @nestjs/common to 11.1.17 by chargome in #19847
  • Bump unhead 2.1.4 to 2.1.12 to fix CVE-2026-31860 and CVE-2026-31873 by chargome in #19848
  • Bump flatted 3.3.1 to 3.4.2 to fix CVE-2026-32141 by chargome in #19842
  • Bump tar 7.5.10 to 7.5.11 to fix CVE-2026-31802 by chargome in #19846
  • Bump hono 4.12.5 to 4.12.7 in cloudflare-hono E2E test app by chargome in #19850
  • Bump undici 6.23.0 to 6.24.1 to fix multiple CVEs by chargome in #19841

Other

  • (deno) Clear pre-existing OTel global before registering TracerProvider by sergical in #19723
  • (node-core) Recycle propagationContext for each request by Lms24 in #19835

Internal Changes 🔧

  • (deps) Bump next from 16.1.5 to 16.1.7 in /dev-packages/e2e-tests/test-applications/nextjs-16 by dependabot in #19851
  • (react) Add gql tests for react router by chargome in #19844
  • (release) Switch from action-prepare-release to Craft by BYK in #18763

🤖 This preview updates automatically when you update the PR.

@Lms24 Lms24 self-assigned this Mar 18, 2026
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.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Reversed fallback makes scope context path unreachable dead code
    • Restored correct order of OR expression to check scope context first before falling back to setSpan, making the fallback path reachable and the scope variable properly utilized.

Create PR

Or push these changes by commenting:

@cursor push 061834c332
Preview (061834c332)
diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts
--- a/packages/opentelemetry/src/utils/getTraceData.ts
+++ b/packages/opentelemetry/src/utils/getTraceData.ts
@@ -24,7 +24,7 @@
   if (span) {
     const { scope } = getCapturedScopesOnSpan(span);
     // fall back to current context if for whatever reason we can't find the one of the span
-    ctx = api.trace.setSpan(api.context.active(), span) || (scope && getContextFromScope(scope));
+    ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span);
   }
 
   const { traceId, spanId, sampled, dynamicSamplingContext } = getInjectionData(ctx, { scope, client });

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

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.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

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

response = await origDocumentRequestFunction.call(this, request, ...args);
}

if (serverTimingHeader && response instanceof Response) {
Copy link

Choose a reason for hiding this comment

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

Uses instanceof Response instead of duck-typed isResponse

Medium Severity

The new check at line 148 uses response instanceof Response to guard Server-Timing injection in the document request wrapper. Every other response check in this file (lines 73, 221, 263, 388) uses the duck-typed isResponse() helper from ../utils/vendor/response, which is already imported on line 40. The isResponse function was adopted from Remix's own source to handle cross-realm and polyfill cases where instanceof fails. Using instanceof Response could silently skip Server-Timing header injection on document responses in Node.js environments with polyfilled or differently-sourced Response constructors.

Fix in Cursor Fix in Web

@Lms24 Lms24 merged commit ae7206f into develop Mar 18, 2026
170 checks passed
@Lms24 Lms24 deleted the onur/remix-server-timing-headers branch March 18, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants