Tunnel route helper + Dynamic tunnel route generator for TanStack Start React#20264
Conversation
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Cloudflare
Core
Deps
Other
Bug Fixes 🐛Deno
Other
Internal Changes 🔧Ci
Deps
Other
Other
🤖 This preview updates automatically when you update the PR. |
There was a problem hiding this comment.
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
createSentryTunnelRoutewhich wraps@sentry/core’shandleTunnelRequestfor 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.
There was a problem hiding this comment.
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.
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.
size-limit report 📦
|
logaretm
left a comment
There was a problem hiding this comment.
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? |
|
@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
logaretm
left a comment
There was a problem hiding this comment.
Some ideas and some linting issues to go through but I think this is looking good and would be very valuable once its out.
Made-with: Cursor
Align sentryTanstackStart tests with TunnelRouteOptions.path and annotate spy case with SentryTanstackStartOptions so options match the public API. Made-with: Cursor
Made-with: Cursor
…r quote styles Made-with: Cursor
logaretm
left a comment
There was a problem hiding this comment.
Minor lint thing, looks good otherwise 👍 🏁
Use optional chaining on `.length` to collapse the redundant existence + length-guard ternary into a single expression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9e04b81. Configure here.
| ? responseUrl.pathname === expectedTunnelPathMatcher | ||
| : expectedTunnelPathMatcher.test(responseUrl.pathname)) | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Tunnel test may capture unrelated Sentry request
Low Severity
The page.waitForResponse matcher accepts any POST to the tunnel path, but the Sentry SDK may send other events through the tunnel (e.g., pageload transactions, session data) before the click triggers the error event. The first matching response could be for a different event type, meaning the tunnel-response assertions validate a different request than the error. While the error event is independently awaited via waitForError, this means the test doesn't truly verify that the error event specifically transited the tunnel — it only proves some event did and the error arrived separately. This is a race condition between the pageload transaction POST and the error POST on the same tunnel endpoint.
Triggered by project rule: PR Review Guidelines for Cursor Bot
Reviewed by Cursor Bugbot for commit 9e04b81. Configure here.
Covers the `{ path, allowedDsns }` shape of `tunnelRoute` end-to-end
alongside the existing dynamic/static/custom variants, exercising the
build-time serialization of `allowedDsns` into the generated virtual
route module. Also refactors the `tunnelRoute` resolution in
`vite.config.ts` from a nested ternary to a switch for readability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tunnel variants run independently through sentryTest.variants in CI, so the chained `test:assert` was redundant and slow to run locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>



Summary
createSentryTunnelRouteto@sentry/tanstackstart-reactas a one-line server route adapter around the core tunnel handlersentryTanstackStart()(the existing Vite integration) with a newtunnelRouteoption which can automatically register a same-origin tunnel route and set the clienttunneloption to point to ittunnelRoutevariants: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 pathtunnel: '/custom-path'): uses an explicit fixed tunnel route pathNotes
yarn.lockcontains unrelated changes and should be reviewed separatelyTesting
cd packages/tanstackstart-react && yarn test/monitorreturns200and 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:Examples
createSentryTunnelRoute(manual server route)sentryTanstackStart({ tunnelRoute })(managed tunnel route via Vite plugin)