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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

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

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
packages/remix/src/server/serverTimingTracePropagation.tsNew utility module for generating Server-Timing headers with trace context
packages/remix/src/client/serverTimingTracePropagation.tsNew client-side utilities for parsing trace data from Server-Timing headers
packages/remix/src/server/instrumentServer.tsUpdated server instrumentation to inject Server-Timing headers and refactored trace propagation logic
packages/remix/src/client/performance.tsxUpdated pageload span initialization to use Server-Timing trace propagation
packages/remix/src/server/index.tsExported new Server-Timing utilities for public API
packages/remix/src/client/index.tsExported new client-side Server-Timing utilities
packages/remix/src/cloudflare/index.tsExported 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.

Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/client/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 4 times, most recently from 1f07bc8 to 67ec468CompareJanuary 2, 2026 15:42
@onurtemizkan
onurtemizkan marked this pull request as ready for review January 5, 2026 12:55
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 3e06c9b to 48e03ddCompareJanuary 5, 2026 12:55
Comment threadpackages/remix/src/client/performance.tsx Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from d882ca3 to 982c420CompareJanuary 5, 2026 15:31
@github-actions

github-actionsBot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

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.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline8,958-9,206-3%
GET With Sentry1,71919%1,710+1%
GET With Sentry (error only)6,08168%6,099-0%
POST Baseline1,200-1,202-0%
POST With Sentry59049%579+2%
POST With Sentry (error only)1,03987%1,059-2%
MYSQL Baseline3,248-3,346-3%
MYSQL With Sentry42713%457-7%
MYSQL With Sentry (error only)2,59180%2,668-3%

View base workflow run

Comment threadpackages/remix/src/server/instrumentServer.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 1b1481d to 8a43e90CompareJanuary 8, 2026 09:25
Comment threadpackages/remix/src/server/serverTimingTracePropagation.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from fa61b3c to 7518ec6CompareJanuary 23, 2026 14:35

@Lms24Lms24 left a comment

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.

@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
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 7518ec6 to 1f7c85cCompareFebruary 2, 2026 13:59
@github-actions

github-actionsBot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊


Generated by Codecov Action

Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
Comment threadpackages/remix/src/server/instrumentServer.ts Outdated
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from 25fa718 to 1033440CompareFebruary 20, 2026 19:17
@github-actions

github-actionsBot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

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.

PathSize% ChangeChange
@sentry/browser25.64 kB--
@sentry/browser - with treeshaking flags24.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 flags71 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/react27.39 kB--
@sentry/react (incl. Tracing)44.95 kB--
@sentry/vue30.08 kB--
@sentry/vue (incl. Tracing)44.48 kB--
@sentry/svelte25.66 kB--
CDN Bundle28.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 - uncompressed82.62 kB--
CDN Bundle (incl. Tracing) - uncompressed128.56 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed85.49 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.43 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.12 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed245.41 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.26 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed258.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.17 kB--
@sentry/nextjs (client)47.37 kB--
@sentry/sveltekit (client)43.07 kB--
@sentry/node-core56.38 kB+0.06%+32 B 🔺
@sentry/node173.19 kB+0.02%+31 B 🔺
@sentry/node - without tracing96.37 kB+0.03%+28 B 🔺
@sentry/aws-serverless113.37 kB+0.03%+31 B 🔺

View base workflow run

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch 2 times, most recently from 4ae90bf to 4d1a53eCompareFebruary 23, 2026 14:36
Comment threadpackages/remix/src/server/instrumentServer.ts
}

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

Copy link
Copy Markdown

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-Timingdesc="..." 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 CursorFix in Web

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-server-timing-headers branch from e16a926 to c88fee9CompareFebruary 24, 2026 07:18
@onurtemizkanonurtemizkan changed the title feat(remix): Server Timing Headers Trace Propagation PoCfeat(remix): Server Timing Headers Trace PropagationFeb 24, 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!

@Lms24
Lms24force-pushed the onur/remix-server-timing-headers branch from c88fee9 to 15ffaceCompareMarch 18, 2026 14:42
@github-actions

github-actionsBot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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.

@Lms24Lms24 self-assigned this Mar 18, 2026
Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadpackages/opentelemetry/src/utils/getTraceData.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown

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

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 CursorFix in Web

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(remix): Server Timing Headers Trace Propagation PoC

3 participants

@onurtemizkan@Lms24