· monorepo / pnpm / turborepo

Công cụ monorepo tốt nhất — pnpm + Turborepo hay Nx?

Câu trả lời thẳng thắn cho lựa chọn mà hầu hết team TypeScript trì hoãn hàng tháng. Khi nào pnpm + Turborepo là đủ và khi nào Nx xứng đáng với chi phí setup.

Bởi

2.260 từ · 12 phút đọc

Nếu bạn có ≤5 developer và task graph monorepo đơn giản, dùng pnpm + Turborepo. Setup mất 15 phút, remote cache miễn phí qua Vercel, và không cần tái cơ cấu bất cứ thứ gì đang có.

Nếu bạn có 6+ developer, CI phức tạp, hoặc kế hoạch mở rộng, dùng pnpm + Nx. 2–3 giờ setup thêm sẽ hoàn vốn trong một tuần nhờ affected detection. Với 80% trường hợp — team TypeScript cỡ vừa trên GitHub Actions — Nx vượt lên khi vượt ~5 người.

Bài này dành cho ai

Team TypeScript đang dùng monorepo (dự án mới hoặc đang cân nhắc migrate) chạy CI trên GitHub Actions. Nếu bạn là developer solo với ba package, chọn Turborepo — bạn không cần phần còn lại của bài này.

Chúng tôi đã kiểm tra gì

  • pnpm workspaces v10 — lớp nền bắt buộc cho cả hai công cụ
  • Turborepo 2.7 (tháng 12 năm 2025) — giấy phép MIT, nhân Rust
  • Nx 21 (2025) — giấy phép MIT, kết hợp TypeScript + Rust

Dữ liệu benchmark đến từ hai nguồn:

  1. Thí nghiệm migrate của Navanath — repo thực 12 package (3 ứng dụng Next.js, 4 thư viện React, 2 Node API, 3 config package), 8 kỹ sư, GitHub Actions với pnpm. Cấu hình máy không được công bố — lưu ý bên dưới.
  2. vsavkin/large-monorepo — MacBook Pro M2Max, tháng 2 năm 2025, ~26.000 component tổng hợp. Được bảo trì bởi Victor Savkin, đồng sáng lập Nx. Chúng tôi chỉ dùng dữ liệu này theo hướng định tính.

Khi một benchmark có vấn đề về nguồn gốc, chúng tôi nói rõ.

Nhận xét

Thời gian setup

Lời hứa của Turborepo là chính xác: bạn có thể thêm nó vào một pnpm workspace hiện có trong 15 phút. Thêm một file turbo.json, chạy hai lệnh để bật remote caching, xong. Không cần tái cơ cấu.

Config v2.x đổi tên pipeline thành tasks — mọi hướng dẫn trước năm 2024 đều sai điểm này:

{
  "$schema": "https://turborepo.dev/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"],
      "inputs": ["src/**", "package.json"]
    },
    "test": {
      "dependsOn": ["build"],
      "cache": true
    }
  },
  "envMode": "strict"
}

dependsOn: ["^build"] chạy build trong tất cả dependency trước (topological). envMode: "strict" có nghĩa là chỉ những env var được khai báo tường minh mới ảnh hưởng đến cache key — giá trị mặc định an toàn trong v2.

Nx mất nhiều thời gian hơn. Thí nghiệm migrate của Navanath ghi lại ~2.5 giờ cho một repo 12 package: cập nhật path reference, viết lại build script, tái cơ cấu theo yêu cầu của generator. File nx.json tối giản chỉ có 3 dòng, nhưng Nx tối giản bỏ qua phần lớn lý do đáng để dùng Nx.

Cán cân đảo chiều về sau: người dùng Turborepo rốt cuộc sẽ tự xây scaffolding script, kiểm tra tính nhất quán phiên bản, và tooling migrate. Generator của Nx xử lý những thứ đó ngay từ đầu. Điểm hòa vốn xấp xỉ khi developer thứ ba hoặc thứ tư gia nhập và bắt đầu tạo package mới thường xuyên.

Nếu bạn đã chọn Turborepo, xem hướng dẫn cài đặt pnpm + Turborepo từ đầu của chúng tôi để bootstrap monorepo đúng cách.

Remote caching

Remote cache của Turborepo miễn phí qua Vercel ở mọi gói, kể cả gói miễn phí. Setup:

npx turbo login
npx turbo link

Nếu muốn tự host, cache API là mở và có tài liệu đầy đủ. Các implementation cộng đồng hỗ trợ S3, Cloudflare R2, GCS, và Azure Blob. Turborepo 2.5 phát hành một viewer thân thiện cho cache API spec hiện tại, giúp đánh giá các tùy chọn self-hosting dễ hơn.

Remote caching có thực sự hữu ích không? Có, và có thể đo được. Mercari Engineering công bố case study về remote cache Turborepo tự host và báo cáo Turbo task duration ngắn hơn ~50% và tổng job duration ngắn hơn ~30% trên một large application build.

Gói miễn phí của Nx Cloud cho 50.000 credit mỗi tháng — đủ cho hầu hết team nhỏ đến vừa. Qua mức đó, gói Team tính $19/contributor cộng credit và phí per-concurrent-CI-connection. Gói Team bao gồm Manual DTE (phân công task tĩnh). Dynamic agent allocation — khi agent kéo task dựa trên CPU/RAM thực tế — chỉ có trong gói Enterprise với giá custom.

Sự khác biệt thực tế: remote caching của Turborepo không tốn gì ở mọi quy mô team. Nx Cloud miễn phí cho đến khi bạn lớn đủ để dùng các tính năng tương xứng với chi phí — và những tính năng đó có thực, đó là nội dung của phần tiếp theo.

Affected detection

Đây là điểm hai công cụ phân kỳ rõ nhất trong CI hàng ngày.

Trên repo 12 package của Navanath (không có thông số máy):

Kịch bảnTurborepoNx
Cold cache build2m 47s2m 31s
Warm cache build387ms421ms
Sau khi thay đổi một dòng48s43s
CI với affected detection~3–4 phút~1–2 phút

Warm cache gần như tương đương — cả hai đều nhanh. Cold cache Nx nhỉnh hơn đôi chút. Khoảng cách thực sự nằm ở CI: Nx phân tích project graph để xác định package nào PR thực sự chạm vào, rồi chỉ chạy những package đó. Trên repo 12 package, điều đó thường có nghĩa là chạy 3–4 package mỗi PR thay vì cả 12, cắt CI từ ~14 phút xuống ~1–2 phút.

Turborepo chưa có affected detection tích hợp tính đến v2.7. Bạn có thể dùng git diff + --filter để tự scripting, nhưng bạn đang tự xây lại thứ Nx đã có sẵn.

Benchmark vsavkin cho thấy Nx ở 192ms so với Turborepo ở 1.432ms — chênh lệch 7.5× trên repo tổng hợp 26.000 component. Lý do kiến trúc có thực: daemon của Nx tính toán trước project graph và cache file hash trong .nx/cache; các lần chạy sau bỏ qua việc re-hash những file đã thấy. Daemon của Turborepo ít tích cực hơn ở điểm này. Nhưng benchmark này do đồng sáng lập Nx bảo trì, trên một repo được thiết kế để khai thác đúng lợi thế này. Con số thực tế nhỏ hơn. Dùng vsavkin theo hướng định tính, không phải làm tiêu đề.

CI phân tán

Với CI một máy, khoảng cách khiêm tốn. Benchmark của chính Nx cho thấy nhanh hơn ~16% với setup tương đương — nguồn gốc có thiên vị, định tính thì có lý.

Khoảng cách thú vị hơn nằm ở CI phân tán. Turborepo phân chia task trước: trước khi bắt đầu thực thi, mỗi agent nhận một batch task. Nếu một batch có task nặng, agent đó chạy 18 phút trong khi các agent kia xong trong 3 phút. Dynamic task execution của Nx Cloud Enterprise phân phối task dựa trên CPU và RAM thực tế — khi một agent xong, nó kéo task tiếp theo. Không có agent nhàn rỗi. (Gói Team bao gồm Manual DTE, phân công batch agent tĩnh.)

Benchmark CI 4 máy của Nx: 19m 18s (Turborepo) so với 9m 20s (Nx). Benchmark đó do Nx tự công bố và khoảng cách có thể bị phóng đại. Nhưng sự khác biệt kiến trúc là có thực, và nó nhân lên khi bạn thêm máy.

Nếu CI là điểm nghẽn và bạn đang chạy nhiều agent, dynamic scheduling của Nx có ý nghĩa. Với một hoặc hai máy thì không.

Nx generators và lock-in

nx g @nx/next:app my-app tạo scaffold một dự án Next.js đầy đủ: TypeScript config, Jest setup, ESLint rules, package.json — tất cả chính xác và nhất quán với mọi package khác trong repo. nx migrate latest cập nhật toàn bộ plugin config trên mọi package khi bạn nâng cấp.

Nếu code generation và automated migration là workflow thường xuyên, những thứ này thực sự hữu ích. Nếu không, chúng là overhead.

Lock-in: Nx executor thay thế việc gọi công cụ trực tiếp. @nx/webpack:webpack trong targets của bạn có nghĩa là rời đi yêu cầu viết lại target, không chỉ đổi dependency. Phiên bản plugin gắn với Nx core — major upgrade cần bump đồng bộ tất cả plugin. Debug cache miss đặc thù Nx khó hơn vì plugin layer thêm một lớp gián tiếp.

Bắt đầu với Nx nếu bạn đang greenfield và muốn scaffolding có quan điểm rõ ràng. Tránh nó nếu bạn có một build setup được tinh chỉnh cẩn thận mà bạn không muốn generator đụng vào.

pnpm catalog — dùng bất kể bạn chọn task runner nào

pnpm 9.5 giới thiệu catalog; pnpm 10.x ổn định hóa chúng. Đáng đề cập riêng vì đây là cải thiện thực sự cho trải nghiệm phát triển, hoạt động với Turborepo, Nx, hoặc không có gì cả.

Trước catalog, giữ react: "^18.3.1" nhất quán trên 20 package trong một monorepo cần một script hoặc sự kỷ luật. Với catalog, chỉ cần một dòng trong pnpm-workspace.yaml:

catalog:
  react: ^18.3.1
  typescript: ^5.4.0

Và trong mỗi package.json:

{
  "dependencies": {
    "react": "catalog:"
  }
}

Nâng cấp React trên toàn bộ monorepo là đổi một dòng trong một file. Khi publish, catalog: resolve về phiên bản được ghim — trong suốt với registry của npm.

pnpm 10.x thêm catalogMode để bắt buộc dùng catalog và cleanupUnusedCatalogs để xóa entry không dùng. Nếu bạn đang ở pnpm 9.5 trở lên, hãy dùng catalog. Opt-in, tương thích ngược, loại bỏ một vấn đề thực sự.

Còn Lerna?

Không, đừng dùng cho dự án mới.

Niche hiện tại của Lerna là versioning phối hợp và publish lên npm. lerna version + lerna publish vẫn là công cụ tốt nhất cho workflow cụ thể đó. Lerna 9 (tháng 9 năm 2025) thêm OIDC Trusted Publishing, loại bỏ static npm token trong CI — đây là cải thiện thực nếu bạn đang publish package.

Về task running và caching, Lerna tụt hậu so với cả Turborepo lẫn Nx. Đừng bắt đầu monorepo mới với nó trừ khi publish npm phối hợp là yêu cầu chính và changesets hoặc nx release không phù hợp.

Kết luận

Dùng pnpm + Turborepo nếu:

  • Bạn có ≤5 developer
  • Task graph đơn giản (build → test → lint, không có phụ thuộc sâu)
  • Bạn muốn thêm caching vào workspace hiện có mà không cần tái cơ cấu gì
  • Bạn đã dùng Vercel hoặc chấp nhận tạo tài khoản Vercel miễn phí cho remote caching

Dùng pnpm + Nx nếu:

  • Bạn có 6+ developer
  • CI là điểm nghẽn và bạn muốn affected detection mà không phải tự scripting
  • Bạn muốn generator để scaffolding và nx migrate để nâng cấp
  • Bạn đang trên đà tăng trưởng — overhead của Nx hoàn vốn nhanh hơn khi team mở rộng

Với 80% trường hợp — team TypeScript cỡ vừa trên GitHub Actions, greenfield hoặc migrate sớm — Nx vượt lên sau ~5 người, chủ yếu nhờ affected detection. Tiết kiệm CI từ việc chạy 3 package mỗi PR thay vì 12 đủ để hoàn vốn chi phí setup trong một tuần phát triển thực tế.

Lưu ý

Lock-in của Nx là có thực. Nx executor và generator tái cấu trúc build config theo cách mà việc tháo gỡ mất nhiều giờ. Turborepo dễ thêm vào hơn và cũng dễ gỡ ra hơn.

Giá DTE của Nx không rõ ràng. Hệ thống credit khó ước tính nếu không biết chính xác task profile của team. Gói miễn phí 50k/tháng đủ cho nhiều team nhỏ đến vừa, nhưng dynamic task feeding đầy đủ yêu cầu Enterprise. Đừng cam kết với Nx Cloud DTE trước khi ước tính mức credit tiêu thụ hàng tháng.

Turborepo không có affected detection tích hợp. Trên repo lớn với nhiều package độc lập, điều này tích lũy. Bạn có thể scripting quanh nó, nhưng bạn đang tự xây thứ Nx đã có sẵn. Để biết đầy đủ những vấn đề tích lũy âm thầm sau khi cài đặt, đọc Các bẫy monorepo Turborepo chúng tôi học được theo cách khó.

Nguồn gốc benchmark. Benchmark vsavkin được bảo trì bởi đồng sáng lập Nx trên một repo được tối ưu cho lợi thế daemon và disk-cache của Nx — dùng theo hướng định tính. Con số của Navanath đến từ một migrate thực 12 package nhưng thiếu thông số máy. Không cái nào trung lập. Coi các con số như chỉ dẫn, không phải kết luận.

Link affiliate Vercel ở trên. Xem phần disclosure ở đầu bài. Khuyến nghị remote cache của Turborepo sẽ là như vậy dù có link hay không — miễn phí với mọi tài khoản Vercel, kể cả gói miễn phí, và tùy chọn self-host cũng khả thi.

Tài liệu tham khảo