Skip to content

Commit f411999

Browse files
authored
fix(nextjs): Don't run webpack plugin on non-prod Vercel deployments (#5603)
When someone adds our Vercel integration, it only adds sentry-cli-relevant environment variables to production deployments. This can cause non-production deployments to fail, because when the webpack loader tries to run, it doesn't find the information it needs. That said, there's a reason we only set the env variables for prod - we really shouldn't be creating releases or uploading sourcemaps for dev or preview deployments in any case. This adds an environment check for vercel deployments (using [the `VERCEL_ENV` env variable](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables)), and only enables the webpack plugin if the environment is `production`. To give users a way to override this, setting `disableClientWebpackPlugin` or `disableServerWebpackPlugin` to `false` now takes precedence over other checks, rather than being a no-op. Finally, given that the number of enablement checks is likely to grow, this pulls them into a separate function.
1 parent 5c462f1 commit f411999

File tree

5 files changed

+165
-47
lines changed

5 files changed

+165
-47
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
This release adds an environment check in `@sentry/nextjs` for Vercel deployments (using the `VERCEL_ENV` env variable), and only enables `SentryWebpackPlugin` if the environment is `production`. To override this, [setting `disableClientWebpackPlugin` or `disableServerWebpackPlugin` to `false`](https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#disable-sentrywebpackplugin) now takes precedence over other checks, rather than being a no-op. Note: Overriding this is not recommended! It can increase build time and clog Release Health data in Sentry with inaccurate noise.
8+
9+
- fix(nextjs): Don't run webpack plugin on non-prod Vercel deployments (#5603)
10+
711
## 7.11.1
812

913
- fix(remix): Store transaction on express req (#5595)

packages/nextjs/src/config/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export type NextConfigObject = {
3535
};
3636

3737
export type UserSentryOptions = {
38+
// Override the SDK's default decision about whether or not to enable to the webpack plugin. Note that `false` forces
39+
// the plugin to be enabled, even in situations where it's not recommended.
3840
disableServerWebpackPlugin?: boolean;
3941
disableClientWebpackPlugin?: boolean;
4042
hideSourceMaps?: boolean;

packages/nextjs/src/config/webpack.ts

+45-11
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,7 @@ export function constructWebpackConfigFunction(
134134
newConfig.entry = async () => addSentryToEntryProperty(origEntryProperty, buildContext);
135135

136136
// Enable the Sentry plugin (which uploads source maps to Sentry when not in dev) by default
137-
const enableWebpackPlugin =
138-
// TODO: this is a hack to fix https://github.com/getsentry/sentry-cli/issues/1085, which is caused by
139-
// https://github.com/getsentry/sentry-cli/issues/915. Once the latter is addressed, this existence check can come
140-
// out. (The check is necessary because currently, `@sentry/cli` uses a post-install script to download an
141-
// architecture-specific version of the `sentry-cli` binary. If `yarn install`, `npm install`, or `npm ci` are run
142-
// with the `--ignore-scripts` option, this will be blocked and the missing binary will cause an error when users
143-
// try to build their apps.)
144-
ensureCLIBinaryExists() &&
145-
(isServer ? !userSentryOptions.disableServerWebpackPlugin : !userSentryOptions.disableClientWebpackPlugin);
146-
147-
if (enableWebpackPlugin) {
137+
if (shouldEnableWebpackPlugin(buildContext, userSentryOptions)) {
148138
// TODO Handle possibility that user is using `SourceMapDevToolPlugin` (see
149139
// https://webpack.js.org/plugins/source-map-dev-tool-plugin/)
150140

@@ -460,3 +450,47 @@ function ensureCLIBinaryExists(): boolean {
460450
}
461451
return false;
462452
}
453+
454+
/** Check various conditions to decide if we should run the plugin */
455+
function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions: UserSentryOptions): boolean {
456+
const { isServer, dev: isDev } = buildContext;
457+
const { disableServerWebpackPlugin, disableClientWebpackPlugin } = userSentryOptions;
458+
459+
/** Non-negotiable */
460+
461+
// TODO: this is a hack to fix https://github.com/getsentry/sentry-cli/issues/1085, which is caused by
462+
// https://github.com/getsentry/sentry-cli/issues/915. Once the latter is addressed, this existence check can come
463+
// out. (The check is necessary because currently, `@sentry/cli` uses a post-install script to download an
464+
// architecture-specific version of the `sentry-cli` binary. If `yarn install`, `npm install`, or `npm ci` are run
465+
// with the `--ignore-scripts` option, this will be blocked and the missing binary will cause an error when users
466+
// try to build their apps.)
467+
if (!ensureCLIBinaryExists()) {
468+
return false;
469+
}
470+
471+
/** User override */
472+
473+
if (isServer && disableServerWebpackPlugin !== undefined) {
474+
return !disableServerWebpackPlugin;
475+
} else if (!isServer && disableClientWebpackPlugin !== undefined) {
476+
return !disableClientWebpackPlugin;
477+
}
478+
479+
/** Situations where the default is to disable the plugin */
480+
481+
// TODO: Are there analogs to Vercel's preveiw and dev modes on other deployment platforms?
482+
483+
if (isDev || process.env.NODE_ENV === 'development') {
484+
// TODO (v8): Right now in dev we set the plugin to dryrun mode, and our boilerplate includes setting the plugin to
485+
// `silent`, so for the vast majority of users, it's as if the plugin doesn't run at all in dev. Making that
486+
// official is technically a breaking change, though, so we probably should wait until v8.
487+
// return false
488+
}
489+
490+
if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') {
491+
return false;
492+
}
493+
494+
// We've passed all of the tests!
495+
return true;
496+
}

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
serverWebpackConfig,
1313
userNextConfig,
1414
} from '../fixtures';
15-
import { materializeFinalWebpackConfig } from '../testUtils';
15+
import { materializeFinalNextConfig, materializeFinalWebpackConfig } from '../testUtils';
1616

1717
describe('constructWebpackConfigFunction()', () => {
1818
it('includes expected properties', async () => {
@@ -47,6 +47,17 @@ describe('constructWebpackConfigFunction()', () => {
4747
expect(finalWebpackConfig).toEqual(expect.objectContaining(materializedUserWebpackConfig));
4848
});
4949

50+
it("doesn't set devtool if webpack plugin is disabled", () => {
51+
const finalNextConfig = materializeFinalNextConfig({
52+
...exportedNextConfig,
53+
webpack: () => ({ devtool: 'something-besides-source-map' } as any),
54+
sentry: { disableServerWebpackPlugin: true },
55+
});
56+
const finalWebpackConfig = finalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext);
57+
58+
expect(finalWebpackConfig?.devtool).not.toEqual('source-map');
59+
});
60+
5061
it('allows for the use of `hidden-source-map` as `devtool` value for client-side builds', async () => {
5162
const exportedNextConfigHiddenSourceMaps = {
5263
...exportedNextConfig,

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

+102-35
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as fs from 'fs';
22
import * as os from 'os';
33
import * as path from 'path';
44

5-
import { BuildContext } from '../../../src/config/types';
5+
import { BuildContext, ExportedNextConfig } from '../../../src/config/types';
66
import { getUserConfigFile, getWebpackPluginOptions, SentryWebpackPlugin } from '../../../src/config/webpack';
77
import {
88
clientBuildContext,
@@ -14,7 +14,7 @@ import {
1414
userSentryWebpackPluginConfig,
1515
} from '../fixtures';
1616
import { exitsSync, mkdtempSyncSpy, mockExistsSync, realExistsSync } from '../mocks';
17-
import { findWebpackPlugin, materializeFinalNextConfig, materializeFinalWebpackConfig } from '../testUtils';
17+
import { findWebpackPlugin, materializeFinalWebpackConfig } from '../testUtils';
1818

1919
describe('Sentry webpack plugin config', () => {
2020
it('includes expected properties', async () => {
@@ -283,44 +283,111 @@ describe('Sentry webpack plugin config', () => {
283283
});
284284
});
285285

286-
describe('disabling SentryWebpackPlugin', () => {
287-
it('allows SentryWebpackPlugin to be turned off for client code (independent of server code)', () => {
288-
const clientFinalNextConfig = materializeFinalNextConfig({
289-
...exportedNextConfig,
290-
sentry: { disableClientWebpackPlugin: true },
291-
});
292-
const clientFinalWebpackConfig = clientFinalNextConfig.webpack?.(clientWebpackConfig, clientBuildContext);
286+
describe('SentryWebpackPlugin enablement', () => {
287+
let processEnvBackup: typeof process.env;
293288

294-
const serverFinalNextConfig = materializeFinalNextConfig(exportedNextConfig, userSentryWebpackPluginConfig);
295-
const serverFinalWebpackConfig = serverFinalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext);
296-
297-
expect(clientFinalWebpackConfig?.plugins).not.toEqual(expect.arrayContaining([expect.any(SentryWebpackPlugin)]));
298-
expect(serverFinalWebpackConfig?.plugins).toEqual(expect.arrayContaining([expect.any(SentryWebpackPlugin)]));
289+
beforeEach(() => {
290+
processEnvBackup = { ...process.env };
299291
});
300-
it('allows SentryWebpackPlugin to be turned off for server code (independent of client code)', () => {
301-
const serverFinalNextConfig = materializeFinalNextConfig({
302-
...exportedNextConfig,
303-
sentry: { disableServerWebpackPlugin: true },
304-
});
305-
const serverFinalWebpackConfig = serverFinalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext);
306292

307-
const clientFinalNextConfig = materializeFinalNextConfig(exportedNextConfig, userSentryWebpackPluginConfig);
308-
const clientFinalWebpackConfig = clientFinalNextConfig.webpack?.(clientWebpackConfig, clientBuildContext);
309-
310-
expect(serverFinalWebpackConfig?.plugins).not.toEqual(expect.arrayContaining([expect.any(SentryWebpackPlugin)]));
311-
expect(clientFinalWebpackConfig?.plugins).toEqual(expect.arrayContaining([expect.any(SentryWebpackPlugin)]));
293+
afterEach(() => {
294+
process.env = processEnvBackup;
312295
});
313296

314-
it("doesn't set devtool if webpack plugin is disabled", () => {
315-
const finalNextConfig = materializeFinalNextConfig({
316-
...exportedNextConfig,
317-
webpack: () => ({ devtool: 'something-besides-source-map' } as any),
318-
sentry: { disableServerWebpackPlugin: true },
319-
});
320-
const finalWebpackConfig = finalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext);
321-
322-
expect(finalWebpackConfig?.devtool).not.toEqual('source-map');
323-
});
297+
it.each([
298+
// [testName, exportedNextConfig, extraEnvValues, shouldFindServerPlugin, shouldFindClientPlugin]
299+
[
300+
'obeys `disableClientWebpackPlugin = true`',
301+
{
302+
...exportedNextConfig,
303+
sentry: { disableClientWebpackPlugin: true },
304+
},
305+
{},
306+
true,
307+
false,
308+
],
309+
310+
[
311+
'obeys `disableServerWebpackPlugin = true`',
312+
{
313+
...exportedNextConfig,
314+
sentry: { disableServerWebpackPlugin: true },
315+
},
316+
{},
317+
false,
318+
true,
319+
],
320+
[
321+
'disables the plugin in Vercel `preview` environment',
322+
exportedNextConfig,
323+
{ VERCEL_ENV: 'preview' },
324+
false,
325+
false,
326+
],
327+
[
328+
'disables the plugin in Vercel `development` environment',
329+
exportedNextConfig,
330+
{ VERCEL_ENV: 'development' },
331+
false,
332+
false,
333+
],
334+
[
335+
'allows `disableClientWebpackPlugin = false` to override env vars`',
336+
{
337+
...exportedNextConfig,
338+
sentry: { disableClientWebpackPlugin: false },
339+
},
340+
{ VERCEL_ENV: 'preview' },
341+
false,
342+
true,
343+
],
344+
[
345+
'allows `disableServerWebpackPlugin = false` to override env vars`',
346+
{
347+
...exportedNextConfig,
348+
sentry: { disableServerWebpackPlugin: false },
349+
},
350+
{ VERCEL_ENV: 'preview' },
351+
true,
352+
false,
353+
],
354+
])(
355+
'%s',
356+
async (
357+
_testName: string,
358+
exportedNextConfig: ExportedNextConfig,
359+
extraEnvValues: Record<string, string>,
360+
shouldFindServerPlugin: boolean,
361+
shouldFindClientPlugin: boolean,
362+
) => {
363+
process.env = { ...process.env, ...extraEnvValues };
364+
365+
// We create a copy of the next config for each `materializeFinalWebpackConfig` call because the `sentry`
366+
// property gets deleted along the way, and its value matters for some of our test cases
367+
const serverFinalWebpackConfig = await materializeFinalWebpackConfig({
368+
exportedNextConfig: { ...exportedNextConfig },
369+
userSentryWebpackPluginConfig,
370+
incomingWebpackConfig: serverWebpackConfig,
371+
incomingWebpackBuildContext: serverBuildContext,
372+
});
373+
374+
const clientFinalWebpackConfig = await materializeFinalWebpackConfig({
375+
exportedNextConfig: { ...exportedNextConfig },
376+
userSentryWebpackPluginConfig,
377+
incomingWebpackConfig: clientWebpackConfig,
378+
incomingWebpackBuildContext: clientBuildContext,
379+
});
380+
381+
const genericSentryWebpackPluginInstance = expect.any(SentryWebpackPlugin);
382+
383+
expect(findWebpackPlugin(serverFinalWebpackConfig, 'SentryCliPlugin')).toEqual(
384+
shouldFindServerPlugin ? genericSentryWebpackPluginInstance : undefined,
385+
);
386+
expect(findWebpackPlugin(clientFinalWebpackConfig, 'SentryCliPlugin')).toEqual(
387+
shouldFindClientPlugin ? genericSentryWebpackPluginInstance : undefined,
388+
);
389+
},
390+
);
324391
});
325392

326393
describe('getUserConfigFile', () => {

0 commit comments

Comments
 (0)