Repository navigation
feat(nextjs): Add a tunnel route handler that sends only the event payload to Sentry - #25456
Conversation
fb12663 to
de8fd3d
Compare
size-limit report 📦
|
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit de8fd3d. Configure here.
logaretm
left a comment
There was a problem hiding this comment.
Tiny issue and a small FYI, LGTM and looks nice!
| } | ||
|
|
||
| // Like the SDK transports, so that the forward creates no spans, breadcrumbs or trace headers. | ||
| return suppressTracing(() => handleTunnelRequest({ request, allowedDsns })); |
There was a problem hiding this comment.
FYI: This will lose the actual client IP, so relay will think the server is the user. Another issue is it parses the whole payload only to re-serialize it again.
This is pr-existing in the current tunnel utils, I'm handling both concern in my project stack atm so let's keep it. Just an FYI.
Check #25199 for reference.
There was a problem hiding this comment.
Good point, thanks! I'll leave this as-is here so you can handle this in your stack.
| projectDir: string = process.cwd(), | ||
| ): string | undefined { | ||
| // Like Next.js, ignore `src/app` if there is a root `app` directory. | ||
| const appDir = ['app', 'src/app'].find(dir => getStats(path.join(projectDir, dir))?.isDirectory()); |
There was a problem hiding this comment.
If the root has pages/ and the handler is in src/app/sentry-tunnel/route.ts, this picks src/app and enables handler mode.
Next.js ignores src/ when app or pages exists in the root, so /sentry-tunnel 404s and all events are lost. Checking for a root pages too fixes it:
| const appDir = ['app', 'src/app'].find(dir => getStats(path.join(projectDir, dir))?.isDirectory()); | |
| const hasRootDir = ['app', 'pages'].some(dir => getStats(path.join(projectDir, dir))?.isDirectory()); | |
| const appDir = hasRootDir ? 'app' : 'src/app'; |
There was a problem hiding this comment.
I tested this with a root pages/ and src/app/sentry-tunnel/route.ts on Next 14.2 and 15.5 (webpack/Turbopack, build/dev). /sentry-tunnel is served from src/app, and the root pages still work.
The docs say:
src/apporsrc/pageswill be ignored if app or pages are present in the root directory.
But the code resolves pages and app separately (find-pages-dir): a root pages/ hides only src/pages/, and a root app/ hides only src/app/. Since Next 16, this mixed layout fails the build anyway (vercel/next.js#83416).
With the suggestion, the SDK wouldn't detect the file in those 14/15 apps, so handler mode would quietly stay off and the tunnel would keep forwarding all headers. So I kept the detection and added a test for this case.
…route-handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…route-handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tunnel route handlers (`@sentry/nextjs/tunnel` and TanStack Start's `createSentryTunnelRoute`) now check the request size before they read the body. A request larger than 20 MiB gets a 413, and a request without `Content-Length` gets a 411. So one request can no longer make the server hold more than 20 MiB in memory. SDK envelopes are much smaller than this limit, so normal traffic does not change. Checking the header is enough, because the HTTP server stops reading the body at `Content-Length`. Stacked on #25456.
…t payload to Sentry (#25559) Backport of: #25456 ## Differences to the original PR - `src/server/index.ts`, `src/edge/index.ts`: adds `getTunnelRouteIgnoreSpans()` to the local `nextjsIgnoreSpans` arrays, because v10 has no `NEXTJS_SERVER_IGNORE_SPANS`. - `src/config/withSentryConfig/getFinalConfigObjectUtils.ts`: logs with `console.log` / `console.warn` and skips the output with `silent`, because v10 has no `getBuildLogger` (#23621). - `src/common/utils/dropMiddlewareTunnelRequests.ts`, `src/common/utils/tunnelPathnameMatch.ts`: keeps the v10 code (`isSentryRequestSpan`, `isSentryTunnelRequest`) and adds only the new functions. The middleware skip of v10 does not change: in handler mode the client posts to the bare path, so the skip does not apply and the user's middleware runs, as on v11. - `src/common/wrapMiddlewareWithSentry.ts` (v10 only): on Next.js 13, the wrapper starts the middleware span itself, and the span has no request target, so the new `ignoreSpans` entries cannot match it. In handler mode, the wrapper now sets `sentry.drop_transaction` on this span for tunnel requests. The middleware still runs. - `src/config/types.ts`: the middleware paragraph starts with "With this handler", because in rewrite mode v10 still skips the middleware for tunnel requests. - E2E `nextjs-16-tunnel`: the handler test without server spans checks transactions, because v10 uses the static lifecycle by default. `@sentry/conventions` is `0.16.0`, the version of v10. Playwright and `@sentry-internal/test-utils` keep the v10 versions. - `CHANGELOG.md`: the entry is under "Unreleased", with one more sentence about middleware.

Today,
tunnelRouterewrites tunnel requests to Sentry as a proxy. This PR adds an opt-in handler mode: ifapp/sentry-tunnel/route.ts(orsrc/app/sentry-tunnel/route.ts) exists, the SDK rewrites the tunnel route internally to it, and the handler sends only the event payload to Sentry. Without the file, nothing changes.tunnelRouteoption, a warning is shown.beforeFiles, so a public file or page at the tunnel path cannot shadow it.createSentryTunnelRoute({ allowedDsns })which covers other DSNs.Tested with unit tests, two new
nextjs-16-tunnelvariants (Turbopack/Node.js, webpack/edge) that also check the forwarded headers.Also tested with a manual Vercel deploy on Next.js 14-16.
Fixes JS-3927