Skip to content

Commit 656017a

Browse files
authored
fix(nextjs): Inject sentry.x.config.js into pages/_error (#4397)
There are certain situations where `_error` is loaded before `_app` has had a chance to run. In those cases, we miss errors caught in `_error`, because the SDK has not yet initialized (which currently happens the first time `_app` runs). This injects the initialization code into `_error` as well, to avoid such problems. (In order to not add extra code to the browser bundle, and because this has only shown up as a problem on the server side, for the moment this only does the injection when building the server bundle.) Fixes #4168.
1 parent e92ad68 commit 656017a

File tree

2 files changed

+38
-8
lines changed

2 files changed

+38
-8
lines changed

packages/nextjs/src/config/webpack.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ async function addSentryToEntryProperty(
144144

145145
// inject into all entry points which might contain user's code
146146
for (const entryPointName in newEntryProperty) {
147-
if (shouldAddSentryToEntryPoint(entryPointName)) {
147+
if (shouldAddSentryToEntryPoint(entryPointName, isServer)) {
148148
addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject);
149149
}
150150
}
@@ -251,10 +251,15 @@ function checkWebpackPluginOverrides(
251251
* Determine if this is an entry point into which both `Sentry.init()` code and the release value should be injected
252252
*
253253
* @param entryPointName The name of the entry point in question
254+
* @param isServer Whether or not this function is being called in the context of a server build
254255
* @returns `true` if sentry code should be injected, and `false` otherwise
255256
*/
256-
function shouldAddSentryToEntryPoint(entryPointName: string): boolean {
257-
return entryPointName === 'pages/_app' || entryPointName.includes('pages/api');
257+
function shouldAddSentryToEntryPoint(entryPointName: string, isServer: boolean): boolean {
258+
return (
259+
entryPointName === 'pages/_app' ||
260+
entryPointName.includes('pages/api') ||
261+
(isServer && entryPointName === 'pages/_error')
262+
);
258263
}
259264

260265
/**
@@ -295,7 +300,7 @@ export function getWebpackPluginOptions(
295300
configFile: hasSentryProperties ? 'sentry.properties' : undefined,
296301
stripPrefix: ['webpack://_N_E/'],
297302
urlPrefix,
298-
entries: shouldAddSentryToEntryPoint,
303+
entries: (entryPointName: string) => shouldAddSentryToEntryPoint(entryPointName, isServer),
299304
release: getSentryRelease(buildId),
300305
dryRun: isDev,
301306
});

packages/nextjs/test/config.test.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const defaultsObject = { defaultConfig: {} as NextConfigObject };
9090
const serverWebpackConfig = {
9191
entry: () =>
9292
Promise.resolve({
93-
'pages/api/dogs/[name]': 'private-next-pages/api/dogs/[name].js',
93+
'pages/_error': 'private-next-pages/_error.js',
9494
'pages/_app': ['./node_modules/smellOVision/index.js', 'private-next-pages/_app.js'],
9595
'pages/api/simulator/dogStats/[name]': { import: 'private-next-pages/api/simulator/dogStats/[name].js' },
9696
'pages/api/simulator/leaderboard': {
@@ -111,6 +111,7 @@ const clientWebpackConfig = {
111111
Promise.resolve({
112112
main: './src/index.ts',
113113
'pages/_app': 'next-client-pages-loader?page=%2F_app',
114+
'pages/_error': 'next-client-pages-loader?page=%2F_error',
114115
}),
115116
output: { filename: 'static/chunks/[name].js', path: '/Users/Maisey/projects/squirrelChasingSimulator/.next' },
116117
target: 'web',
@@ -316,8 +317,8 @@ describe('webpack config', () => {
316317
expect(finalWebpackConfig.entry).toEqual(
317318
expect.objectContaining({
318319
// original entrypoint value is a string
319-
// (was 'private-next-pages/api/dogs/[name].js')
320-
'pages/api/dogs/[name]': [rewriteFramesHelper, serverConfigFilePath, 'private-next-pages/api/dogs/[name].js'],
320+
// (was 'private-next-pages/_error.js')
321+
'pages/_error': [rewriteFramesHelper, serverConfigFilePath, 'private-next-pages/_error.js'],
321322

322323
// original entrypoint value is a string array
323324
// (was ['./node_modules/smellOVision/index.js', 'private-next-pages/_app.js'])
@@ -379,6 +380,30 @@ describe('webpack config', () => {
379380
);
380381
});
381382

383+
it('injects user config file into `_error` in server bundle but not client bundle', async () => {
384+
const finalServerWebpackConfig = await materializeFinalWebpackConfig({
385+
userNextConfig,
386+
incomingWebpackConfig: serverWebpackConfig,
387+
incomingWebpackBuildContext: serverBuildContext,
388+
});
389+
const finalClientWebpackConfig = await materializeFinalWebpackConfig({
390+
userNextConfig,
391+
incomingWebpackConfig: clientWebpackConfig,
392+
incomingWebpackBuildContext: clientBuildContext,
393+
});
394+
395+
expect(finalServerWebpackConfig.entry).toEqual(
396+
expect.objectContaining({
397+
'pages/_error': expect.arrayContaining([serverConfigFilePath]),
398+
}),
399+
);
400+
expect(finalClientWebpackConfig.entry).toEqual(
401+
expect.objectContaining({
402+
'pages/_error': expect.not.arrayContaining([clientConfigFilePath]),
403+
}),
404+
);
405+
});
406+
382407
it('injects user config file into API routes', async () => {
383408
const finalWebpackConfig = await materializeFinalWebpackConfig({
384409
userNextConfig,
@@ -403,7 +428,7 @@ describe('webpack config', () => {
403428
);
404429
});
405430

406-
it('does not inject anything into non-_app, non-API routes', async () => {
431+
it('does not inject anything into non-_app, non-_error, non-API routes', async () => {
407432
const finalWebpackConfig = await materializeFinalWebpackConfig({
408433
userNextConfig,
409434
incomingWebpackConfig: clientWebpackConfig,

0 commit comments

Comments
 (0)