Why Setting Up a Worktree Can Be Slow on a Mac
Linux installed dependencies faster in all four repositories I tested. But building Next.js on Linux also changed files inside pnpm's package cache, breaking the next offline install. The Mac's cached files stayed intact.
That matters alongside Theo's 29.7-second Mac versus 3.8-second Linux chart. There is a real wait to investigate. There are also different file-sharing choices behind those numbers, and the fastest setting needs more than a successful install.
Four repositories, one physical Mac
These are package-install times in fresh Git worktrees. Creating the checkout is a separate step.
| Repository | macOS median | Linux VM median |
|---|---|---|
| T3 Code | 8.12 s | 2.10 s |
| Next.js | 4.56 s | 1.56 s |
| Vite | 1.82 s | 0.48 s |
| SvelteKit | 0.59 s | 0.13 s |
Each cell contains five runs. T3 came from an earlier 30-install block; the other repositories added 90 trials across defaults, four installer workers and shared dependencies. Those blocks remain separate in the results and methods.
All used Node 26.12.1 and their own pinned pnpm 12 version. Package and build caches were populated; allowed install scripts stayed enabled. Timed installs used --offline --frozen-lockfile. CI mode skipped Next's optional native-compiler download. Checks ran outside the timer.
The M5 Mac had ten logical CPUs and 24 GB RAM; its Linux guest had four virtual CPUs and 4 GB. Linux's project and cache lived inside its own filesystem. This compares software and storage paths with different resources, rather than isolating APFS alone.
Theo's disclosed method also describes five machines, averaging their individual middle timings, with Linux's after-fix runs. His price caption isn't a controlled two-machine experiment.
The Mac creates independent files
For ordinary packages, pnpm's default Linux importer starts with hardlinks: multiple names for the same file. macOS starts with clones: independent files that initially share their disk data.
Current pnpm already clones eligible whole package directories. My Mac trace recorded 1,621 successful directory clones. The resulting T3 dependency tree still contained roughly 180,000 regular files. A process sample repeatedly found workers inside the filesystem's clone operation.
Sharing an already-installed tree avoids much of that repeated work. It also changes what happens when something writes into a dependency.
Next.js exposed the write problem
Next uses @vercel/ncc@0.38.4. Its packaged loaders write compiled-code caches back into their own package when the process exits.
The Linux build changed four packaged files. They were the same files as pnpm's cached copies, so the cache changed too. On Mac, the installed copies changed while the original cache bytes still matched the published package.
I reduced this to one package compiling a tiny program. Linux's default hardlinks reproduced the failure: compilation worked, then the next offline install failed. With --package-import-method=clone-or-copy, compilation and the next install both passed; the cache stayed unchanged.
The failed trial remains in the ledger. Restoring that exact package repaired the original checkout. Disabling integrity checks would conceal the problem.
What helped without weakening checks
Four installer workers lowered every Mac median in these tests. T3 dropped from 8.12 to 6.55 seconds; Next.js from 4.56 to 3.90 seconds.
RAYON_NUM_THREADS=4 pnpm install --offline --frozen-lockfile
Measure it first. SvelteKit's Linux median got worse with that setting, and its default Mac/Linux gap was only 0.46 seconds anyway.
Also check cache placement:
df -h . "$(pnpm store path)"
A cache on another filesystem forces different import work. My separate XFS control dropped from 7.6 to 1.4 seconds when the cache moved onto the checkout's filesystem.
Shared installed packages were faster still: Mac medians reached 0.68 seconds for Vite, 0.28 for SvelteKit and 0.96 for Next.js. But Vite and Next failed typechecks. T3 had previously produced 64 errors.
For T3, I declared four libraries' optional React type dependencies. Its original compiler configuration then passed on both systems, alongside 4,302 unit tests and the web build. Separate React 18 and 19 controls found their own type files and still rejected an invalid prop. No special compiler paths or weaker checking were needed.
Vite and Next also exposed missing type dependencies. Their version-scoped metadata patches preserve package versions. SvelteKit passed its core typecheck and unit suite unchanged. Next's complete Linux build remains unaccepted; the 4 GB guest also hit memory limits. Earlier T3 browser runs passed 471/472 tests in both layouts, failing the same scrolling assertion.
Keep writes private too
A fully read-only cache protected the small ncc case, but refused a fresh T3 worktree that needed a new local-package entry.
I then tested private writable views over a frozen cache: Linux overlays and Mac disk-image shadow files. Two simultaneous T3 views passed normal typechecks; unit suites and web builds passed on both systems. A write in one view left the other unchanged. The entire Mac base image also kept its original hash.
That's a working isolation prototype, with cache preparation and mount costs. It hasn't established a general Mac speed win. The simpler supported choice for independent package files is clone-or-copy; on filesystems without cloning, that means copying.
Compare installation time, typechecking and write isolation together. A faster install can be doing less work because it promises less isolation.
Glossary
| Term / claim | Primary source | Date |
|---|---|---|
| Worktree: another checkout sharing Git history | Git manual | Checked Oct 28, 2026 |
| Platform import order and private copies | pnpm import methods | Checked Oct 28, 2026 |
| Shared packages and trust boundary | Global virtual store | Checked Oct 28, 2026 |
| Read-only stores need explicit support | pnpm frozenStore | Checked Oct 28, 2026 |