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
2 changes: 1 addition & 1 deletion packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
29 changes: 26 additions & 3 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://github.com/nodejs/node/pull/55877>
* - Backported to 25.4.0: <https://github.com/nodejs/node/pull/61400>
* - Backported to 24.13.1: <https://github.com/nodejs/node/pull/61661>
*
* `'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.
Expand Down
47 changes: 47 additions & 0 deletions packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already always pull in this dependency in the CLI to check your node.js version and exit the CLI with an error if you're on an unsupported version.

import * as Log from '../build/output/log'
import * as ciEnvironment from '../server/ci-info'
import {
Expand Down Expand Up @@ -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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use a positive test instead? isNode?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't check for process.versions.node because bun (and probably deno too, I didn't check) puts fake values there for compatibility reasons.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢

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).
Expand Down
3 changes: 1 addition & 2 deletions test/production/turbopack-node-backend/next.config.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { nextTestSetup } from 'e2e-utils'

describe.each([
['workerThreads', true],
['forceWorkerThreads', true],
['childProcesses', false],
] as const)(
'turbopack-node-backend (%s)',
Expand Down
Loading