Skip to content

Commit 9e27723

Browse files
author
Luca Forstner
committed
feat(nextjs): Add option to automatically tunnel events
1 parent 711bb7d commit 9e27723

File tree

4 files changed

+104
-5
lines changed

4 files changed

+104
-5
lines changed

packages/nextjs/src/config/types.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@ export type NextConfigFunctionWithSentry = (
2626
defaults: { defaultConfig: NextConfigObject },
2727
) => NextConfigObjectWithSentry;
2828

29+
// Vendored from Next.js
30+
type RouteHas =
31+
| {
32+
type: 'header' | 'query' | 'cookie';
33+
key: string;
34+
value?: string;
35+
}
36+
| {
37+
type: 'host';
38+
key?: undefined;
39+
value: string;
40+
};
41+
42+
// Vendored from Next.js
43+
type Rewrite = {
44+
source: string;
45+
destination: string;
46+
basePath?: false;
47+
locale?: false;
48+
has?: RouteHas[];
49+
};
50+
2951
export type NextConfigObject = {
3052
// Custom webpack options
3153
webpack?: WebpackConfigFunction | null;
@@ -41,6 +63,15 @@ export type NextConfigObject = {
4163
publicRuntimeConfig?: { [key: string]: unknown };
4264
// File extensions that count as pages in the `pages/` directory
4365
pageExtensions?: string[];
66+
// TODO
67+
rewrites?: () => Promise<
68+
| Rewrite[]
69+
| {
70+
beforeFiles: Rewrite[];
71+
afterFiles: Rewrite[];
72+
fallback: Rewrite[];
73+
}
74+
>;
4475
};
4576

4677
export type UserSentryOptions = {
@@ -74,6 +105,9 @@ export type UserSentryOptions = {
74105
// (`pages/animals/index.js` or `.\src\pages\api\animals\[animalType]\habitat.tsx`), and strings must be be a full,
75106
// exact match.
76107
excludeServerRoutes?: Array<RegExp | string>;
108+
109+
// TODO
110+
rewritesTunnel?: string;
77111
};
78112

79113
export type NextConfigFunction = (phase: string, defaults: { defaultConfig: NextConfigObject }) => NextConfigObject;

packages/nextjs/src/config/webpack.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin';
1313
import * as chalk from 'chalk';
1414
import * as fs from 'fs';
1515
import * as path from 'path';
16+
import * as webpack from 'webpack';
1617

1718
// Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our
1819
// circular dependency check thinks this file is importing from itself. See https://github.com/pahen/madge/issues/306.
@@ -201,6 +202,15 @@ export function constructWebpackConfigFunction(
201202
);
202203
}
203204

205+
if (userSentryOptions.rewritesTunnel) {
206+
newConfig.plugins = newConfig.plugins || [];
207+
newConfig.plugins.push(
208+
new webpack.DefinePlugin({
209+
__SENTRY_REWRITES_TUNNEL_PATH__: JSON.stringify(userSentryOptions.rewritesTunnel),
210+
}),
211+
);
212+
}
213+
204214
return newConfig;
205215
};
206216
}

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ function getFinalConfigObject(
4040
// to `constructWebpackConfigFunction` so that it can live in the returned function's closure.
4141
const { sentry: userSentryOptions } = incomingUserNextConfigObject;
4242
delete incomingUserNextConfigObject.sentry;
43-
// Remind TS that there's now no `sentry` property
44-
const userNextConfigObject = incomingUserNextConfigObject as NextConfigObject;
43+
44+
if (userSentryOptions?.rewritesTunnel) {
45+
setUpRewriteRules(incomingUserNextConfigObject, userSentryOptions.rewritesTunnel);
46+
}
4547

4648
// In order to prevent all of our build-time code from being bundled in people's route-handling serverless functions,
4749
// we exclude `webpack.ts` and all of its dependencies from nextjs's `@vercel/nft` filetracing. We therefore need to
@@ -50,11 +52,52 @@ function getFinalConfigObject(
5052
// eslint-disable-next-line @typescript-eslint/no-var-requires
5153
const { constructWebpackConfigFunction } = require('./webpack');
5254
return {
53-
...userNextConfigObject,
54-
webpack: constructWebpackConfigFunction(userNextConfigObject, userSentryWebpackPluginOptions, userSentryOptions),
55+
...incomingUserNextConfigObject,
56+
webpack: constructWebpackConfigFunction(
57+
incomingUserNextConfigObject,
58+
userSentryWebpackPluginOptions,
59+
userSentryOptions,
60+
),
5561
};
5662
}
5763

5864
// At runtime, we just return the user's config untouched.
59-
return userNextConfigObject;
65+
return incomingUserNextConfigObject;
66+
}
67+
68+
/**
69+
* TODO
70+
*/
71+
function setUpRewriteRules(userNextConfig: NextConfigObject, tunnelPath: string): void {
72+
const originalRewrites = userNextConfig.rewrites;
73+
74+
// This function doesn't take any arguments at the time of writing but we future-proof
75+
// here in case Next.js ever decides to pass some
76+
userNextConfig.rewrites = async (...args: unknown[]) => {
77+
// TODO: Consider trailing slash option
78+
79+
const injectedRewrites = [
80+
{
81+
source: `${tunnelPath}/:orgid/:projectid`, // without slash
82+
destination: 'https://:orgid.ingest.sentry.io/api/:projectid/envelope/',
83+
},
84+
{
85+
source: `${tunnelPath}/:orgid/:projectid/`, // with slash
86+
destination: 'https://:orgid.ingest.sentry.io/api/:projectid/envelope/',
87+
},
88+
];
89+
90+
if (typeof originalRewrites === 'function') {
91+
// @ts-ignore weird args error
92+
const intermediaryValue = await originalRewrites(...args);
93+
if (Array.isArray(intermediaryValue)) {
94+
intermediaryValue.push(...injectedRewrites);
95+
} else {
96+
intermediaryValue.beforeFiles.push(...injectedRewrites);
97+
}
98+
return intermediaryValue;
99+
} else {
100+
return injectedRewrites;
101+
}
102+
};
60103
}

packages/nextjs/src/index.client.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { RewriteFrames } from '@sentry/integrations';
22
import { configureScope, init as reactInit, Integrations } from '@sentry/react';
33
import { BrowserTracing, defaultRequestInstrumentationOptions, hasTracingEnabled } from '@sentry/tracing';
44
import { EventProcessor } from '@sentry/types';
5+
import { dsnFromString } from '@sentry/utils';
56

67
import { nextRouterInstrumentation } from './performance/client';
78
import { buildMetadata } from './utils/metadata';
@@ -29,10 +30,21 @@ export { BrowserTracing };
2930
// Treeshakable guard to remove all code related to tracing
3031
declare const __SENTRY_TRACING__: boolean;
3132

33+
// Variable that is injected at build-time to have an automatic tunneling setup that uses Next.js' rewrites option
34+
declare const __SENTRY_REWRITES_TUNNEL_PATH__: string | undefined;
35+
3236
type GlobalWithAssetPrefixPath = typeof global & { __rewriteFramesAssetPrefixPath__: string };
3337

3438
/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
3539
export function init(options: NextjsOptions): void {
40+
if (typeof __SENTRY_REWRITES_TUNNEL_PATH__ === 'string' && options.dsn) {
41+
const dsnComponents = dsnFromString(options.dsn);
42+
if (dsnComponents.host.match(/^o\d+\.ingest.sentry.io$/)) {
43+
const orgId = dsnComponents.host.split('.')[0];
44+
options.tunnel = `${__SENTRY_REWRITES_TUNNEL_PATH__}/${orgId}/${dsnComponents.projectId}/`;
45+
}
46+
}
47+
3648
buildMetadata(options, ['nextjs', 'react']);
3749
options.environment = options.environment || process.env.NODE_ENV;
3850
addClientIntegrations(options);

0 commit comments

Comments
 (0)