Skip to content

Commit 063ad99

Browse files
authored
fix(nextjs): Don't set experimental instrumentation hook flag for next 16 (#17978)
Updated the logic to determine if the instrumentation hook is required or not, as this was wrongly set in next.js 16 apps. closes #17965
1 parent 910b40b commit 063ad99

File tree

3 files changed

+154
-41
lines changed

3 files changed

+154
-41
lines changed

packages/nextjs/src/config/util.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,61 @@ export function supportsNativeDebugIds(version: string): boolean {
108108
return false;
109109
}
110110

111+
/**
112+
* Checks if the given Next.js version requires the `experimental.instrumentationHook` option.
113+
* Next.js 15.0.0 and higher (including certain RC and canary versions) no longer require this option
114+
* and will print a warning if it is set.
115+
*
116+
* @param version - version string to check.
117+
* @returns true if the version requires the instrumentationHook option to be set
118+
*/
119+
export function requiresInstrumentationHook(version: string): boolean {
120+
if (!version) {
121+
return true; // Default to requiring it if version cannot be determined
122+
}
123+
124+
const { major, minor, patch, prerelease } = parseSemver(version);
125+
126+
if (major === undefined || minor === undefined || patch === undefined) {
127+
return true; // Default to requiring it if parsing fails
128+
}
129+
130+
// Next.js 16+ never requires the hook
131+
if (major >= 16) {
132+
return false;
133+
}
134+
135+
// Next.js 14 and below always require the hook
136+
if (major < 15) {
137+
return true;
138+
}
139+
140+
// At this point, we know it's Next.js 15.x.y
141+
// Stable releases (15.0.0+) don't require the hook
142+
if (!prerelease) {
143+
return false;
144+
}
145+
146+
// Next.js 15.x.y with x > 0 or y > 0 don't require the hook
147+
if (minor > 0 || patch > 0) {
148+
return false;
149+
}
150+
151+
// Check specific prerelease versions that don't require the hook
152+
if (prerelease.startsWith('rc.')) {
153+
const rcNumber = parseInt(prerelease.split('.')[1] || '0', 10);
154+
return rcNumber === 0; // Only rc.0 requires the hook
155+
}
156+
157+
if (prerelease.startsWith('canary.')) {
158+
const canaryNumber = parseInt(prerelease.split('.')[1] || '0', 10);
159+
return canaryNumber < 124; // canary.124+ doesn't require the hook
160+
}
161+
162+
// All other 15.0.0 prerelease versions (alpha, beta, etc.) require the hook
163+
return true;
164+
}
165+
111166
/**
112167
* Determines which bundler is actually being used based on environment variables,
113168
* and CLI flags.

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import type {
1616
SentryBuildOptions,
1717
TurbopackOptions,
1818
} from './types';
19-
import { detectActiveBundler, getNextjsVersion, supportsProductionCompileHook } from './util';
19+
import {
20+
detectActiveBundler,
21+
getNextjsVersion,
22+
requiresInstrumentationHook,
23+
supportsProductionCompileHook,
24+
} from './util';
2025
import { constructWebpackConfigFunction } from './webpack';
2126

2227
let showedExportModeTunnelWarning = false;
@@ -178,47 +183,18 @@ function getFinalConfigObject(
178183

179184
// From Next.js version (15.0.0-canary.124) onwards, Next.js does no longer require the `experimental.instrumentationHook` option and will
180185
// print a warning when it is set, so we need to conditionally provide it for lower versions.
181-
if (nextJsVersion) {
182-
const { major, minor, patch, prerelease } = parseSemver(nextJsVersion);
183-
const isFullySupportedRelease =
184-
major !== undefined &&
185-
minor !== undefined &&
186-
patch !== undefined &&
187-
major >= 15 &&
188-
((minor === 0 && patch === 0 && prerelease === undefined) || minor > 0 || patch > 0);
189-
const isSupportedV15Rc =
190-
major !== undefined &&
191-
minor !== undefined &&
192-
patch !== undefined &&
193-
prerelease !== undefined &&
194-
major === 15 &&
195-
minor === 0 &&
196-
patch === 0 &&
197-
prerelease.startsWith('rc.') &&
198-
parseInt(prerelease.split('.')[1] || '', 10) > 0;
199-
const isSupportedCanary =
200-
minor !== undefined &&
201-
patch !== undefined &&
202-
prerelease !== undefined &&
203-
major === 15 &&
204-
minor === 0 &&
205-
patch === 0 &&
206-
prerelease.startsWith('canary.') &&
207-
parseInt(prerelease.split('.')[1] || '', 10) >= 124;
208-
209-
if (!isFullySupportedRelease && !isSupportedV15Rc && !isSupportedCanary) {
210-
if (incomingUserNextConfigObject.experimental?.instrumentationHook === false) {
211-
// eslint-disable-next-line no-console
212-
console.warn(
213-
'[@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)`.',
214-
);
215-
}
216-
incomingUserNextConfigObject.experimental = {
217-
instrumentationHook: true,
218-
...incomingUserNextConfigObject.experimental,
219-
};
186+
if (nextJsVersion && requiresInstrumentationHook(nextJsVersion)) {
187+
if (incomingUserNextConfigObject.experimental?.instrumentationHook === false) {
188+
// eslint-disable-next-line no-console
189+
console.warn(
190+
'[@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)`.',
191+
);
220192
}
221-
} else {
193+
incomingUserNextConfigObject.experimental = {
194+
instrumentationHook: true,
195+
...incomingUserNextConfigObject.experimental,
196+
};
197+
} else if (!nextJsVersion) {
222198
// 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.
223199
if (
224200
incomingUserNextConfigObject.experimental &&

packages/nextjs/test/config/util.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,88 @@ describe('util', () => {
213213
});
214214
});
215215

216+
describe('requiresInstrumentationHook', () => {
217+
describe('versions that do NOT require the hook (returns false)', () => {
218+
it.each([
219+
// Fully supported releases (15.0.0 or higher)
220+
['15.0.0', 'Next.js 15.0.0'],
221+
['15.0.1', 'Next.js 15.0.1'],
222+
['15.1.0', 'Next.js 15.1.0'],
223+
['15.2.0', 'Next.js 15.2.0'],
224+
['16.0.0', 'Next.js 16.0.0'],
225+
['17.0.0', 'Next.js 17.0.0'],
226+
['20.0.0', 'Next.js 20.0.0'],
227+
228+
// Supported v15.0.0-rc.1 or higher
229+
['15.0.0-rc.1', 'Next.js 15.0.0-rc.1'],
230+
['15.0.0-rc.2', 'Next.js 15.0.0-rc.2'],
231+
['15.0.0-rc.5', 'Next.js 15.0.0-rc.5'],
232+
['15.0.0-rc.100', 'Next.js 15.0.0-rc.100'],
233+
234+
// Supported v15.0.0-canary.124 or higher
235+
['15.0.0-canary.124', 'Next.js 15.0.0-canary.124 (exact threshold)'],
236+
['15.0.0-canary.125', 'Next.js 15.0.0-canary.125'],
237+
['15.0.0-canary.130', 'Next.js 15.0.0-canary.130'],
238+
['15.0.0-canary.200', 'Next.js 15.0.0-canary.200'],
239+
240+
// Next.js 16+ prerelease versions (all supported)
241+
['16.0.0-beta.0', 'Next.js 16.0.0-beta.0'],
242+
['16.0.0-beta.1', 'Next.js 16.0.0-beta.1'],
243+
['16.0.0-rc.0', 'Next.js 16.0.0-rc.0'],
244+
['16.0.0-rc.1', 'Next.js 16.0.0-rc.1'],
245+
['16.0.0-canary.1', 'Next.js 16.0.0-canary.1'],
246+
['16.0.0-alpha.1', 'Next.js 16.0.0-alpha.1'],
247+
['17.0.0-canary.1', 'Next.js 17.0.0-canary.1'],
248+
])('returns false for %s (%s)', version => {
249+
expect(util.requiresInstrumentationHook(version)).toBe(false);
250+
});
251+
});
252+
253+
describe('versions that DO require the hook (returns true)', () => {
254+
it.each([
255+
// Next.js 14 and below
256+
['14.2.0', 'Next.js 14.2.0'],
257+
['14.0.0', 'Next.js 14.0.0'],
258+
['13.5.0', 'Next.js 13.5.0'],
259+
['12.0.0', 'Next.js 12.0.0'],
260+
261+
// Unsupported v15.0.0-rc.0
262+
['15.0.0-rc.0', 'Next.js 15.0.0-rc.0'],
263+
264+
// Unsupported v15.0.0-canary versions below 124
265+
['15.0.0-canary.123', 'Next.js 15.0.0-canary.123'],
266+
['15.0.0-canary.100', 'Next.js 15.0.0-canary.100'],
267+
['15.0.0-canary.50', 'Next.js 15.0.0-canary.50'],
268+
['15.0.0-canary.1', 'Next.js 15.0.0-canary.1'],
269+
['15.0.0-canary.0', 'Next.js 15.0.0-canary.0'],
270+
271+
// Other prerelease versions
272+
['15.0.0-alpha.1', 'Next.js 15.0.0-alpha.1'],
273+
['15.0.0-beta.1', 'Next.js 15.0.0-beta.1'],
274+
])('returns true for %s (%s)', version => {
275+
expect(util.requiresInstrumentationHook(version)).toBe(true);
276+
});
277+
});
278+
279+
describe('edge cases', () => {
280+
it('returns true for empty string', () => {
281+
expect(util.requiresInstrumentationHook('')).toBe(true);
282+
});
283+
284+
it('returns true for invalid version strings', () => {
285+
expect(util.requiresInstrumentationHook('invalid.version')).toBe(true);
286+
});
287+
288+
it('returns true for versions missing patch number', () => {
289+
expect(util.requiresInstrumentationHook('15.4')).toBe(true);
290+
});
291+
292+
it('returns true for versions missing minor number', () => {
293+
expect(util.requiresInstrumentationHook('15')).toBe(true);
294+
});
295+
});
296+
});
297+
216298
describe('detectActiveBundler', () => {
217299
const originalArgv = process.argv;
218300
const originalEnv = process.env;

0 commit comments

Comments
 (0)