Skip to content

Commit db9ab7c

Browse files
authored
ref(nextjs): Extract isBuild into an exported function (#5444)
In nextjs hooks which run both during build- and runtime (like `getStaticProps`, which mostly is a build-time function, but will run at runtime[1] if revalidation is turned on) it's sometimes helpful to know which you're in, and there's no official way to do that that I've been able to find. In our case, we need to know which phase we're in because by the time pages get loaded in order to be rendered during build, they already have a `Sentry.init()` call baked in, causing the SDK to run. Since certain operations can only happen at runtime, we have logic in `index.server.ts` to detect the current phase. This pulls that logic into a utility function, so that it can be exported. (Ultimately this is a selfish change, as I'd like to be able to use `isBuild` in my test apps, but I figure it's a harmless addition which might help other developers, too.) [1] https://nextjs.org/docs/basic-features/data-fetching/get-static-props#when-does-getstaticprops-run
1 parent 43ace49 commit db9ab7c

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

packages/nextjs/src/index.server.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { escapeStringForRegex, logger } from '@sentry/utils';
77
import * as domainModule from 'domain';
88
import * as path from 'path';
99

10+
import { isBuild } from './utils/isBuild';
1011
import { buildMetadata } from './utils/metadata';
1112
import { NextjsOptions } from './utils/nextjsOptions';
1213
import { addIntegration } from './utils/userIntegrations';
@@ -21,23 +22,6 @@ export { ErrorBoundary, showReportDialog, withErrorBoundary } from '@sentry/reac
2122
type GlobalWithDistDir = typeof global & { __rewriteFramesDistDir__: string };
2223
const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null };
2324

24-
// During build, the main process is invoked by
25-
// `node next build`
26-
// and child processes are invoked as
27-
// `node <path>/node_modules/.../jest-worker/processChild.js`.
28-
// The former is (obviously) easy to recognize, but the latter could happen at runtime as well. Fortunately, the main
29-
// process hits this file before any of the child processes do, so we're able to set an env variable which the child
30-
// processes can then check. During runtime, the main process is invoked as
31-
// `node next start`
32-
// or
33-
// `node /var/runtime/index.js`,
34-
// so we never drop into the `if` in the first place.
35-
let isBuild = false;
36-
if (process.argv.includes('build') || process.env.SENTRY_BUILD_PHASE) {
37-
process.env.SENTRY_BUILD_PHASE = 'true';
38-
isBuild = true;
39-
}
40-
4125
const isVercel = !!process.env.VERCEL;
4226

4327
/** Inits the Sentry NextJS SDK on node. */
@@ -140,12 +124,13 @@ function addServerIntegrations(options: NextjsOptions): void {
140124
export type { SentryWebpackPluginOptions } from './config/types';
141125
export { withSentryConfig } from './config';
142126
export { withSentry } from './utils/withSentry';
127+
export { isBuild } from './utils/isBuild';
143128

144129
// Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel
145130
// deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and
146131
// b) doesn't work on those apps anyway. We also don't do it during build, because there's no server running in that
147132
// phase.)
148-
if (!isVercel && !isBuild) {
133+
if (!isVercel && !isBuild()) {
149134
// Dynamically require the file because even importing from it causes Next 12 to crash on Vercel.
150135
// In environments where the JS file doesn't exist, such as testing, import the TS file.
151136
try {

packages/nextjs/src/utils/isBuild.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Decide if the currently running process is part of the build phase or happening at runtime.
3+
*/
4+
export function isBuild(): boolean {
5+
// During build, the main process is invoked by
6+
// `node next build`
7+
// and child processes are invoked as
8+
// `node <path>/node_modules/.../jest-worker/processChild.js`.
9+
// The former is (obviously) easy to recognize, but the latter could happen at runtime as well. Fortunately, the main
10+
// process hits this file before any of the child processes do, so we're able to set an env variable which the child
11+
// processes can then check. During runtime, the main process is invoked as
12+
// `node next start`
13+
// or
14+
// `node /var/runtime/index.js`,
15+
// so we never drop into the `if` in the first place.
16+
if (process.argv.includes('build') || process.env.SENTRY_BUILD_PHASE) {
17+
process.env.SENTRY_BUILD_PHASE = 'true';
18+
return true;
19+
}
20+
21+
return false;
22+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { isBuild } from '../../src/utils/isBuild';
2+
3+
let originalEnv: typeof process.env;
4+
let originalArgv: typeof process.argv;
5+
6+
function assertNoMagicValues(): void {
7+
if (Object.keys(process.env).includes('SENTRY_BUILD_PHASE') || process.argv.includes('build')) {
8+
throw new Error('Not starting test with a clean setup');
9+
}
10+
}
11+
12+
describe('isBuild()', () => {
13+
beforeEach(() => {
14+
assertNoMagicValues();
15+
originalEnv = { ...process.env };
16+
originalArgv = [...process.argv];
17+
});
18+
19+
afterEach(() => {
20+
process.env = originalEnv;
21+
process.argv = originalArgv;
22+
assertNoMagicValues();
23+
});
24+
25+
it("detects 'build' in argv", () => {
26+
// the result of calling `next build`
27+
process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build'];
28+
expect(isBuild()).toBe(true);
29+
});
30+
31+
it("sets env var when 'build' in argv", () => {
32+
// the result of calling `next build`
33+
process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build'];
34+
isBuild();
35+
expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(true);
36+
});
37+
38+
it("does not set env var when 'build' not in argv", () => {
39+
isBuild();
40+
expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(false);
41+
});
42+
43+
it('detects env var', () => {
44+
process.env.SENTRY_BUILD_PHASE = 'true';
45+
expect(isBuild()).toBe(true);
46+
});
47+
48+
it("returns false when 'build' not in `argv` and env var not present", () => {
49+
expect(isBuild()).toBe(false);
50+
});
51+
});

0 commit comments

Comments
 (0)