Skip to content

Commit 66bcbd7

Browse files
authored
ref(nextjs): Invert serverside injection criteria (#6206)
In nextjs, all non-API pages (other than sometimes `_error`) include the `_app` component. Up until now, we've leveraged this fact when deciding which webpack entrypoints should have `sentry.server.config.js` injected during serverside build; specifically, we inject into `_app`, `_error`, and all API handlers, but not any other non-API pages. This works fine, but it means that if we want to be able to pick and choose _which_ non-API pages get the config file injected, we're out of luck. Either we inject into `_app` (and therefore all non-API pages) or we don't (and therefore no non-API pages). In order to allow selective injection (which will be included in an upcoming PR), this inverts the logic: instead of `_app` being the only non-API page into which we inject, it is now one of the only pages we _don't_ inject into. (The other is `_document`, which plays a similar role as `_app` does.) Given that `_app` and `_document` can't stand on their own (without the context of a page component inside of them), they don't need to have Sentry injected separately. Having it injected into the pages `_app` and `_document` wrap is sufficient. (Note that this change only applies to serverside injection. Client-side, we still only inject into `_app`, as we can't be selective about which pages get instrumented on the front end given all of the global monkeypatching we do.)
1 parent 6627882 commit 66bcbd7

File tree

3 files changed

+67
-27
lines changed

3 files changed

+67
-27
lines changed

packages/nextjs/src/config/webpack.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,37 @@ function checkWebpackPluginOverrides(
380380
* @returns `true` if sentry code should be injected, and `false` otherwise
381381
*/
382382
function shouldAddSentryToEntryPoint(entryPointName: string, isServer: boolean): boolean {
383-
return (
384-
entryPointName === 'pages/_app' ||
385-
(entryPointName.includes('pages/api') && !entryPointName.includes('_middleware')) ||
386-
(isServer && entryPointName === 'pages/_error')
387-
);
383+
// On the server side, by default we inject the `Sentry.init()` code into every page (with a few exceptions).
384+
if (isServer) {
385+
const entryPointRoute = entryPointName.replace(/^pages/, '');
386+
if (
387+
// All non-API pages contain both of these components, and we don't want to inject more than once, so as long as
388+
// we're doing the individual pages, it's fine to skip these. (Note: Even if a given user doesn't have either or
389+
// both of these in their `pages/` folder, they'll exist as entrypoints because nextjs will supply default
390+
// versions.)
391+
entryPointRoute === '/_app' ||
392+
entryPointRoute === '/_document' ||
393+
// While middleware was in beta, it could be anywhere (at any level) in the `pages` directory, and would be called
394+
// `_middleware.js`. Until the SDK runs successfully in the lambda edge environment, we have to exclude these.
395+
entryPointName.includes('_middleware') ||
396+
// Newer versions of nextjs are starting to introduce things outside the `pages/` folder (middleware, an `app/`
397+
// directory, etc), but until those features are stable and we know how we want to support them, the safest bet is
398+
// not to inject anywhere but inside `pages/`.
399+
!entryPointName.startsWith('pages/')
400+
) {
401+
return false;
402+
}
403+
404+
// We want to inject Sentry into all other pages
405+
return true;
406+
}
407+
408+
// On the client side, we only want to inject into `_app`, because that guarantees there'll be only one copy of the
409+
// SDK in the eventual bundle. Since `_app` is the (effectively) the root component for every nextjs app, inclusing
410+
// Sentry there means it will be available for every front end page.
411+
else {
412+
return entryPointName === 'pages/_app';
413+
}
388414
}
389415

390416
/**

packages/nextjs/test/config/fixtures.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ export const serverWebpackConfig: WebpackConfigObject = {
4141
entry: () =>
4242
Promise.resolve({
4343
'pages/_error': 'private-next-pages/_error.js',
44-
'pages/_app': ['./node_modules/smellOVision/index.js', 'private-next-pages/_app.js'],
44+
'pages/_app': 'private-next-pages/_app.js',
45+
'pages/sniffTour': ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'],
4546
'pages/api/_middleware': 'private-next-pages/api/_middleware.js',
4647
'pages/api/simulator/dogStats/[name]': { import: 'private-next-pages/api/simulator/dogStats/[name].js' },
47-
'pages/api/simulator/leaderboard': {
48-
import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/api/simulator/leaderboard.js'],
48+
'pages/simulator/leaderboard': {
49+
import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/simulator/leaderboard.js'],
4950
},
5051
'pages/api/tricks/[trickName]': {
5152
import: 'private-next-pages/api/tricks/[trickName].js',
@@ -64,6 +65,10 @@ export const clientWebpackConfig: WebpackConfigObject = {
6465
main: './src/index.ts',
6566
'pages/_app': 'next-client-pages-loader?page=%2F_app',
6667
'pages/_error': 'next-client-pages-loader?page=%2F_error',
68+
'pages/sniffTour': ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'],
69+
'pages/simulator/leaderboard': {
70+
import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/simulator/leaderboard.js'],
71+
},
6772
}),
6873
output: { filename: 'static/chunks/[name].js', path: '/Users/Maisey/projects/squirrelChasingSimulator/.next' },
6974
target: 'web',

packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts

+28-19
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ describe('constructWebpackConfigFunction()', () => {
102102
'pages/_error': [serverConfigFilePath, 'private-next-pages/_error.js'],
103103

104104
// original entrypoint value is a string array
105-
// (was ['./node_modules/smellOVision/index.js', 'private-next-pages/_app.js'])
106-
'pages/_app': [serverConfigFilePath, './node_modules/smellOVision/index.js', 'private-next-pages/_app.js'],
105+
// (was ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'])
106+
'pages/sniffTour': [
107+
serverConfigFilePath,
108+
'./node_modules/smellOVision/index.js',
109+
'private-next-pages/sniffTour.js',
110+
],
107111

108112
// original entrypoint value is an object containing a string `import` value
109113
// (was { import: 'private-next-pages/api/simulator/dogStats/[name].js' })
@@ -112,12 +116,12 @@ describe('constructWebpackConfigFunction()', () => {
112116
},
113117

114118
// original entrypoint value is an object containing a string array `import` value
115-
// (was { import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/api/simulator/leaderboard.js'] })
116-
'pages/api/simulator/leaderboard': {
119+
// (was { import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/simulator/leaderboard.js'] })
120+
'pages/simulator/leaderboard': {
117121
import: [
118122
serverConfigFilePath,
119123
'./node_modules/dogPoints/converter.js',
120-
'private-next-pages/api/simulator/leaderboard.js',
124+
'private-next-pages/simulator/leaderboard.js',
121125
],
122126
},
123127

@@ -131,7 +135,7 @@ describe('constructWebpackConfigFunction()', () => {
131135
);
132136
});
133137

134-
it('injects user config file into `_app` in both server and client bundles', async () => {
138+
it('injects user config file into `_app` in client bundle but not in server bundle', async () => {
135139
const finalServerWebpackConfig = await materializeFinalWebpackConfig({
136140
exportedNextConfig,
137141
incomingWebpackConfig: serverWebpackConfig,
@@ -145,7 +149,7 @@ describe('constructWebpackConfigFunction()', () => {
145149

146150
expect(finalServerWebpackConfig.entry).toEqual(
147151
expect.objectContaining({
148-
'pages/_app': expect.arrayContaining([serverConfigFilePath]),
152+
'pages/_app': expect.not.arrayContaining([serverConfigFilePath]),
149153
}),
150154
);
151155
expect(finalClientWebpackConfig.entry).toEqual(
@@ -179,7 +183,7 @@ describe('constructWebpackConfigFunction()', () => {
179183
);
180184
});
181185

182-
it('injects user config file into API routes', async () => {
186+
it('injects user config file into both API routes and non-API routes', async () => {
183187
const finalWebpackConfig = await materializeFinalWebpackConfig({
184188
exportedNextConfig,
185189
incomingWebpackConfig: serverWebpackConfig,
@@ -192,13 +196,13 @@ describe('constructWebpackConfigFunction()', () => {
192196
import: expect.arrayContaining([serverConfigFilePath]),
193197
},
194198

195-
'pages/api/simulator/leaderboard': {
196-
import: expect.arrayContaining([serverConfigFilePath]),
197-
},
198-
199199
'pages/api/tricks/[trickName]': expect.objectContaining({
200200
import: expect.arrayContaining([serverConfigFilePath]),
201201
}),
202+
203+
'pages/simulator/leaderboard': {
204+
import: expect.arrayContaining([serverConfigFilePath]),
205+
},
202206
}),
203207
);
204208
});
@@ -218,19 +222,24 @@ describe('constructWebpackConfigFunction()', () => {
218222
);
219223
});
220224

221-
it('does not inject anything into non-_app, non-_error, non-API routes', async () => {
225+
it('does not inject anything into non-_app pages during client build', async () => {
222226
const finalWebpackConfig = await materializeFinalWebpackConfig({
223227
exportedNextConfig,
224228
incomingWebpackConfig: clientWebpackConfig,
225229
incomingWebpackBuildContext: clientBuildContext,
226230
});
227231

228-
expect(finalWebpackConfig.entry).toEqual(
229-
expect.objectContaining({
230-
// no injected file
231-
main: './src/index.ts',
232-
}),
233-
);
232+
expect(finalWebpackConfig.entry).toEqual({
233+
main: './src/index.ts',
234+
// only _app has config file injected
235+
'pages/_app': [clientConfigFilePath, 'next-client-pages-loader?page=%2F_app'],
236+
'pages/_error': 'next-client-pages-loader?page=%2F_error',
237+
'pages/sniffTour': ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'],
238+
'pages/simulator/leaderboard': {
239+
import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/simulator/leaderboard.js'],
240+
},
241+
simulatorBundle: './src/simulator/index.ts',
242+
});
234243
});
235244
});
236245
});

0 commit comments

Comments
 (0)