vx vs Turborepo
Both tools run the tasks of a JavaScript monorepo and cache their results. This page puts them side by side on one benchmark, feature by feature, and says when Turborepo is the better choice. The design choices behind both are on vx, Turborepo, Nx, Bazel.
The numbers
Section titled “The numbers”The table
| 1,090 packages, 3,270 tasks | vx | Turborepo |
|---|---|---|
| Cold build: total time | 3 min 40 s | 4 min 59 s (vx 36% faster) |
| Cold build: CPU burned | 17 s | 21 s (vx 22% faster) |
| Fully cached run (restored) | 650 ms | 997 ms (vx 53% faster) |
| Fully cached run (up-to-date) | 393 ms | 463 ms (vx 18% faster) |
| Secondary: time the runner adds to a cold build | 2 s | 1 min 21 s |
vx N% or N× faster: that tool takes N% longer or N times as long as vx.
Benchmark workload: a synthetic monorepo of 1,090 packages and 3,270 tasks in 100 dependency layers, every build and test taking 1 s; real repos with uneven task times will differ. Run 2026-10-04 on linux x64, 4 cores: vx from source, Turborepo 2.11.7, Nx 23.2.1, Vite Task (vite-plus) 1.0.0.
Same graph, commands and concurrency, each tool in its own native config: how it is measured.
Feature by feature
Section titled “Feature by feature”| vx | Turborepo | |
|---|---|---|
| Config | TypeScript, evaluated into the cache key | JSON (turbo.json), static |
| Default inputs | None: you declare cache.inputs.files | Every tracked file in the package |
| Outputs on run and restore | Wiped first: no stale file survives a hit | Additive: stale files survive |
| Task sandbox | Free and local, opt-in per task (Linux, macOS) | Its docs describe no file sandbox |
| Clean-tree hashing | No source reads: git index OIDs | git OIDs |
| Daemon | None | None for turbo run since 2.8.11 |
| Remote cache | Any wire through a plugin; turboCache() speaks Turbo’s own | Vercel’s /v8/artifacts, hosted or your own |
| Plugins | Every pipeline stage, 14 hooks; core applies none | Its docs describe no plugin surface |
| Why a task missed | vx why diffs two keys: the file, env var or upstream that moved | --summarize shows hashes, not the diff |
| Watch mode | vx watch <task> | turbo watch |
| Prune for Docker | vx prune, from @vzn/vx-lockfile | turbo prune |
| Import boundaries | No: a lint’s job | boundaries |
| Non-JS projects | No | Experimental: Go, Cargo, uv |
| Terminal output | Streamed blocks you can scroll, copy and pipe | Terminal UI |
| Windows | WSL2 | Native |
| Install | One binary; no Bun, Node optional | npm and Node |
Every row is held in vx vs Turborepo vs Nx, which links each claim to its source.
A cache you can trust
Section titled “A cache you can trust”- Your config is in the key. vx evaluates each TypeScript config before hashing, so imports and computed values move the key.
- Outputs are owned. Declared outputs are wiped before every run and every restore, so a file from last week’s build cannot ride along on a hit.
- Tasks can prove their reads. Opt a task into the sandbox, and a workspace file it did not declare is out of its reach. A violation fails the task, and a failed task is never cached.
- Pull requests keep to their own scope. A pull request run reads the trusted keys and writes only its own, over any wire, Turbo’s included. The real boundary is a token your cache server limits; the scope keeps honest runs apart (security model).
Where Turborepo wins
Section titled “Where Turborepo wins”Pick Turborepo if one of these decides it for you:
- Caching before anyone writes an input list. Turborepo hashes every
tracked file in a package by default
(
inputs). vx makes you declare inputs, and keeping them right is your job. - Native Windows. vx runs under WSL2.
- A stable release with a support policy, today. vx is pre-alpha: 0.0.x on npm, no support policy yet.
- A terminal UI and import boundaries. Both ship in Turborepo. vx streams plain blocks and leaves boundaries to your linter.
- Go, Cargo or uv in the graph. Turborepo has experimental support. vx runs JavaScript workspaces only.
Switch in one command
Section titled “Switch in one command”vx initvx run build --allvx init finds turbo.json and writes one vx.config.ts per package,
plus the workspace file, through @vzn/vx-migrate.
Anything Turbo can say that vx cannot becomes a TODO(vx-migrate)
comment, never a silent wrong value. Your package.json scripts are never
edited. Keep your remote cache with turboCache(): any server speaking
Turbo’s /v8/artifacts API, Vercel’s hosted cache included. The numbers
above are native vx config.
Do my Turbo flags still work?
vx run takes each Turbo flag as it is (--filter, --affected,
--concurrency), rewrites it to vx’s spelling, or refuses it and names the
vx way. None is dropped in silence.
Can I keep Vercel Remote Cache?
Yes. Set TURBO_TOKEN and TURBO_TEAM, declare turboCache(), and vx
stores its own artifacts there. Turbo cannot read what vx stores, and vx
does not read Turbo’s entries.
What does it cost? Nothing. vx is MIT, with no cloud and no account.
Do I need Bun? No. The npm package installs a prebuilt binary for Linux and macOS, x64 and arm64. Windows: use WSL2.