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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm build",
"test:build-canary-webpack": "pnpm install && pnpm add next@canary && pnpm build-webpack",
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:assert-webpack": "pnpm test:prod"
"test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack"
},
"dependencies": {
"@sentry/nextjs": "latest || *",
Expand Down
64 changes: 9 additions & 55 deletions packages/nextjs/src/config/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,66 +109,20 @@ export function supportsNativeDebugIds(version: string): boolean {
}

/**
* Checks if the current Next.js version uses Turbopack as the default bundler.
* Starting from Next.js 15.6.0-canary.38, turbopack became the default for `next build`.
* Determines which bundler is actually being used based on environment variables,
* and CLI flags.
*
* @param version - Next.js version string to check.
* @returns true if the version uses Turbopack by default
* @returns 'turbopack' or 'webpack'
*/
export function isTurbopackDefaultForVersion(version: string): boolean {
if (!version) {
return false;
}

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

if (major === undefined || minor === undefined) {
return false;
}
export function detectActiveBundler(): 'turbopack' | 'webpack' {
const turbopackEnv = process.env.TURBOPACK;

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

// For Next.js 15, only canary versions 15.6.0-canary.40+ use turbopack by default
// Stable 15.x releases still use webpack by default
if (major === 15 && minor >= 6 && prerelease && prerelease.startsWith('canary.')) {
if (minor >= 7) {
return true;
}
const canaryNumber = parseInt(prerelease.split('.')[1] || '0', 10);
if (canaryNumber >= 40) {
return true;
}
}

return false;
}

/**
* Determines which bundler is actually being used based on environment variables,
* CLI flags, and Next.js version.
*
* @param nextJsVersion - The Next.js version string
* @returns 'turbopack', 'webpack', or undefined if it cannot be determined
*/
export function detectActiveBundler(nextJsVersion: string | undefined): 'turbopack' | 'webpack' | undefined {
if (process.env.TURBOPACK || process.argv.includes('--turbo')) {
if (isTurbopackEnabled || process.argv.includes('--turbo')) {
return 'turbopack';
}

// Explicit opt-in to webpack via --webpack flag
if (process.argv.includes('--webpack')) {
} else {
return 'webpack';
}

// Fallback to version-based default behavior
if (nextJsVersion) {
const turbopackIsDefault = isTurbopackDefaultForVersion(nextJsVersion);
return turbopackIsDefault ? 'turbopack' : 'webpack';
}

// Unlikely but at this point, we just assume webpack for older behavior
return 'webpack';
}
2 changes: 1 addition & 1 deletion packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ function getFinalConfigObject(
nextMajor = major;
}

const activeBundler = detectActiveBundler(nextJsVersion);
const activeBundler = detectActiveBundler();
const isTurbopack = activeBundler === 'turbopack';
const isWebpack = activeBundler === 'webpack';
const isTurbopackSupported = supportsProductionCompileHook(nextJsVersion ?? '');
Expand Down
161 changes: 12 additions & 149 deletions packages/nextjs/test/config/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,117 +213,6 @@ describe('util', () => {
});
});

describe('isTurbopackDefaultForVersion', () => {
describe('returns true for versions where turbopack is default', () => {
it.each([
// Next.js 16+ stable versions
['16.0.0', 'Next.js 16.0.0 stable'],
['16.0.1', 'Next.js 16.0.1 stable'],
['16.1.0', 'Next.js 16.1.0 stable'],
['16.2.5', 'Next.js 16.2.5 stable'],

// Next.js 16+ pre-release versions
['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.1.0-beta.2', 'Next.js 16.1.0-beta.2'],

// Next.js 17+
['17.0.0', 'Next.js 17.0.0'],
['18.0.0', 'Next.js 18.0.0'],
['20.0.0', 'Next.js 20.0.0'],

// Next.js 15.6.0-canary.40+ (boundary case)
['15.6.0-canary.40', 'Next.js 15.6.0-canary.40 (exact threshold)'],
['15.6.0-canary.41', 'Next.js 15.6.0-canary.41'],
['15.6.0-canary.42', 'Next.js 15.6.0-canary.42'],
['15.6.0-canary.100', 'Next.js 15.6.0-canary.100'],

// Next.js 15.7+ canary versions
['15.7.0-canary.1', 'Next.js 15.7.0-canary.1'],
['15.7.0-canary.50', 'Next.js 15.7.0-canary.50'],
['15.8.0-canary.1', 'Next.js 15.8.0-canary.1'],
['15.10.0-canary.1', 'Next.js 15.10.0-canary.1'],
])('returns true for %s (%s)', version => {
expect(util.isTurbopackDefaultForVersion(version)).toBe(true);
});
});

describe('returns false for versions where webpack is still default', () => {
it.each([
// Next.js 15.6.0-canary.39 and below
['15.6.0-canary.39', 'Next.js 15.6.0-canary.39 (just below threshold)'],
['15.6.0-canary.36', 'Next.js 15.6.0-canary.36'],
['15.6.0-canary.38', 'Next.js 15.6.0-canary.38'],
['15.6.0-canary.0', 'Next.js 15.6.0-canary.0'],

// Next.js 15.6.x stable releases (NOT canary)
['15.6.0', 'Next.js 15.6.0 stable'],
['15.6.1', 'Next.js 15.6.1 stable'],
['15.6.2', 'Next.js 15.6.2 stable'],
['15.6.10', 'Next.js 15.6.10 stable'],

// Next.js 15.6.x rc releases (NOT canary)
['15.6.0-rc.1', 'Next.js 15.6.0-rc.1'],
['15.6.0-rc.2', 'Next.js 15.6.0-rc.2'],

// Next.js 15.7+ stable releases (NOT canary)
['15.7.0', 'Next.js 15.7.0 stable'],
['15.8.0', 'Next.js 15.8.0 stable'],
['15.10.0', 'Next.js 15.10.0 stable'],

// Next.js 15.5 and below (all versions)
['15.5.0', 'Next.js 15.5.0'],
['15.5.0-canary.100', 'Next.js 15.5.0-canary.100'],
['15.4.1', 'Next.js 15.4.1'],
['15.0.0', 'Next.js 15.0.0'],
['15.0.0-canary.1', 'Next.js 15.0.0-canary.1'],

// Next.js 14.x and below
['14.2.0', 'Next.js 14.2.0'],
['14.0.0', 'Next.js 14.0.0'],
['14.0.0-canary.50', 'Next.js 14.0.0-canary.50'],
['13.5.0', 'Next.js 13.5.0'],
['13.0.0', 'Next.js 13.0.0'],
['12.0.0', 'Next.js 12.0.0'],
])('returns false for %s (%s)', version => {
expect(util.isTurbopackDefaultForVersion(version)).toBe(false);
});
});

describe('edge cases', () => {
it.each([
['', 'empty string'],
['invalid', 'invalid version string'],
['15', 'missing minor and patch'],
['15.6', 'missing patch'],
['not.a.version', 'completely invalid'],
['15.6.0-alpha.1', 'alpha prerelease (not canary)'],
['15.6.0-beta.1', 'beta prerelease (not canary)'],
])('returns false for %s (%s)', version => {
expect(util.isTurbopackDefaultForVersion(version)).toBe(false);
});
});

describe('canary number parsing edge cases', () => {
it.each([
['15.6.0-canary.', 'canary with no number'],
['15.6.0-canary.abc', 'canary with non-numeric value'],
['15.6.0-canary.38.extra', 'canary with extra segments'],
])('handles malformed canary versions: %s (%s)', version => {
// Should not throw, just return appropriate boolean
expect(() => util.isTurbopackDefaultForVersion(version)).not.toThrow();
});

it('handles canary.40 exactly (boundary)', () => {
expect(util.isTurbopackDefaultForVersion('15.6.0-canary.40')).toBe(true);
});

it('handles canary.39 exactly (boundary)', () => {
expect(util.isTurbopackDefaultForVersion('15.6.0-canary.39')).toBe(false);
});
});
});

describe('detectActiveBundler', () => {
const originalArgv = process.argv;
const originalEnv = process.env;
Expand All @@ -341,52 +230,26 @@ describe('util', () => {

it('returns turbopack when TURBOPACK env var is set', () => {
process.env.TURBOPACK = '1';
expect(util.detectActiveBundler('15.5.0')).toBe('turbopack');
});

it('returns webpack when --webpack flag is present', () => {
process.argv.push('--webpack');
expect(util.detectActiveBundler('16.0.0')).toBe('webpack');
});

it('returns turbopack for Next.js 16+ by default', () => {
expect(util.detectActiveBundler('16.0.0')).toBe('turbopack');
expect(util.detectActiveBundler('17.0.0')).toBe('turbopack');
});

it('returns turbopack for Next.js 15.6.0-canary.40+', () => {
expect(util.detectActiveBundler('15.6.0-canary.40')).toBe('turbopack');
expect(util.detectActiveBundler('15.6.0-canary.50')).toBe('turbopack');
expect(util.detectActiveBundler()).toBe('turbopack');
});

it('returns webpack for Next.js 15.6.0 stable', () => {
expect(util.detectActiveBundler('15.6.0')).toBe('webpack');
it('returns turbopack when TURBOPACK env var is set to auto', () => {
process.env.TURBOPACK = 'auto';
expect(util.detectActiveBundler()).toBe('turbopack');
});

it('returns webpack for Next.js 15.5.x and below', () => {
expect(util.detectActiveBundler('15.5.0')).toBe('webpack');
expect(util.detectActiveBundler('15.0.0')).toBe('webpack');
expect(util.detectActiveBundler('14.2.0')).toBe('webpack');
it('returns webpack when TURBOPACK env var is undefined', () => {
process.env.TURBOPACK = undefined;
expect(util.detectActiveBundler()).toBe('webpack');
});

it('returns webpack when version is undefined', () => {
expect(util.detectActiveBundler(undefined)).toBe('webpack');
it('returns webpack when TURBOPACK env var is false', () => {
process.env.TURBOPACK = 'false';
expect(util.detectActiveBundler()).toBe('webpack');
});

it('prioritizes TURBOPACK env var over version detection', () => {
process.env.TURBOPACK = '1';
expect(util.detectActiveBundler('14.0.0')).toBe('turbopack');
});

it('prioritizes --webpack flag over version detection', () => {
process.argv.push('--webpack');
expect(util.detectActiveBundler('16.0.0')).toBe('webpack');
});

it('prioritizes TURBOPACK env var over --webpack flag', () => {
process.env.TURBOPACK = '1';
process.argv.push('--webpack');
expect(util.detectActiveBundler('15.5.0')).toBe('turbopack');
it('returns webpack when TURBOPACK env var is not set', () => {
expect(util.detectActiveBundler()).toBe('webpack');
});
});
});
Loading