Skip to content

Commit 910b40b

Browse files
authored
fix(nextjs): Update bundler detection (#17976)
Test were failing due to missing value injection, because the bundler was incorrectly detected. We can rely on `process.env.TURBOPACK` being set, confirmed this with Vercel. So this PR - simplifies the bundler detection by just checking the env var - brings back webpack dev tests closes https://linear.app/getsentry/issue/FE-618/webpack-breaks-instrumentation-for-dev-mode-in-next-16
1 parent f664505 commit 910b40b

File tree

5 files changed

+89
-408
lines changed

5 files changed

+89
-408
lines changed

dev-packages/e2e-tests/test-applications/nextjs-16/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm build",
1919
"test:build-canary-webpack": "pnpm install && pnpm add next@canary && pnpm build-webpack",
2020
"test:assert": "pnpm test:prod && pnpm test:dev",
21-
"test:assert-webpack": "pnpm test:prod"
21+
"test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack"
2222
},
2323
"dependencies": {
2424
"@sentry/nextjs": "latest || *",

packages/nextjs/src/config/util.ts

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -109,66 +109,20 @@ export function supportsNativeDebugIds(version: string): boolean {
109109
}
110110

111111
/**
112-
* Checks if the current Next.js version uses Turbopack as the default bundler.
113-
* Starting from Next.js 15.6.0-canary.38, turbopack became the default for `next build`.
112+
* Determines which bundler is actually being used based on environment variables,
113+
* and CLI flags.
114114
*
115-
* @param version - Next.js version string to check.
116-
* @returns true if the version uses Turbopack by default
115+
* @returns 'turbopack' or 'webpack'
117116
*/
118-
export function isTurbopackDefaultForVersion(version: string): boolean {
119-
if (!version) {
120-
return false;
121-
}
122-
123-
const { major, minor, prerelease } = parseSemver(version);
124-
125-
if (major === undefined || minor === undefined) {
126-
return false;
127-
}
117+
export function detectActiveBundler(): 'turbopack' | 'webpack' {
118+
const turbopackEnv = process.env.TURBOPACK;
128119

129-
// Next.js 16+ uses turbopack by default
130-
if (major >= 16) {
131-
return true;
132-
}
120+
// Check if TURBOPACK env var is set to a truthy value (excluding falsy strings like 'false', '0', '')
121+
const isTurbopackEnabled = turbopackEnv && turbopackEnv !== 'false' && turbopackEnv !== '0';
133122

134-
// For Next.js 15, only canary versions 15.6.0-canary.40+ use turbopack by default
135-
// Stable 15.x releases still use webpack by default
136-
if (major === 15 && minor >= 6 && prerelease && prerelease.startsWith('canary.')) {
137-
if (minor >= 7) {
138-
return true;
139-
}
140-
const canaryNumber = parseInt(prerelease.split('.')[1] || '0', 10);
141-
if (canaryNumber >= 40) {
142-
return true;
143-
}
144-
}
145-
146-
return false;
147-
}
148-
149-
/**
150-
* Determines which bundler is actually being used based on environment variables,
151-
* CLI flags, and Next.js version.
152-
*
153-
* @param nextJsVersion - The Next.js version string
154-
* @returns 'turbopack', 'webpack', or undefined if it cannot be determined
155-
*/
156-
export function detectActiveBundler(nextJsVersion: string | undefined): 'turbopack' | 'webpack' | undefined {
157-
if (process.env.TURBOPACK || process.argv.includes('--turbo')) {
123+
if (isTurbopackEnabled || process.argv.includes('--turbo')) {
158124
return 'turbopack';
159-
}
160-
161-
// Explicit opt-in to webpack via --webpack flag
162-
if (process.argv.includes('--webpack')) {
125+
} else {
163126
return 'webpack';
164127
}
165-
166-
// Fallback to version-based default behavior
167-
if (nextJsVersion) {
168-
const turbopackIsDefault = isTurbopackDefaultForVersion(nextJsVersion);
169-
return turbopackIsDefault ? 'turbopack' : 'webpack';
170-
}
171-
172-
// Unlikely but at this point, we just assume webpack for older behavior
173-
return 'webpack';
174128
}

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ function getFinalConfigObject(
261261
nextMajor = major;
262262
}
263263

264-
const activeBundler = detectActiveBundler(nextJsVersion);
264+
const activeBundler = detectActiveBundler();
265265
const isTurbopack = activeBundler === 'turbopack';
266266
const isWebpack = activeBundler === 'webpack';
267267
const isTurbopackSupported = supportsProductionCompileHook(nextJsVersion ?? '');

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

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

216-
describe('isTurbopackDefaultForVersion', () => {
217-
describe('returns true for versions where turbopack is default', () => {
218-
it.each([
219-
// Next.js 16+ stable versions
220-
['16.0.0', 'Next.js 16.0.0 stable'],
221-
['16.0.1', 'Next.js 16.0.1 stable'],
222-
['16.1.0', 'Next.js 16.1.0 stable'],
223-
['16.2.5', 'Next.js 16.2.5 stable'],
224-
225-
// Next.js 16+ pre-release versions
226-
['16.0.0-rc.1', 'Next.js 16.0.0-rc.1'],
227-
['16.0.0-canary.1', 'Next.js 16.0.0-canary.1'],
228-
['16.1.0-beta.2', 'Next.js 16.1.0-beta.2'],
229-
230-
// Next.js 17+
231-
['17.0.0', 'Next.js 17.0.0'],
232-
['18.0.0', 'Next.js 18.0.0'],
233-
['20.0.0', 'Next.js 20.0.0'],
234-
235-
// Next.js 15.6.0-canary.40+ (boundary case)
236-
['15.6.0-canary.40', 'Next.js 15.6.0-canary.40 (exact threshold)'],
237-
['15.6.0-canary.41', 'Next.js 15.6.0-canary.41'],
238-
['15.6.0-canary.42', 'Next.js 15.6.0-canary.42'],
239-
['15.6.0-canary.100', 'Next.js 15.6.0-canary.100'],
240-
241-
// Next.js 15.7+ canary versions
242-
['15.7.0-canary.1', 'Next.js 15.7.0-canary.1'],
243-
['15.7.0-canary.50', 'Next.js 15.7.0-canary.50'],
244-
['15.8.0-canary.1', 'Next.js 15.8.0-canary.1'],
245-
['15.10.0-canary.1', 'Next.js 15.10.0-canary.1'],
246-
])('returns true for %s (%s)', version => {
247-
expect(util.isTurbopackDefaultForVersion(version)).toBe(true);
248-
});
249-
});
250-
251-
describe('returns false for versions where webpack is still default', () => {
252-
it.each([
253-
// Next.js 15.6.0-canary.39 and below
254-
['15.6.0-canary.39', 'Next.js 15.6.0-canary.39 (just below threshold)'],
255-
['15.6.0-canary.36', 'Next.js 15.6.0-canary.36'],
256-
['15.6.0-canary.38', 'Next.js 15.6.0-canary.38'],
257-
['15.6.0-canary.0', 'Next.js 15.6.0-canary.0'],
258-
259-
// Next.js 15.6.x stable releases (NOT canary)
260-
['15.6.0', 'Next.js 15.6.0 stable'],
261-
['15.6.1', 'Next.js 15.6.1 stable'],
262-
['15.6.2', 'Next.js 15.6.2 stable'],
263-
['15.6.10', 'Next.js 15.6.10 stable'],
264-
265-
// Next.js 15.6.x rc releases (NOT canary)
266-
['15.6.0-rc.1', 'Next.js 15.6.0-rc.1'],
267-
['15.6.0-rc.2', 'Next.js 15.6.0-rc.2'],
268-
269-
// Next.js 15.7+ stable releases (NOT canary)
270-
['15.7.0', 'Next.js 15.7.0 stable'],
271-
['15.8.0', 'Next.js 15.8.0 stable'],
272-
['15.10.0', 'Next.js 15.10.0 stable'],
273-
274-
// Next.js 15.5 and below (all versions)
275-
['15.5.0', 'Next.js 15.5.0'],
276-
['15.5.0-canary.100', 'Next.js 15.5.0-canary.100'],
277-
['15.4.1', 'Next.js 15.4.1'],
278-
['15.0.0', 'Next.js 15.0.0'],
279-
['15.0.0-canary.1', 'Next.js 15.0.0-canary.1'],
280-
281-
// Next.js 14.x and below
282-
['14.2.0', 'Next.js 14.2.0'],
283-
['14.0.0', 'Next.js 14.0.0'],
284-
['14.0.0-canary.50', 'Next.js 14.0.0-canary.50'],
285-
['13.5.0', 'Next.js 13.5.0'],
286-
['13.0.0', 'Next.js 13.0.0'],
287-
['12.0.0', 'Next.js 12.0.0'],
288-
])('returns false for %s (%s)', version => {
289-
expect(util.isTurbopackDefaultForVersion(version)).toBe(false);
290-
});
291-
});
292-
293-
describe('edge cases', () => {
294-
it.each([
295-
['', 'empty string'],
296-
['invalid', 'invalid version string'],
297-
['15', 'missing minor and patch'],
298-
['15.6', 'missing patch'],
299-
['not.a.version', 'completely invalid'],
300-
['15.6.0-alpha.1', 'alpha prerelease (not canary)'],
301-
['15.6.0-beta.1', 'beta prerelease (not canary)'],
302-
])('returns false for %s (%s)', version => {
303-
expect(util.isTurbopackDefaultForVersion(version)).toBe(false);
304-
});
305-
});
306-
307-
describe('canary number parsing edge cases', () => {
308-
it.each([
309-
['15.6.0-canary.', 'canary with no number'],
310-
['15.6.0-canary.abc', 'canary with non-numeric value'],
311-
['15.6.0-canary.38.extra', 'canary with extra segments'],
312-
])('handles malformed canary versions: %s (%s)', version => {
313-
// Should not throw, just return appropriate boolean
314-
expect(() => util.isTurbopackDefaultForVersion(version)).not.toThrow();
315-
});
316-
317-
it('handles canary.40 exactly (boundary)', () => {
318-
expect(util.isTurbopackDefaultForVersion('15.6.0-canary.40')).toBe(true);
319-
});
320-
321-
it('handles canary.39 exactly (boundary)', () => {
322-
expect(util.isTurbopackDefaultForVersion('15.6.0-canary.39')).toBe(false);
323-
});
324-
});
325-
});
326-
327216
describe('detectActiveBundler', () => {
328217
const originalArgv = process.argv;
329218
const originalEnv = process.env;
@@ -341,52 +230,26 @@ describe('util', () => {
341230

342231
it('returns turbopack when TURBOPACK env var is set', () => {
343232
process.env.TURBOPACK = '1';
344-
expect(util.detectActiveBundler('15.5.0')).toBe('turbopack');
345-
});
346-
347-
it('returns webpack when --webpack flag is present', () => {
348-
process.argv.push('--webpack');
349-
expect(util.detectActiveBundler('16.0.0')).toBe('webpack');
350-
});
351-
352-
it('returns turbopack for Next.js 16+ by default', () => {
353-
expect(util.detectActiveBundler('16.0.0')).toBe('turbopack');
354-
expect(util.detectActiveBundler('17.0.0')).toBe('turbopack');
355-
});
356-
357-
it('returns turbopack for Next.js 15.6.0-canary.40+', () => {
358-
expect(util.detectActiveBundler('15.6.0-canary.40')).toBe('turbopack');
359-
expect(util.detectActiveBundler('15.6.0-canary.50')).toBe('turbopack');
233+
expect(util.detectActiveBundler()).toBe('turbopack');
360234
});
361235

362-
it('returns webpack for Next.js 15.6.0 stable', () => {
363-
expect(util.detectActiveBundler('15.6.0')).toBe('webpack');
236+
it('returns turbopack when TURBOPACK env var is set to auto', () => {
237+
process.env.TURBOPACK = 'auto';
238+
expect(util.detectActiveBundler()).toBe('turbopack');
364239
});
365240

366-
it('returns webpack for Next.js 15.5.x and below', () => {
367-
expect(util.detectActiveBundler('15.5.0')).toBe('webpack');
368-
expect(util.detectActiveBundler('15.0.0')).toBe('webpack');
369-
expect(util.detectActiveBundler('14.2.0')).toBe('webpack');
241+
it('returns webpack when TURBOPACK env var is undefined', () => {
242+
process.env.TURBOPACK = undefined;
243+
expect(util.detectActiveBundler()).toBe('webpack');
370244
});
371245

372-
it('returns webpack when version is undefined', () => {
373-
expect(util.detectActiveBundler(undefined)).toBe('webpack');
246+
it('returns webpack when TURBOPACK env var is false', () => {
247+
process.env.TURBOPACK = 'false';
248+
expect(util.detectActiveBundler()).toBe('webpack');
374249
});
375250

376-
it('prioritizes TURBOPACK env var over version detection', () => {
377-
process.env.TURBOPACK = '1';
378-
expect(util.detectActiveBundler('14.0.0')).toBe('turbopack');
379-
});
380-
381-
it('prioritizes --webpack flag over version detection', () => {
382-
process.argv.push('--webpack');
383-
expect(util.detectActiveBundler('16.0.0')).toBe('webpack');
384-
});
385-
386-
it('prioritizes TURBOPACK env var over --webpack flag', () => {
387-
process.env.TURBOPACK = '1';
388-
process.argv.push('--webpack');
389-
expect(util.detectActiveBundler('15.5.0')).toBe('turbopack');
251+
it('returns webpack when TURBOPACK env var is not set', () => {
252+
expect(util.detectActiveBundler()).toBe('webpack');
390253
});
391254
});
392255
});

0 commit comments

Comments
 (0)