Skip to content

Add a tunnel route helper for TanStack Start React#20264

Open
nikolovlazar wants to merge 7 commits intodevelopfrom
lazarnikolov/js-2140-tanstack-start-tunnel-adapter
Open

Add a tunnel route helper for TanStack Start React#20264
nikolovlazar wants to merge 7 commits intodevelopfrom
lazarnikolov/js-2140-tanstack-start-tunnel-adapter

Conversation

@nikolovlazar
Copy link
Copy Markdown
Member

@nikolovlazar nikolovlazar commented Apr 13, 2026

Summary

  • add createSentryTunnelRoute to @sentry/tanstackstart-react as a one-line server route adapter around the core tunnel handler
  • extend sentryTanstackStart() (the existing Vite integration) with a new tunnelRoute option which can automatically register a same-origin tunnel route and set the client tunnel option to point to it
  • support two tunnelRoute variants:
    • dynamic (tunnel: true, default): generates an opaque route path once per dev session / production build so ad blockers can’t reliably target it by a known path
    • static (tunnel: '/custom-path'): uses an explicit fixed tunnel route path

Notes

  • yarn.lock contains unrelated changes and should be reviewed separately

Testing

  • cd packages/tanstackstart-react && yarn test
  • manually verified in the TanStack Start e2e app in dev-packages that /monitor returns 200 and forwards envelopes to a real Sentry project. I also made sure session replays are being handled correctly. Screenshots from my Sentry org's usage indicating the events were received:
SCR-20260413-nnol SCR-20260413-nnrr SCR-20260413-nppm
  • I also manually tested the generated tunnel endpoints and they work correctly as well
image

Examples

  • createSentryTunnelRoute (manual server route)
// src/routes/monitor.ts
import { createFileRoute } from '@tanstack/react-router';
import * as Sentry from '@sentry/tanstackstart-react';

export const Route = createFileRoute('/monitor')({
  server: Sentry.createSentryTunnelRoute({
    allowedDsns: ['https://public@o0.ingest.sentry.io/0'],
  }),
});
  • sentryTanstackStart({ tunnelRoute }) (managed tunnel route via Vite plugin)
// vite.config.ts
import { defineConfig } from 'vite';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import { sentryTanstackStart } from '@sentry/tanstackstart-react/vite';

export default defineConfig({
  plugins: [
    tanstackStart(),
    sentryTanstackStart({
      tunnelRoute: {
        allowedDsns: ['https://public@o0.ingest.sentry.io/0'],
        // dynamic per dev session / build:
        tunnel: true,
        // or static:
        // tunnel: '/monitor',
      },
    }),
  ],
});

@linear-code
Copy link
Copy Markdown

linear-code bot commented Apr 13, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Semver Impact of This PR

None (no version bump detected)

📋 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 ✨

Cloudflare

  • Split alarms into multiple traces and link them by JPeer264 in #19373
  • Propagate traceparent to RPC calls - via fetch by JPeer264 in #19991

Core

  • Automatically disable truncation when span streaming is enabled in LangGraph integration by andreiborza in #20231
  • Automatically disable truncation when span streaming is enabled in LangChain integration by andreiborza in #20230
  • Automatically disable truncation when span streaming is enabled in Google GenAI integration by andreiborza in #20229
  • Automatically disable truncation when span streaming is enabled in Anthropic AI integration by andreiborza in #20228
  • Automatically disable truncation when span streaming is enabled in Vercel AI integration by andreiborza in #20232
  • Automatically disable truncation when span streaming is enabled in OpenAI integration by andreiborza in #20227
  • Add enableTruncation option to Vercel AI integration by nicohrubec in #20195
  • Add enableTruncation option to Google GenAI integration by andreiborza in #20184
  • Add enableTruncation option to Anthropic AI integration by andreiborza in #20181
  • Add enableTruncation option to LangGraph integration by andreiborza in #20183
  • Add enableTruncation option to LangChain integration by andreiborza in #20182
  • Add enableTruncation option to OpenAI integration by andreiborza in #20167
  • Export a reusable function to add tracing headers by JPeer264 in #20076

Deps

  • Bump axios from 1.13.5 to 1.15.0 by dependabot in #20180
  • Bump hono from 4.12.7 to 4.12.12 by dependabot in #20118
  • Bump defu from 6.1.4 to 6.1.6 by dependabot in #20104

Other

  • (browser) Add View Hierarchy integration by timfish in #14981
  • (node) Include global scope for eventLoopBlockIntegration by timfish in #20108
  • (node-native) Add support for V8 v14 (Node v25+) by timfish in #20125

Bug Fixes 🐛

Deno

  • Handle reader.closed rejection from releaseLock() in streaming by andreiborza in #20187
  • Avoid inferring invalid span op from Deno tracer by Lms24 in #20128

Other

  • (ci) Prevent command injection in ci-metadata workflow by fix-it-felix-sentry in #19899
  • (core, node) Support loading Express options lazily by isaacs in #20211
  • (e2e) Add op check to waitForTransaction in React Router e2e tests by copilot-swe-agent in #20193
  • (node-integration-tests) Fix flaky kafkajs test race condition by copilot-swe-agent in #20189

Internal Changes 🔧

Ci

  • Remove node-overhead GitHub Action by mydea in #20246
  • Bump dorny/paths-filter from v3.0.1 to v4.0.1 by mydea in #20251
  • Remove codecov steps from jobs that produce no coverage/JUnit data by mydea in #20244

Deps

  • Bump hono from 4.12.7 to 4.12.12 in /dev-packages/e2e-tests/test-applications/cloudflare-hono by dependabot in #20119
  • Bump axios from 1.13.5 to 1.15.0 in /dev-packages/e2e-tests/test-applications/nestjs-basic by dependabot in #20179

Other

  • (bugbot) Add rules to flag test-flake-provoking patterns by Lms24 in #20192
  • (deps-dev) Bump vite from 7.2.0 to 7.3.2 in /dev-packages/e2e-tests/test-applications/tanstackstart-react by dependabot in #20107
  • (react) Remove duplicated test mock by s1gr1d in #20200
  • (size-limit) Bump failing size limit scenario by Lms24 in #20186
  • Fix lint warnings by mydea in #20250
  • Fix flaky ANR test by increasing blocking duration by JPeer264 in #20239
  • Add automatic flaky test detector by nicohrubec in #18684

Other

  • Add a tunnel route helper for TanStack Start React by nikolovlazar in #20264

🤖 This preview updates automatically when you update the PR.

@nikolovlazar nikolovlazar marked this pull request as ready for review April 13, 2026 19:34
Copilot AI review requested due to automatic review settings April 13, 2026 19:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a TanStack Start server-route helper in @sentry/tanstackstart-react to make setting up a Sentry tunnel endpoint a one-liner, with unit tests to validate the POST-only route shape and forwarding behavior.

Changes:

  • Introduce createSentryTunnelRoute which wraps @sentry/core’s handleTunnelRequest for TanStack Start server routes.
  • Export the helper from the package’s server entrypoint.
  • Add unit tests covering the POST-only handler shape and correct request/options forwarding.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
yarn.lock Lockfile updates (not directly related to the new tunnel helper).
packages/tanstackstart-react/src/server/tunnelRoute.ts Adds createSentryTunnelRoute helper wrapping the core tunnel handler.
packages/tanstackstart-react/src/server/index.ts Re-exports createSentryTunnelRoute from the server entrypoint.
packages/tanstackstart-react/test/server/tunnelRoute.test.ts Adds focused unit tests for handler shape and forwarding behavior.

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

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

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

Autofix Details

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

  • ✅ Fixed: Missing integration or E2E test for new feature
    • Added E2E test file (tunnel.test.ts) and tunnel route (api.tunnel.ts) to verify createSentryTunnelRoute functionality in the tanstackstart-react E2E test application.

Create PR

Or push these changes by commenting:

@cursor push b1fe0b60b9
Preview (b1fe0b60b9)
diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/api.tunnel.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/api.tunnel.ts
new file mode 100644
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/api.tunnel.ts
@@ -1,0 +1,8 @@
+import { createFileRoute } from '@tanstack/react-router';
+import { createSentryTunnelRoute } from '@sentry/tanstackstart-react';
+
+export const Route = createFileRoute('/api/tunnel')({
+  server: createSentryTunnelRoute({
+    allowedDsns: [process.env.E2E_TEST_DSN || ''],
+  }),
+});

diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts
new file mode 100644
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts
@@ -1,0 +1,66 @@
+import { expect, test } from '@playwright/test';
+import { waitForError } from '@sentry-internal/test-utils';
+
+test('Tunnel route forwards envelopes to Sentry', async ({ baseURL }) => {
+  const errorEventPromise = waitForError('tanstackstart-react', errorEvent => {
+    return errorEvent?.exception?.values?.[0]?.value === 'Test error for tunnel route';
+  });
+
+  const dsn = process.env.E2E_TEST_DSN || '';
+  const [protocol, rest] = dsn.split('://');
+  const [auth, hostAndPath] = rest.split('@');
+  const [publicKey] = auth.split(':');
+  const [host, ...pathParts] = hostAndPath.split('/');
+  const projectId = pathParts[pathParts.length - 1];
+
+  const envelope = [
+    JSON.stringify({ event_id: crypto.randomUUID(), sent_at: new Date().toISOString() }),
+    JSON.stringify({
+      type: 'event',
+      length: 0,
+    }),
+    JSON.stringify({
+      exception: {
+        values: [
+          {
+            type: 'Error',
+            value: 'Test error for tunnel route',
+          },
+        ],
+      },
+      platform: 'javascript',
+      sdk: {
+        name: 'sentry.javascript.tanstackstart-react',
+        version: '0.0.0',
+      },
+      timestamp: Date.now() / 1000,
+    }),
+  ].join('\n');
+
+  const tunnelUrl = `${baseURL}/api/tunnel`;
+  const sentryUrl = `${protocol}://${host}/api/${projectId}/envelope/`;
+
+  const response = await fetch(tunnelUrl, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/x-sentry-envelope',
+      'X-Sentry-Auth': `Sentry sentry_key=${publicKey}, sentry_version=7`,
+    },
+    body: `${sentryUrl}\n${envelope}`,
+  });
+
+  expect(response.status).toBe(200);
+
+  const errorEvent = await errorEventPromise;
+
+  expect(errorEvent).toMatchObject({
+    exception: {
+      values: [
+        {
+          type: 'Error',
+          value: 'Test error for tunnel route',
+        },
+      ],
+    },
+  });
+});

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

Comment thread packages/tanstackstart-react/test/server/tunnelRoute.test.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.78 kB - -
@sentry/browser - with treeshaking flags 24.27 kB - -
@sentry/browser (incl. Tracing) 42.77 kB - -
@sentry/browser (incl. Tracing, Profiling) 47.4 kB - -
@sentry/browser (incl. Tracing, Replay) 81.69 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71.22 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 86.39 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 98.6 kB - -
@sentry/browser (incl. Feedback) 42.59 kB - -
@sentry/browser (incl. sendFeedback) 30.45 kB - -
@sentry/browser (incl. FeedbackAsync) 35.45 kB - -
@sentry/browser (incl. Metrics) 27.07 kB - -
@sentry/browser (incl. Logs) 27.2 kB - -
@sentry/browser (incl. Metrics & Logs) 27.89 kB - -
@sentry/react 27.53 kB - -
@sentry/react (incl. Tracing) 45.09 kB - -
@sentry/vue 30.61 kB - -
@sentry/vue (incl. Tracing) 44.62 kB - -
@sentry/svelte 25.8 kB - -
CDN Bundle 28.46 kB - -
CDN Bundle (incl. Tracing) 43.82 kB - -
CDN Bundle (incl. Logs, Metrics) 29.83 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.89 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.73 kB - -
CDN Bundle (incl. Tracing, Replay) 80.78 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.83 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 86.31 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 87.34 kB - -
CDN Bundle - uncompressed 83.12 kB - -
CDN Bundle (incl. Tracing) - uncompressed 129.95 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 87.27 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 133.36 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 210.63 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 247.21 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 250.6 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 260.12 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 263.51 kB - -
@sentry/nextjs (client) 47.52 kB - -
@sentry/sveltekit (client) 43.24 kB - -
@sentry/node-core 57.94 kB +0.02% +6 B 🔺
@sentry/node 174.78 kB +0.01% +7 B 🔺
@sentry/node - without tracing 97.89 kB +0.03% +21 B 🔺
@sentry/aws-serverless 115.12 kB +0.01% +8 B 🔺

View base workflow run

Copy link
Copy Markdown
Member

@logaretm logaretm left a comment

Choose a reason for hiding this comment

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

I think it is worth adding an e2e test case for this in one of the tanstack test applications we have.

It will also act as a live way to show how to set it up in an application, I think dev-packages/e2e-tests/test-applications/tanstackstart-react can be a good place for it or maybe a new application if you come across conflicts with pre-existing tests.

@nikolovlazar
Copy link
Copy Markdown
Member Author

nikolovlazar commented Apr 13, 2026

I think it is worth adding an e2e test case for this in one of the tanstack test applications we have.

It will also act as a live way to show how to set it up in an application, I think dev-packages/e2e-tests/test-applications/tanstackstart-react can be a good place for it or maybe a new application if you come across conflicts with pre-existing tests.

yes I did try that, but I needed to rip out the local tunnel that was defined at this file. In order not to risk breaking existing testing functionality, I didn't push the code. Should I create a different tanstack start app instead, @logaretm?

@logaretm
Copy link
Copy Markdown
Member

@nikolovlazar Yep that works, or if you feel extra fancy check my comment here.

…github.com:getsentry/sentry-javascript into lazarnikolov/js-2140-tanstack-start-tunnel-adapter
Copy link
Copy Markdown
Member

@logaretm logaretm left a comment

Choose a reason for hiding this comment

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

LGTM, nice one!

Comment thread packages/tanstackstart-react/src/vite/tunnelRoute.ts
Comment thread packages/tanstackstart-react/src/vite/tunnelRoute.ts
Comment on lines +42 to +44
if (process.env[MANAGED_TUNNEL_ROUTE_PATH_ENV_KEY]) {
return process.env[MANAGED_TUNNEL_ROUTE_PATH_ENV_KEY];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The tunnel route path read from the __SENTRY_TANSTACKSTART_TUNNEL_ROUTE__ environment variable is returned without validation, bypassing checks for invalid characters.
Severity: LOW

Suggested Fix

Validate the path retrieved from the process.env[MANAGED_TUNNEL_ROUTE_PATH_ENV_KEY] environment variable before returning it. The validation should be the same as the one applied to user-provided string paths in validateTunnelRouteOptions.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/tanstackstart-react/src/vite/tunnelRoute.ts#L42-L44

Potential issue: The function `resolveTunnelRoute` checks for the
`__SENTRY_TANSTACKSTART_TUNNEL_ROUTE__` environment variable and, if it's set, returns
its value directly. This bypasses the validation logic in `validateTunnelRouteOptions`
that checks for invalid characters like `?` or `#`. If this internal environment
variable is polluted by an external source (e.g., in a CI environment) with an invalid
path, that invalid path will be injected into the client bundle, potentially breaking
the Sentry tunneling functionality.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

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

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 98202a9. Configure here.

const responseUrl = new URL(response.url());

return responseUrl.origin === pageOrigin && response.request().method() === 'POST';
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Overly broad POST matcher risks test flakiness

Low Severity

The page.waitForResponse filter matches any POST request to the same origin, not specifically the tunnel endpoint. Other same-origin POST requests (e.g., pageload transaction envelopes, server function calls) could satisfy the matcher first, causing the test to assert against the wrong response. Narrowing the filter (e.g., matching on the expected tunnel pathname) would make it more robust.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 98202a9. Configure here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants