Skip to content

Commit 3d21b79

Browse files
authored
Turbopack: Add a check for node version before allowing workerThreads (#91614)
We want to start dogfooding `workerThreads`, but we can only safely do it if you have a new enough node version. We don't have any sort of strong enforcement around node.js version in front (e.g. you could have a misconfigured nvm or fnm), so we need to check the node version first. - napi-rs/napi-rs#2555 (comment) - nodejs/node#55877 - nodejs/node#61400 - nodejs/node#61661 Looks like this: ![Screenshot 2026-03-18 at 1.04.10 PM.png](https://app.graphite.com/user-attachments/assets/bfdb1051-2ebc-403a-af5c-7c715d651aa9.png) Related Node.js version bump for front: vercel/front#65059
1 parent df6cd59 commit 3d21b79

5 files changed

Lines changed: 76 additions & 7 deletions

File tree

packages/next/src/server/config-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export const experimentalSchema = {
351351
webpackMemoryOptimizations: z.boolean().optional(),
352352
turbopackMemoryLimit: z.number().optional(),
353353
turbopackPluginRuntimeStrategy: z
354-
.enum(['workerThreads', 'childProcesses'])
354+
.enum(['workerThreads', 'childProcesses', 'forceWorkerThreads'])
355355
.optional(),
356356
turbopackMinify: z.boolean().optional(),
357357
turbopackFileSystemCacheForDev: z.boolean().optional(),

packages/next/src/server/config-shared.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -628,9 +628,32 @@ export interface ExperimentalConfig {
628628
turbopackMemoryLimit?: number
629629

630630
/**
631-
* Selects the runtime backend used by Turbopack for Node.js evaluation.
632-
*/
633-
turbopackPluginRuntimeStrategy?: 'workerThreads' | 'childProcesses'
631+
* Selects the backend used by Turbopack for Node.js evaluation, e.g. webpack
632+
* loaders, Babel, or PostCSS.
633+
*
634+
* This defaults to `'childProcesses'`, which creates a pool of child node.js
635+
* processes and communciates with them over sockets.
636+
*
637+
* `'workerThreads'` should use less memory and CPU. It may become the default
638+
* in a future version of Next.js.
639+
*
640+
* Node.js 24.13.1+ or 25.4.0+ is required for `'workerThreads'` due to memory
641+
* safety bugs in older versions. If you use this option with an older Node.js
642+
* version, the setting is ignored and a warning is emitted. Bun and Deno are
643+
* assumed safe, and are not checked for compatibility.
644+
*
645+
* - Fix for memory safety issue: <https://github.com/nodejs/node/pull/55877>
646+
* - Backported to 25.4.0: <https://github.com/nodejs/node/pull/61400>
647+
* - Backported to 24.13.1: <https://github.com/nodejs/node/pull/61661>
648+
*
649+
* `'forceWorkerThreads'` behaves like `'workerThreads'` but skips the
650+
* version-gated downgrade. You should not use this option unless you're
651+
* confident that the version check in Next.js is wrong.
652+
*/
653+
turbopackPluginRuntimeStrategy?:
654+
| 'workerThreads'
655+
| 'childProcesses'
656+
| 'forceWorkerThreads'
634657

635658
/**
636659
* Enable minification. Defaults to true in build mode and false in dev mode.

packages/next/src/server/config.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { existsSync } from 'fs'
22
import { basename, extname, join, relative, isAbsolute, resolve } from 'path'
33
import { pathToFileURL } from 'url'
44
import findUp from 'next/dist/compiled/find-up'
5+
import semver from 'next/dist/compiled/semver'
56
import * as Log from '../build/output/log'
67
import * as ciEnvironment from '../server/ci-info'
78
import {
@@ -1453,6 +1454,52 @@ function assignDefaultsAndValidate(
14531454
result.experimental.useCache = result.cacheComponents
14541455
}
14551456

1457+
// Node.js version gate for turbopackPluginRuntimeStrategy: 'workerThreads'.
1458+
// Older Node.js versions have memory safety bugs in worker threads. Bun and
1459+
// Deno are not affected by this check.
1460+
{
1461+
const strategy = result.experimental.turbopackPluginRuntimeStrategy
1462+
const isForced = strategy === 'forceWorkerThreads'
1463+
if (strategy === 'workerThreads' || isForced) {
1464+
// Normalize 'forceWorkerThreads' → 'workerThreads' for Rust/serde
1465+
result.experimental.turbopackPluginRuntimeStrategy = 'workerThreads'
1466+
1467+
const isBun = !!process.versions.bun
1468+
const isDeno = !!process.versions.deno
1469+
if (!isBun && !isDeno) {
1470+
const nodeVersion = process.versions.node
1471+
const WORKER_THREADS_SAFE_RANGE = '>=24.13.1 <25.0.0 || >=25.4.0'
1472+
if (
1473+
!semver.satisfies(nodeVersion, WORKER_THREADS_SAFE_RANGE, {
1474+
includePrerelease: true,
1475+
})
1476+
) {
1477+
if (isForced) {
1478+
Log.warn(
1479+
`\`experimental.turbopackPluginRuntimeStrategy = ` +
1480+
`'forceWorkerThreads'\` has been enabled, but you're using ` +
1481+
`Node.js ${nodeVersion}, which has known memory safety bugs ` +
1482+
`with worker threads used from the Node-API. You may ` +
1483+
`experience crashes, segmentation faults, or other ` +
1484+
`instability. Upgrade to Node.js ${WORKER_THREADS_SAFE_RANGE}.`
1485+
)
1486+
} else {
1487+
Log.warn(
1488+
`\`experimental.turbopackPluginRuntimeStrategy = ` +
1489+
`'workerThreads'\` is set but has been ` +
1490+
`ignored because you're using Node.js ${nodeVersion}, which ` +
1491+
`has memory safety bugs in worker threads. Falling back to ` +
1492+
`'childProcesses'. Upgrade to Node.js ` +
1493+
`${WORKER_THREADS_SAFE_RANGE}.`
1494+
)
1495+
result.experimental.turbopackPluginRuntimeStrategy =
1496+
'childProcesses'
1497+
}
1498+
}
1499+
}
1500+
}
1501+
}
1502+
14561503
// Store the distDirRoot in the config before it is modified for development mode
14571504
;(result as NextConfigComplete).distDirRoot = result.distDir
14581505
// Pre-compute the effective hash salt (used by both Webpack and Turbopack).

test/production/turbopack-node-backend/next.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
const runtimeStrategy =
2-
process.env.TEST_TURBOPACK_PLUGIN_RUNTIME_STRATEGY || 'workerThreads'
1+
const runtimeStrategy = process.env.TEST_TURBOPACK_PLUGIN_RUNTIME_STRATEGY
32

43
module.exports = {
54
experimental: {

test/production/turbopack-node-backend/turbopack-node-backend.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { nextTestSetup } from 'e2e-utils'
22

33
describe.each([
4-
['workerThreads', true],
4+
['forceWorkerThreads', true],
55
['childProcesses', false],
66
] as const)(
77
'turbopack-node-backend (%s)',

0 commit comments

Comments
 (0)