Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions packages/nextjs/src/config/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,61 @@ export function supportsNativeDebugIds(version: string): boolean {
return false;
}

/**
* Checks if the given Next.js version requires the `experimental.instrumentationHook` option.
* Next.js 15.0.0 and higher (including certain RC and canary versions) no longer require this option
* and will print a warning if it is set.
*
* @param version - version string to check.
* @returns true if the version requires the instrumentationHook option to be set
*/
export function requiresInstrumentationHook(version: string): boolean {
if (!version) {
return true; // Default to requiring it if version cannot be determined
}

const { major, minor, patch, prerelease } = parseSemver(version);

if (major === undefined || minor === undefined || patch === undefined) {
return true; // Default to requiring it if parsing fails
}

// Next.js 16+ never requires the hook
if (major >= 16) {
return false;
}

// Next.js 14 and below always require the hook
if (major < 15) {
return true;
}

// At this point, we know it's Next.js 15.x.y
// Stable releases (15.0.0+) don't require the hook
if (!prerelease) {
return false;
}

// Next.js 15.x.y with x > 0 or y > 0 don't require the hook
if (minor > 0 || patch > 0) {
return false;
}

// Check specific prerelease versions that don't require the hook
if (prerelease.startsWith('rc.')) {
const rcNumber = parseInt(prerelease.split('.')[1] || '0', 10);
return rcNumber === 0; // Only rc.0 requires the hook
}

if (prerelease.startsWith('canary.')) {
const canaryNumber = parseInt(prerelease.split('.')[1] || '0', 10);
return canaryNumber < 124; // canary.124+ doesn't require the hook
}

// All other 15.0.0 prerelease versions (alpha, beta, etc.) require the hook
return true;
}

/**
* Determines which bundler is actually being used based on environment variables,
* and CLI flags.
Expand Down
58 changes: 17 additions & 41 deletions packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import type {
SentryBuildOptions,
TurbopackOptions,
} from './types';
import { detectActiveBundler, getNextjsVersion, supportsProductionCompileHook } from './util';
import {
detectActiveBundler,
getNextjsVersion,
requiresInstrumentationHook,
supportsProductionCompileHook,
} from './util';
import { constructWebpackConfigFunction } from './webpack';

let showedExportModeTunnelWarning = false;
Expand Down Expand Up @@ -178,47 +183,18 @@ function getFinalConfigObject(

// From Next.js version (15.0.0-canary.124) onwards, Next.js does no longer require the `experimental.instrumentationHook` option and will
// print a warning when it is set, so we need to conditionally provide it for lower versions.
if (nextJsVersion) {
const { major, minor, patch, prerelease } = parseSemver(nextJsVersion);
const isFullySupportedRelease =
major !== undefined &&
minor !== undefined &&
patch !== undefined &&
major >= 15 &&
((minor === 0 && patch === 0 && prerelease === undefined) || minor > 0 || patch > 0);
const isSupportedV15Rc =
major !== undefined &&
minor !== undefined &&
patch !== undefined &&
prerelease !== undefined &&
major === 15 &&
minor === 0 &&
patch === 0 &&
prerelease.startsWith('rc.') &&
parseInt(prerelease.split('.')[1] || '', 10) > 0;
const isSupportedCanary =
minor !== undefined &&
patch !== undefined &&
prerelease !== undefined &&
major === 15 &&
minor === 0 &&
patch === 0 &&
prerelease.startsWith('canary.') &&
parseInt(prerelease.split('.')[1] || '', 10) >= 124;

if (!isFullySupportedRelease && !isSupportedV15Rc && !isSupportedCanary) {
if (incomingUserNextConfigObject.experimental?.instrumentationHook === false) {
// eslint-disable-next-line no-console
console.warn(
'[@sentry/nextjs] You turned off the `experimental.instrumentationHook` option. Note that Sentry will not be initialized if you did not set it up inside `instrumentation.(js|ts)`.',
);
}
incomingUserNextConfigObject.experimental = {
instrumentationHook: true,
...incomingUserNextConfigObject.experimental,
};
if (nextJsVersion && requiresInstrumentationHook(nextJsVersion)) {
if (incomingUserNextConfigObject.experimental?.instrumentationHook === false) {
// eslint-disable-next-line no-console
console.warn(
'[@sentry/nextjs] You turned off the `experimental.instrumentationHook` option. Note that Sentry will not be initialized if you did not set it up inside `instrumentation.(js|ts)`.',
);
}
} else {
incomingUserNextConfigObject.experimental = {
instrumentationHook: true,
...incomingUserNextConfigObject.experimental,
};
} else if (!nextJsVersion) {
// If we cannot detect a Next.js version for whatever reason, the sensible default is to set the `experimental.instrumentationHook`, even though it may create a warning.
if (
incomingUserNextConfigObject.experimental &&
Expand Down
82 changes: 82 additions & 0 deletions packages/nextjs/test/config/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,88 @@ describe('util', () => {
});
});

describe('requiresInstrumentationHook', () => {
describe('versions that do NOT require the hook (returns false)', () => {
it.each([
// Fully supported releases (15.0.0 or higher)
['15.0.0', 'Next.js 15.0.0'],
['15.0.1', 'Next.js 15.0.1'],
['15.1.0', 'Next.js 15.1.0'],
['15.2.0', 'Next.js 15.2.0'],
['16.0.0', 'Next.js 16.0.0'],
['17.0.0', 'Next.js 17.0.0'],
['20.0.0', 'Next.js 20.0.0'],

// Supported v15.0.0-rc.1 or higher
['15.0.0-rc.1', 'Next.js 15.0.0-rc.1'],
['15.0.0-rc.2', 'Next.js 15.0.0-rc.2'],
['15.0.0-rc.5', 'Next.js 15.0.0-rc.5'],
['15.0.0-rc.100', 'Next.js 15.0.0-rc.100'],

// Supported v15.0.0-canary.124 or higher
['15.0.0-canary.124', 'Next.js 15.0.0-canary.124 (exact threshold)'],
['15.0.0-canary.125', 'Next.js 15.0.0-canary.125'],
['15.0.0-canary.130', 'Next.js 15.0.0-canary.130'],
['15.0.0-canary.200', 'Next.js 15.0.0-canary.200'],

// Next.js 16+ prerelease versions (all supported)
['16.0.0-beta.0', 'Next.js 16.0.0-beta.0'],
['16.0.0-beta.1', 'Next.js 16.0.0-beta.1'],
['16.0.0-rc.0', 'Next.js 16.0.0-rc.0'],
['16.0.0-rc.1', 'Next.js 16.0.0-rc.1'],
['16.0.0-canary.1', 'Next.js 16.0.0-canary.1'],
['16.0.0-alpha.1', 'Next.js 16.0.0-alpha.1'],
['17.0.0-canary.1', 'Next.js 17.0.0-canary.1'],
])('returns false for %s (%s)', version => {
expect(util.requiresInstrumentationHook(version)).toBe(false);
});
});

describe('versions that DO require the hook (returns true)', () => {
it.each([
// Next.js 14 and below
['14.2.0', 'Next.js 14.2.0'],
['14.0.0', 'Next.js 14.0.0'],
['13.5.0', 'Next.js 13.5.0'],
['12.0.0', 'Next.js 12.0.0'],

// Unsupported v15.0.0-rc.0
['15.0.0-rc.0', 'Next.js 15.0.0-rc.0'],

// Unsupported v15.0.0-canary versions below 124
['15.0.0-canary.123', 'Next.js 15.0.0-canary.123'],
['15.0.0-canary.100', 'Next.js 15.0.0-canary.100'],
['15.0.0-canary.50', 'Next.js 15.0.0-canary.50'],
['15.0.0-canary.1', 'Next.js 15.0.0-canary.1'],
['15.0.0-canary.0', 'Next.js 15.0.0-canary.0'],

// Other prerelease versions
['15.0.0-alpha.1', 'Next.js 15.0.0-alpha.1'],
['15.0.0-beta.1', 'Next.js 15.0.0-beta.1'],
])('returns true for %s (%s)', version => {
expect(util.requiresInstrumentationHook(version)).toBe(true);
});
});

describe('edge cases', () => {
it('returns true for empty string', () => {
expect(util.requiresInstrumentationHook('')).toBe(true);
});

it('returns true for invalid version strings', () => {
expect(util.requiresInstrumentationHook('invalid.version')).toBe(true);
});

it('returns true for versions missing patch number', () => {
expect(util.requiresInstrumentationHook('15.4')).toBe(true);
});

it('returns true for versions missing minor number', () => {
expect(util.requiresInstrumentationHook('15')).toBe(true);
});
});
});

describe('detectActiveBundler', () => {
const originalArgv = process.argv;
const originalEnv = process.env;
Expand Down
Loading