Skip to content

Commit

Permalink
ref(nextjs): Extract isBuild into an exported function (#5444)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lobsterkatie committed Jul 26, 2022
1 parent 43ace49 commit db9ab7c
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 18 deletions.
21 changes: 3 additions & 18 deletions packages/nextjs/src/index.server.ts
Expand Up @@ -7,6 +7,7 @@ import { escapeStringForRegex, logger } from '@sentry/utils';
import * as domainModule from 'domain';
import * as path from 'path';

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

// During build, the main process is invoked by
// `node next build`
// and child processes are invoked as
// `node <path>/node_modules/.../jest-worker/processChild.js`.
// The former is (obviously) easy to recognize, but the latter could happen at runtime as well. Fortunately, the main
// process hits this file before any of the child processes do, so we're able to set an env variable which the child
// processes can then check. During runtime, the main process is invoked as
// `node next start`
// or
// `node /var/runtime/index.js`,
// so we never drop into the `if` in the first place.
let isBuild = false;
if (process.argv.includes('build') || process.env.SENTRY_BUILD_PHASE) {
process.env.SENTRY_BUILD_PHASE = 'true';
isBuild = true;
}

const isVercel = !!process.env.VERCEL;

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

// Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel
// deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and
// b) doesn't work on those apps anyway. We also don't do it during build, because there's no server running in that
// phase.)
if (!isVercel && !isBuild) {
if (!isVercel && !isBuild()) {
// Dynamically require the file because even importing from it causes Next 12 to crash on Vercel.
// In environments where the JS file doesn't exist, such as testing, import the TS file.
try {
Expand Down
22 changes: 22 additions & 0 deletions packages/nextjs/src/utils/isBuild.ts
@@ -0,0 +1,22 @@
/**
* Decide if the currently running process is part of the build phase or happening at runtime.
*/
export function isBuild(): boolean {
// During build, the main process is invoked by
// `node next build`
// and child processes are invoked as
// `node <path>/node_modules/.../jest-worker/processChild.js`.
// The former is (obviously) easy to recognize, but the latter could happen at runtime as well. Fortunately, the main
// process hits this file before any of the child processes do, so we're able to set an env variable which the child
// processes can then check. During runtime, the main process is invoked as
// `node next start`
// or
// `node /var/runtime/index.js`,
// so we never drop into the `if` in the first place.
if (process.argv.includes('build') || process.env.SENTRY_BUILD_PHASE) {
process.env.SENTRY_BUILD_PHASE = 'true';
return true;
}

return false;
}
51 changes: 51 additions & 0 deletions packages/nextjs/test/utils/isBuild.test.ts
@@ -0,0 +1,51 @@
import { isBuild } from '../../src/utils/isBuild';

let originalEnv: typeof process.env;
let originalArgv: typeof process.argv;

function assertNoMagicValues(): void {
if (Object.keys(process.env).includes('SENTRY_BUILD_PHASE') || process.argv.includes('build')) {
throw new Error('Not starting test with a clean setup');
}
}

describe('isBuild()', () => {
beforeEach(() => {
assertNoMagicValues();
originalEnv = { ...process.env };
originalArgv = [...process.argv];
});

afterEach(() => {
process.env = originalEnv;
process.argv = originalArgv;
assertNoMagicValues();
});

it("detects 'build' in argv", () => {
// the result of calling `next build`
process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build'];
expect(isBuild()).toBe(true);
});

it("sets env var when 'build' in argv", () => {
// the result of calling `next build`
process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build'];
isBuild();
expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(true);
});

it("does not set env var when 'build' not in argv", () => {
isBuild();
expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(false);
});

it('detects env var', () => {
process.env.SENTRY_BUILD_PHASE = 'true';
expect(isBuild()).toBe(true);
});

it("returns false when 'build' not in `argv` and env var not present", () => {
expect(isBuild()).toBe(false);
});
});

0 comments on commit db9ab7c

Please sign in to comment.