esbuild & Turbopack Version Compatibility Reference
This reference pins the version relationships across the esbuild and Turbopack toolchains: which esbuild releases run on which Node versions, which Turbopack flag and config shape ships in each Next.js major, and how Turborepo 1.x and 2.x differ. It exists because the most expensive failures in this stack are not bugs but mismatches — an esbuild context() call against a pre-0.17 API, a next.config still using experimental.turbo after the key was promoted, or a turbo.json using pipeline under Turborepo 2.x. For the workflows these versions power, see esbuild & Turbopack Workflows; use this page to decide exactly which versions to install before you write a line of config.
What This Page Pins and Why
Three independent tools share this section but version on their own cadence. esbuild ships frequently with occasional behavior changes inside the 0.x range — there is no 1.0, so a minor like 0.17 or 0.25 can carry a breaking change a semver-trained eye would miss. Turbopack does not version independently at all; it is embedded in Next.js, so “which Turbopack” is really “which Next.js,” and the dev-flag and config-key names changed as it moved from alpha to stable. Turborepo versions normally, and its 1.x → 2.x jump renamed the central turbo.json key. Pinning all three together is the only way to keep a monorepo’s local, CI, and deploy environments hashing and building identically — the same determinism concern that drives remote caching.
esbuild ↔ Node Compatibility
esbuild’s Go binary is largely Node-agnostic for the build itself, but the JavaScript API wrapper, the context() incremental API, and the watch/serve features assume a modern Node. The table below maps the practical floor.
| esbuild | Node floor | Context/watch API | Notable behavior |
|---|---|---|---|
| 0.17.x | Node 12+ | New context() API introduced; old incremental/rebuild/watch() on build() removed |
The breaking re-architecture — build({ incremental: true }) no longer exists. |
| 0.18.x | Node 12+ | context() stable |
Minor option cleanups; safe upgrade from 0.17. |
| 0.19.x | Node 12+ | context() stable |
Common modern baseline; widely embedded by Vite 5. |
| 0.20.x | Node 18+ | context() stable |
Drops older Node in the published wrapper; align CI Node. |
| 0.21.x | Node 18+ | context() stable |
Incremental refinements; no API breaks. |
| 0.23.x | Node 18+ | context() stable |
Continued option additions. |
| 0.25.x | Node 18+ | context() stable |
Default-tightening release (see deprecations); current baseline for this section. |
The single most disruptive line is 0.17: any code written against esbuild 0.16 that called build({ incremental: true, watch: {...} }) must be rewritten to const ctx = await esbuild.context({...}); await ctx.watch();. The watch-mode workflow built on the modern API is covered in Using esbuild context watch mode for incremental rebuilds.
Turbopack ↔ Next.js ↔ Node Compatibility
Turbopack ships inside Next.js. The dev flag and config key evolved with maturity; production next build --turbopack only stabilized in the Next 15 line.
| Next.js | Turbopack stage | Dev flag | Config key | Node floor |
|---|---|---|---|---|
| 13.x | alpha | next dev --turbo |
experimental.turbo |
Node 16+ |
| 14.x | beta (dev) | next dev --turbo |
experimental.turbo |
Node 18+ |
| 15.0–15.2 | stable dev, beta build | next dev --turbopack |
experimental.turbo (deprecated) → turbopack |
Node 18.18+ |
| 15.3+ | stable dev, --turbopack build maturing |
next dev --turbopack / next build --turbopack |
turbopack (top-level) |
Node 18.18+ / 20+ recommended |
Two renames bite here. The flag changed from --turbo (Next 13/14) to --turbopack (Next 15); scripts that still call next dev --turbo on Next 15 hit an unknown-flag error. The config key moved from experimental.turbo to a top-level turbopack in Next 15 — experimental.turbo still works with a deprecation warning during the 15 line but should be migrated.
// next.config.js — Next 13/14 (Turbopack alpha/beta). Old shape.
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
turbo: {
rules: { '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' } },
},
},
};
// next.config.js — Next 15+ (Turbopack stable dev). Promoted top-level key.
/** @type {import('next').NextConfig} */
module.exports = {
turbopack: {
rules: { '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' } },
},
};
For the incremental-compilation behavior these versions expose, see Turbopack Incremental Compilation.
Turborepo 1.x ↔ 2.x
Turborepo’s major jump renamed the central config key and tightened environment-variable handling, which directly affects cache hashing.
| Turborepo | Config key | Env handling | Node floor | Migration |
|---|---|---|---|---|
| 1.x | pipeline |
Loose; env/globalEnv optional, more implicit inclusion |
Node 14+ | n/a |
| 2.x | tasks |
Stricter; declare env/globalEnv/globalPassThroughEnv explicitly |
Node 18+ | npx @turbo/codemod migrate |
// turbo.json — Turborepo 1.x. The `pipeline` key.
{
"pipeline": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }
}
}
// turbo.json — Turborepo 2.x. `pipeline` renamed to `tasks`.
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }
}
}
The 2.x env-handling change is the subtler hazard: a task that implicitly picked up an env var under 1.x may now miss it from its hash unless you declare it under env/globalEnv, producing stale hits. Run the codemod, then audit declarations — the mechanics are detailed in Remote Caching and Distributed Build Coordination.
Known Breaking Changes & Deprecations
- esbuild 0.17 — context API replaces
incremental/watch(). Removebuild({ incremental, watch }); adoptesbuild.context()plusctx.watch()/ctx.rebuild()/ctx.serve(). This is the hard cutover dividing pre- and post-0.17 build scripts. - esbuild 0.25 — tightened defaults. The 0.25 line carried default-behavior changes (stricter resolution and option defaults that previously had looser fallbacks). Re-run your build after upgrading and compare
--metafileoutput; do not assume a silent passthrough. - esbuild 0.20 — published wrapper Node floor raised. The JS API wrapper expects Node 18+; older CI Node images can fail to load the package even though the Go binary would run.
- Next.js 15 —
--turbo→--turbopack. The dev flag was renamed. Update everydevscript; the old flag errors on Next 15. - Next.js 15 —
experimental.turbo→turbopack. The config namespace was promoted to a top-level key.experimental.turbowarns during the 15 line and should be migrated before it is removed. - Turborepo 2.x —
pipeline→tasks. Theturbo.jsonkey was renamed and env handling became stricter. Apply@turbo/codemod migrateand declare env vars explicitly.
Upgrade & Migration Notes
Upgrade these tools one axis at a time and re-measure, because they interact through the cache. A safe order: bump Node first and confirm the existing build is green; upgrade esbuild and diff --metafile output for unexpected tree-shaking or resolution changes; upgrade Next.js and migrate the Turbopack flag/key in the same commit so dev scripts and next.config move together; upgrade Turborepo last and run the codemod so the tasks rename and env declarations land atomically. After any of these, invalidate caches once (rm -rf .next node_modules/.cache/turbo) so the first post-upgrade run rebuilds from clean inputs rather than replaying an artifact produced by the prior toolchain — recall that the Node version is not in the Turborepo hash by default, so a Node bump alone will not bust stale artifacts.
Related
- esbuild & Turbopack Workflows — the overview these version pairings underpin.
- Turbopack Incremental Compilation — the engine whose flag and config shape changed across Next.js majors above.
- Remote Caching and Distributed Build Coordination — where the Turborepo 1.x/2.x env-handling change directly affects cache keys.
- esbuild API and CLI for Rapid Builds — the API surface reshaped by the esbuild 0.17 context cutover.