Skip to content

Commit 57256ad

Browse files
authored
feat(nextjs): Add option for auto-generated random tunnel route (#16626)
This PR adds support for auto-generated tunnel paths to avoid ad-blocker detection. - Random paths are less likely to be blocked than `/monitoring` - Users can just set `tunnelRoute: true` for an auto-generated route - Existing configs will still work **Note that every build will now create a different random path which should be quite unpredictable for ad-blockers**
1 parent ceb003c commit 57256ad

File tree

4 files changed

+65
-4
lines changed

4 files changed

+65
-4
lines changed

packages/nextjs/src/config/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,12 @@ export type SentryBuildOptions = {
426426
* Tunnel Sentry requests through this route on the Next.js server, to circumvent ad-blockers blocking Sentry events
427427
* from being sent. This option should be a path (for example: '/error-monitoring').
428428
*
429+
* - Pass `true` to auto-generate a random, ad-blocker-resistant route for each build
430+
* - Pass a string path (e.g., '/monitoring') to use a custom route
431+
*
429432
* NOTE: This feature only works with Next.js 11+
430433
*/
431-
tunnelRoute?: string;
434+
tunnelRoute?: string | boolean;
432435

433436
/**
434437
* Tree shakes Sentry SDK logger statements from the bundle.

packages/nextjs/src/config/webpack.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,9 @@ function addValueInjectionLoader(
692692
const isomorphicValues = {
693693
// `rewritesTunnel` set by the user in Next.js config
694694
_sentryRewritesTunnelPath:
695-
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
695+
userSentryOptions.tunnelRoute !== undefined &&
696+
userNextConfig.output !== 'export' &&
697+
typeof userSentryOptions.tunnelRoute === 'string'
696698
? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}`
697699
: undefined,
698700

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ export function withSentryConfig<C>(nextConfig?: C, sentryBuildOptions: SentryBu
7575
}
7676
}
7777

78+
/**
79+
* Generates a random tunnel route path that's less likely to be blocked by ad-blockers
80+
*/
81+
function generateRandomTunnelRoute(): string {
82+
// Generate a random 8-character alphanumeric string
83+
const randomString = Math.random().toString(36).substring(2, 10);
84+
return `/${randomString}`;
85+
}
86+
7887
// Modify the materialized object form of the user's next config by deleting the `sentry` property and wrapping the
7988
// `webpack` property
8089
function getFinalConfigObject(
@@ -93,7 +102,14 @@ function getFinalConfigObject(
93102
);
94103
}
95104
} else {
96-
setUpTunnelRewriteRules(incomingUserNextConfigObject, userSentryOptions.tunnelRoute);
105+
const resolvedTunnelRoute =
106+
typeof userSentryOptions.tunnelRoute === 'boolean'
107+
? generateRandomTunnelRoute()
108+
: userSentryOptions.tunnelRoute;
109+
110+
// Update the global options object to use the resolved value everywhere
111+
userSentryOptions.tunnelRoute = resolvedTunnelRoute;
112+
setUpTunnelRewriteRules(incomingUserNextConfigObject, resolvedTunnelRoute);
97113
}
98114
}
99115

@@ -363,8 +379,11 @@ function setUpBuildTimeVariables(
363379
): void {
364380
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
365381
const basePath = userNextConfig.basePath ?? '';
382+
366383
const rewritesTunnelPath =
367-
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
384+
userSentryOptions.tunnelRoute !== undefined &&
385+
userNextConfig.output !== 'export' &&
386+
typeof userSentryOptions.tunnelRoute === 'string'
368387
? `${basePath}${userSentryOptions.tunnelRoute}`
369388
: undefined;
370389

packages/nextjs/test/utils/tunnelRoute.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,40 @@ describe('applyTunnelRouteOption()', () => {
8181
expect(options.tunnel).toBe('/my-error-monitoring-route?o=2222222&p=3333333&r=us');
8282
});
8383
});
84+
85+
describe('Random tunnel route generation', () => {
86+
it('Works when tunnelRoute is true and generates random-looking paths', () => {
87+
globalWithInjectedValues._sentryRewritesTunnelPath = '/abc123def'; // Simulated random path
88+
const options: any = {
89+
dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333',
90+
} as BrowserOptions;
91+
92+
applyTunnelRouteOption(options);
93+
94+
expect(options.tunnel).toBe('/abc123def?o=2222222&p=3333333');
95+
expect(options.tunnel).toMatch(/^\/[a-z0-9]+\?o=2222222&p=3333333$/);
96+
});
97+
98+
it('Works with region DSNs when tunnelRoute is true', () => {
99+
globalWithInjectedValues._sentryRewritesTunnelPath = '/x7h9k2m'; // Simulated random path
100+
const options: any = {
101+
dsn: 'https://11111111111111111111111111111111@o2222222.ingest.eu.sentry.io/3333333',
102+
} as BrowserOptions;
103+
104+
applyTunnelRouteOption(options);
105+
106+
expect(options.tunnel).toBe('/x7h9k2m?o=2222222&p=3333333&r=eu');
107+
expect(options.tunnel).toMatch(/^\/[a-z0-9]+\?o=2222222&p=3333333&r=eu$/);
108+
});
109+
110+
it('Does not apply tunnel when tunnelRoute is false', () => {
111+
globalWithInjectedValues._sentryRewritesTunnelPath = undefined;
112+
const options: any = {
113+
dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333',
114+
} as BrowserOptions;
115+
116+
applyTunnelRouteOption(options);
117+
118+
expect(options.tunnel).toBeUndefined();
119+
});
120+
});

0 commit comments

Comments
 (0)