Tunnel route helper + Dynamic tunnel route generator for TanStack Start React#20264
Tunnel route helper + Dynamic tunnel route generator for TanStack Start React#20264nikolovlazar wants to merge 11 commits intodevelopfrom
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
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 5a2c6e9. Configure here.
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.
| * | ||
| * @default true | ||
| */ | ||
| tunnel?: true | string; |
There was a problem hiding this comment.
might be a bit nitpicky but tunnelRoute.tunnel or even tunnelRoute.route feels a bit weird to read.
Might be friday brain but what do you think if:
{
// random tunnel, all DSNs allowed...
tunnelRoute: true,
tunnelRoute: {
// random tunnel, specific DSNs
allowedDsns: ['whatever']
},
// static tunnel route
tunnelRoute: '/static',
// both options...
tunnelRoute: {
allowedDsns: ['whatever']
path: '/static'
}
}It might be confusing but I personally like reductive options like this because it adapts to the user's own app complexity and reduces the args they need to pass.
| /** | ||
| * A list of DSNs that are allowed to use the managed tunnel route. | ||
| */ | ||
| allowedDsns: string[]; |
There was a problem hiding this comment.
This should be optional, otherwise the user must configure it. Is that intended? it probably can be defaulted to the user's current DSN if we can detect that.
| function validateTunnelRouteOptions(options: TunnelRouteOptions): string { | ||
| if (options.allowedDsns.length === 0) { | ||
| throw new Error( | ||
| "[@sentry/tanstackstart-react] `sentryTanstackStart({ tunnelRoute })` requires at least one allowed DSN.", |
There was a problem hiding this comment.
Ah so it is required, could we detect the user DSN?
| const importMatches = [...source.matchAll(/^import .+$/gm)]; | ||
| const lastImport = importMatches.at(-1); | ||
|
|
||
| if (!lastImport || lastImport.index === undefined) { |
There was a problem hiding this comment.
Should be fixed, you can yarn lint to find these locally.
| /const rootRouteChildren(?:\s*:\s*RootRouteChildren)?\s*=\s*\{/, | ||
| ); | ||
|
|
||
| if (!rootRouteChildrenMatch || rootRouteChildrenMatch.index === undefined) { |


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)