diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 929efaa767e2..ea919d16e24a 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -351,7 +351,7 @@ export const experimentalSchema = { webpackMemoryOptimizations: z.boolean().optional(), turbopackMemoryLimit: z.number().optional(), turbopackPluginRuntimeStrategy: z - .enum(['workerThreads', 'childProcesses']) + .enum(['workerThreads', 'childProcesses', 'forceWorkerThreads']) .optional(), turbopackMinify: z.boolean().optional(), turbopackFileSystemCacheForDev: z.boolean().optional(), diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 30fb0b34069c..1427c20cdd6d 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -628,9 +628,32 @@ export interface ExperimentalConfig { turbopackMemoryLimit?: number /** - * Selects the runtime backend used by Turbopack for Node.js evaluation. - */ - turbopackPluginRuntimeStrategy?: 'workerThreads' | 'childProcesses' + * Selects the backend used by Turbopack for Node.js evaluation, e.g. webpack + * loaders, Babel, or PostCSS. + * + * This defaults to `'childProcesses'`, which creates a pool of child node.js + * processes and communciates with them over sockets. + * + * `'workerThreads'` should use less memory and CPU. It may become the default + * in a future version of Next.js. + * + * Node.js 24.13.1+ or 25.4.0+ is required for `'workerThreads'` due to memory + * safety bugs in older versions. If you use this option with an older Node.js + * version, the setting is ignored and a warning is emitted. Bun and Deno are + * assumed safe, and are not checked for compatibility. + * + * - Fix for memory safety issue: + * - Backported to 25.4.0: + * - Backported to 24.13.1: + * + * `'forceWorkerThreads'` behaves like `'workerThreads'` but skips the + * version-gated downgrade. You should not use this option unless you're + * confident that the version check in Next.js is wrong. + */ + turbopackPluginRuntimeStrategy?: + | 'workerThreads' + | 'childProcesses' + | 'forceWorkerThreads' /** * Enable minification. Defaults to true in build mode and false in dev mode. diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 18810002fe82..fec0d013f7aa 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -2,6 +2,7 @@ import { existsSync } from 'fs' import { basename, extname, join, relative, isAbsolute, resolve } from 'path' import { pathToFileURL } from 'url' import findUp from 'next/dist/compiled/find-up' +import semver from 'next/dist/compiled/semver' import * as Log from '../build/output/log' import * as ciEnvironment from '../server/ci-info' import { @@ -1453,6 +1454,52 @@ function assignDefaultsAndValidate( result.experimental.useCache = result.cacheComponents } + // Node.js version gate for turbopackPluginRuntimeStrategy: 'workerThreads'. + // Older Node.js versions have memory safety bugs in worker threads. Bun and + // Deno are not affected by this check. + { + const strategy = result.experimental.turbopackPluginRuntimeStrategy + const isForced = strategy === 'forceWorkerThreads' + if (strategy === 'workerThreads' || isForced) { + // Normalize 'forceWorkerThreads' → 'workerThreads' for Rust/serde + result.experimental.turbopackPluginRuntimeStrategy = 'workerThreads' + + const isBun = !!process.versions.bun + const isDeno = !!process.versions.deno + if (!isBun && !isDeno) { + const nodeVersion = process.versions.node + const WORKER_THREADS_SAFE_RANGE = '>=24.13.1 <25.0.0 || >=25.4.0' + if ( + !semver.satisfies(nodeVersion, WORKER_THREADS_SAFE_RANGE, { + includePrerelease: true, + }) + ) { + if (isForced) { + Log.warn( + `\`experimental.turbopackPluginRuntimeStrategy = ` + + `'forceWorkerThreads'\` has been enabled, but you're using ` + + `Node.js ${nodeVersion}, which has known memory safety bugs ` + + `with worker threads used from the Node-API. You may ` + + `experience crashes, segmentation faults, or other ` + + `instability. Upgrade to Node.js ${WORKER_THREADS_SAFE_RANGE}.` + ) + } else { + Log.warn( + `\`experimental.turbopackPluginRuntimeStrategy = ` + + `'workerThreads'\` is set but has been ` + + `ignored because you're using Node.js ${nodeVersion}, which ` + + `has memory safety bugs in worker threads. Falling back to ` + + `'childProcesses'. Upgrade to Node.js ` + + `${WORKER_THREADS_SAFE_RANGE}.` + ) + result.experimental.turbopackPluginRuntimeStrategy = + 'childProcesses' + } + } + } + } + } + // Store the distDirRoot in the config before it is modified for development mode ;(result as NextConfigComplete).distDirRoot = result.distDir // Pre-compute the effective hash salt (used by both Webpack and Turbopack). diff --git a/test/production/turbopack-node-backend/next.config.js b/test/production/turbopack-node-backend/next.config.js index d359dc20168d..3e42b1ded203 100644 --- a/test/production/turbopack-node-backend/next.config.js +++ b/test/production/turbopack-node-backend/next.config.js @@ -1,5 +1,4 @@ -const runtimeStrategy = - process.env.TEST_TURBOPACK_PLUGIN_RUNTIME_STRATEGY || 'workerThreads' +const runtimeStrategy = process.env.TEST_TURBOPACK_PLUGIN_RUNTIME_STRATEGY module.exports = { experimental: { diff --git a/test/production/turbopack-node-backend/turbopack-node-backend.test.ts b/test/production/turbopack-node-backend/turbopack-node-backend.test.ts index f480a5ec0726..ec4983a44b48 100644 --- a/test/production/turbopack-node-backend/turbopack-node-backend.test.ts +++ b/test/production/turbopack-node-backend/turbopack-node-backend.test.ts @@ -1,7 +1,7 @@ import { nextTestSetup } from 'e2e-utils' describe.each([ - ['workerThreads', true], + ['forceWorkerThreads', true], ['childProcesses', false], ] as const)( 'turbopack-node-backend (%s)',