ถ้าคุณเคยทำงานกับโปรเจกต์ที่มีหลาย Package หลาย Service หรือหลาย App ที่ต้องแชร์โค้ดร่วมกัน คุณน่าจะเคยเจอปัญหาเหล่านี้: แก้โค้ด Shared Library แล้วต้องไป Publish อัปเดตทีละ Repo, เวอร์ชันไม่ตรงกัน หรือ CI/CD Pipeline ที่ซับซ้อนจนจัดการไม่ไหว ทั้งหมดนี้คือเหตุผลที่ Monorepo เกิดขึ้นมา และในปี 2026 เครื่องมืออย่าง Turborepo และ Nx ทำให้การจัดการ Monorepo เป็นเรื่องง่ายกว่าที่เคย
บทความนี้จะพาคุณเข้าใจ Monorepo ตั้งแต่แนวคิดพื้นฐาน เปรียบเทียบกับ Polyrepo ไปจนถึงการตั้งค่า Turborepo และ Nx แบบ Step-by-step พร้อม CI/CD Pipeline ที่ใช้งานได้จริงในทีม
Monorepo คืออะไร?
Monorepo (Monolithic Repository) คือแนวทางการจัดการ Source Code ที่เก็บหลาย Package, หลาย Application หรือหลาย Service ไว้ใน Git Repository เดียวกัน แทนที่จะแยกเป็นหลาย Repo ตัวอย่างเช่น คุณอาจมี Frontend App, Backend API, Shared UI Components และ Utility Functions ทั้งหมดอยู่ใน Repo เดียว
บริษัทเทคโนโลยีชั้นนำหลายแห่งใช้ Monorepo ในการจัดการโค้ดของตนเอง เช่น Google ที่เก็บโค้ดทั้งหมดไว้ใน Repo เดียวกว่าสองพันล้านบรรทัด, Meta (Facebook) ที่ใช้ Monorepo สำหรับ Web Frontend ทั้งหมด, Microsoft ที่เก็บ Windows Source Code ใน Monorepo ขนาดมหาศาล รวมถึง Vercel ที่เป็นผู้สร้าง Turborepo เองก็ใช้ Monorepo ในการพัฒนาผลิตภัณฑ์ของตัวเอง
โครงสร้าง Monorepo ทั่วไป
my-monorepo/
├── apps/
│ ├── web/ # Next.js Frontend
│ ├── mobile/ # React Native App
│ └── api/ # Express/NestJS Backend
├── packages/
│ ├── ui/ # Shared UI Components
│ ├── utils/ # Shared Utilities
│ ├── config/ # Shared ESLint, TS Config
│ └── database/ # Prisma Schema & Client
├── turbo.json # Turborepo Config
├── package.json # Root Package
└── pnpm-workspace.yaml # Workspace Config
Monorepo vs Polyrepo: เปรียบเทียบให้ชัด
ก่อนจะตัดสินใจใช้ Monorepo คุณต้องเข้าใจความแตกต่างระหว่าง Monorepo กับ Polyrepo (การแยก Repo แบบดั้งเดิม) ให้ชัดเจนก่อน เพราะแต่ละแนวทางมีข้อดีข้อเสียที่แตกต่างกันอย่างสิ้นเชิง
| หัวข้อ | Monorepo | Polyrepo |
|---|---|---|
| Code Sharing | แชร์โค้ดง่าย Import ตรงจาก Package | ต้อง Publish แล้ว Install ทีละ Repo |
| Atomic Commits | แก้หลาย Package ใน Commit เดียว | ต้อง Commit แยกแต่ละ Repo |
| CI/CD | Pipeline เดียว แต่ต้องฉลาดพอจะ Build เฉพาะที่เปลี่ยน | แต่ละ Repo มี Pipeline แยก ง่ายแต่ซ้ำซ้อน |
| Dependency Management | เวอร์ชันตรงกันเสมอ ไม่มี Version Mismatch | อาจเจอปัญหาเวอร์ชันไม่ตรงกัน |
| Repo Size | อาจใหญ่มากจนช้า ต้องใช้ Sparse Checkout | แต่ละ Repo เล็ก Clone เร็ว |
| Permissions | ควบคุมสิทธิ์ระดับ Folder ยากกว่า | แยกสิทธิ์ตาม Repo ได้ง่าย |
| Tooling | ต้องใช้ Turborepo/Nx ช่วย | ใช้เครื่องมือมาตรฐานได้เลย |
ข้อดีของ Monorepo ที่ทำให้ทีมเลือกใช้
การใช้ Monorepo มีข้อได้เปรียบหลายประการที่ทำให้ทีมพัฒนาขนาดกลางถึงใหญ่เลือกใช้แนวทางนี้มากขึ้นเรื่อยๆ ในปี 2026
1. Code Sharing ที่ราบรื่น
แทนที่จะต้อง Publish Package ไปยัง npm Registry แล้วค่อย Install ในแต่ละ Repo คุณสามารถ Import โค้ดจาก Package อื่นได้โดยตรงราวกับเป็นโค้ดใน Folder เดียวกัน เมื่อแก้ไข Shared Component แล้ว ทุก App ที่ใช้จะได้รับการเปลี่ยนแปลงทันทีโดยไม่ต้อง Publish ใหม่
2. Atomic Commits ข้าม Package
สมมติคุณต้องเปลี่ยน API Response Format ใน Backend และอัปเดต Frontend ให้รองรับ Format ใหม่ ใน Monorepo คุณทำได้ใน Commit เดียว ไม่ต้องเปิด PR สอง Repo แล้วพยายาม Merge ให้พร้อมกัน ซึ่งเป็นฝันร้ายที่นักพัฒนาหลายคนคงเคยประสบมาก่อน
3. Unified CI/CD Pipeline
มี Pipeline เดียวที่ดูแลทุกอย่าง เมื่อรวมกับเครื่องมืออย่าง Turborepo หรือ Nx ที่สามารถตรวจจับได้ว่า Package ไหนถูกแก้ไขและ Build เฉพาะส่วนที่เกี่ยวข้อง ทำให้ CI เร็วพอๆ กับ Polyrepo แต่จัดการง่ายกว่ามาก
4. Consistent Tooling
ESLint Config, TypeScript Config, Prettier Config ทั้งหมดแชร์จากที่เดียว ไม่ต้องก็อปไปวางทุก Repo แล้วพยายาม Sync ให้ตรงกัน เมื่ออัปเดต Config ที่เดียวก็มีผลทุกที่ทันที
ความท้าทายของ Monorepo
แม้ว่า Monorepo จะมีข้อดีมากมาย แต่ก็มาพร้อมกับความท้าทายที่ต้องเตรียมรับมือ
Build Time ที่อาจนานขึ้น
เมื่อมีหลาย Package ในที่เดียว ถ้าไม่มีเครื่องมือที่ฉลาดพอ CI อาจ Build ทุกอย่างทุกครั้ง แม้ว่าจะแก้ไขแค่ไฟล์เดียว นี่คือเหตุผลหลักที่ Turborepo และ Nx ถูกสร้างขึ้นมา เพื่อแก้ปัญหานี้ด้วย Caching และ Affected Analysis
Permission Management
ใน Polyrepo คุณสามารถกำหนดสิทธิ์เข้าถึงแต่ละ Repo ได้ง่ายๆ ผ่าน GitHub Settings แต่ใน Monorepo ทุกคนเห็นทุกอย่าง ต้องใช้ CODEOWNERS File และ Branch Protection Rules เข้ามาช่วย
Repository Size
เมื่อเวลาผ่านไป Monorepo อาจมีขนาดใหญ่มากจน git clone ช้า ทางแก้คือใช้ git sparse-checkout หรือ git shallow clone เพื่อดึงเฉพาะส่วนที่ต้องการ
Turborepo คืออะไร?
Turborepo เป็น High-Performance Build System สำหรับ JavaScript/TypeScript Monorepo ที่สร้างโดย Jared Palmer และถูก Vercel ซื้อไปในปี 2021 ปัจจุบันเป็นหนึ่งในเครื่องมือยอดนิยมที่สุดสำหรับจัดการ Monorepo โดยเฉพาะในระบบนิเวศของ Next.js และ React
ฟีเจอร์เด่นของ Turborepo
- Remote Caching: Cache ผลลัพธ์ของ Build ไว้บน Cloud เมื่อ Teammate Build สิ่งเดียวกัน ไม่ต้อง Build ซ้ำ ดึง Cache มาใช้ได้เลย
- Parallel Execution: รัน Task ที่ไม่ขึ้นต่อกันพร้อมกันอัตโนมัติ ใช้ CPU ทุก Core ให้เต็มประสิทธิภาพ
- Task Pipeline: กำหนดลำดับ Dependency ระหว่าง Task ได้ เช่น ต้อง Build Shared Package ก่อนแล้วค่อย Build App
- Incremental Builds: Build เฉพาะ Package ที่เปลี่ยนแปลงจริงๆ ไม่ Build ซ้ำสิ่งที่ไม่เปลี่ยน
- Pruned Subsets: สร้าง Docker Image ที่มีเฉพาะ Package ที่จำเป็น ลดขนาด Image ได้มาก
ติดตั้งและตั้งค่า Turborepo
# สร้าง Monorepo ใหม่ด้วย Turborepo
npx create-turbo@latest my-monorepo
# หรือเพิ่ม Turborepo เข้าโปรเจกต์ที่มีอยู่แล้ว
npm install turbo --save-dev
turbo.json Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"],
"outputs": [],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"dev": {
"cache": false,
"persistent": true
},
"type-check": {
"dependsOn": ["^build"]
}
}
}
pnpm-workspace.yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Root package.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test",
"type-check": "turbo run type-check"
},
"devDependencies": {
"turbo": "^2.4.0"
},
"packageManager": "pnpm@9.15.0"
}
Remote Caching กับ Vercel
หนึ่งในฟีเจอร์ที่ทรงพลังที่สุดของ Turborepo คือ Remote Caching ซึ่งช่วยให้ทีมทั้งทีมแชร์ Build Cache ร่วมกันได้ เมื่อคนหนึ่ง Build Package แล้ว คนอื่นไม่ต้อง Build ซ้ำอีก แค่ดึง Cache มาใช้ได้เลย
# เชื่อมต่อกับ Vercel Remote Cache
npx turbo login
npx turbo link
# ตอนนี้ทุกคนในทีมจะแชร์ Cache ร่วมกัน
# Build ครั้งแรกอาจใช้เวลา 2 นาที
# Build ครั้งต่อไป (Cache hit) อาจใช้เวลาแค่ 5 วินาที
# ดู Cache status
turbo run build --dry-run
turborepo-remote-cache Server ที่รองรับ S3-compatible Storage เช่น MinIO หรือ Cloudflare R2
Nx คืออะไร?
Nx (สร้างโดย Nrwl) เป็น Smart Build System และ Monorepo Tool ที่เน้นการจัดการโปรเจกต์ขนาดใหญ่ มีมาก่อน Turborepo และมีฟีเจอร์ครบกว่า เหมาะสำหรับทีมที่ต้องการเครื่องมือที่มี Built-in Support สำหรับหลาย Framework
ฟีเจอร์เด่นของ Nx
- Computation Caching: Cache ผลลัพธ์ของทุก Task ทั้ง Local และ Remote เหมือน Turborepo แต่มี Granularity สูงกว่า
- Affected Commands: วิเคราะห์ได้ว่า Package ไหนถูกกระทบจากการเปลี่ยนแปลง แล้ว Run Task เฉพาะส่วนที่จำเป็น ลดเวลา CI ได้มหาศาล
- Project Graph: สร้างกราฟแสดงความสัมพันธ์ระหว่าง Package ทั้งหมดแบบ Visual ทำให้เข้าใจ Architecture ได้ง่าย
- Generators: สร้าง Boilerplate Code ด้วยคำสั่งเดียว เช่น สร้าง Component, Module, Service ตาม Convention ของทีม
- Plugins: มี Plugin สำหรับ React, Angular, Node.js, Next.js, Nest.js, Storybook และอื่นๆ อีกมาก
ติดตั้งและใช้ Nx
# สร้าง Nx Workspace ใหม่
npx create-nx-workspace@latest my-workspace
# เพิ่ม Nx เข้าโปรเจกต์ที่มีอยู่
npx nx@latest init
# รัน Task
npx nx run my-app:build
npx nx run-many --target=build --all
npx nx affected --target=test # เฉพาะที่ถูกกระทบ
# ดู Project Graph
npx nx graph
nx.json Configuration
{
"$schema": "https://nx.dev/reference/nx-json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": ["default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)"],
"sharedGlobals": []
}
}
Turborepo vs Nx: เลือกตัวไหนดี?
นี่คือคำถามที่นักพัฒนาหลายคนสงสัย มาเปรียบเทียบกันอย่างละเอียดเพื่อช่วยในการตัดสินใจ
| หัวข้อ | Turborepo | Nx |
|---|---|---|
| Learning Curve | ง่าย ตั้งค่าน้อย เรียนรู้เร็ว | ซับซ้อนกว่า มีฟีเจอร์เยอะกว่า |
| Configuration | turbo.json ไฟล์เดียว | nx.json + project.json หลายไฟล์ |
| Caching | Local + Vercel Remote Cache | Local + Nx Cloud Remote Cache |
| Affected Analysis | ไม่มี Built-in ต้องใช้ --filter | มี nx affected ที่ทรงพลัง |
| Code Generation | ไม่มี | มี Generators ครบ |
| Plugin Ecosystem | น้อย เน้น Minimal | เยอะมาก ครบทุก Framework |
| Project Graph | ไม่มี Visual Tool | มี nx graph ที่สวยงาม |
| Language Support | JavaScript/TypeScript เท่านั้น | JS/TS, Go, Rust, Java และอื่นๆ |
| เหมาะกับ | ทีมเล็ก-กลาง ต้องการความเรียบง่าย | ทีมใหญ่ Enterprise ต้องการเครื่องมือครบ |
Workspace Tools: npm, pnpm, yarn
ทั้ง Turborepo และ Nx ไม่ได้จัดการ Dependency เอง แต่ใช้ Workspace Feature ของ Package Manager แทน ทำให้คุณต้องเลือก Package Manager ที่รองรับ Workspaces ด้วย
pnpm Workspaces (แนะนำ)
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
# ติดตั้ง Dependency สำหรับ Package เฉพาะ
pnpm add lodash --filter @myorg/utils
# รัน Script ใน Package เฉพาะ
pnpm --filter @myorg/web dev
# ติดตั้ง Dependency ทั้งหมด
pnpm install
npm Workspaces
// package.json
{
"workspaces": ["apps/*", "packages/*"]
}
// ติดตั้ง Dependency สำหรับ Package เฉพาะ
npm install lodash -w packages/utils
yarn Workspaces
// package.json
{
"workspaces": ["apps/*", "packages/*"]
}
// ติดตั้ง Dependency สำหรับ Package เฉพาะ
yarn workspace @myorg/utils add lodash
Internal Packages vs Published Packages
ใน Monorepo มี Package สองแบบที่ต้องเข้าใจ
Internal Packages
Package ที่ใช้ภายใน Monorepo เท่านั้น ไม่ได้ Publish ไป npm Registry ตั้งค่าง่ายมากเพราะไม่ต้อง Build ก่อน Import ได้ ใช้ "main" ชี้ไปที่ Source Code ตรงๆ หรือใช้ Bundler ของ App ที่ Consume มัน Transpile ให้
// packages/ui/package.json
{
"name": "@myorg/ui",
"version": "0.0.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
}
}
// apps/web/package.json — Import ได้เลย
{
"dependencies": {
"@myorg/ui": "workspace:*"
}
}
Published Packages
Package ที่ต้อง Publish ไป npm Registry เพื่อให้โปรเจกต์ภายนอกใช้ ต้อง Build เป็น JavaScript ก่อน Publish และต้องจัดการ Versioning อย่างเป็นระบบ
// packages/sdk/package.json
{
"name": "@myorg/sdk",
"version": "1.2.3",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts"
}
}
Changesets สำหรับ Versioning
Changesets เป็นเครื่องมือที่ช่วยจัดการ Versioning และ Changelog ใน Monorepo ได้อย่างเป็นระบบ แทนที่จะต้องจำว่า Package ไหนต้องขึ้น Version เท่าไหร่ Changesets จะช่วยจัดการให้
# ติดตั้ง Changesets
pnpm add -D -w @changesets/cli @changesets/changelog-github
# เริ่มต้นใช้งาน
pnpm changeset init
# สร้าง Changeset เมื่อมีการเปลี่ยนแปลง
pnpm changeset
# → เลือก Package ที่เปลี่ยน
# → เลือก Semver bump (patch/minor/major)
# → เขียน Summary ของการเปลี่ยนแปลง
# Apply Changesets → ขึ้น Version + Generate Changelog
pnpm changeset version
# Publish ไป npm
pnpm changeset publish
.changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "myorg/monorepo" }],
"commit": false,
"fixed": [],
"linked": [["@myorg/ui", "@myorg/utils"]],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}
Shared Packages: UI, Utils, Config
หัวใจของ Monorepo คือ Shared Packages ที่ทุก App สามารถใช้ร่วมกันได้ มาดูตัวอย่างการสร้าง Shared Package แต่ละประเภท
Shared UI Package
// packages/ui/src/button.tsx
import React from "react";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
}
export function Button({ variant = "primary", size = "md", children, ...props }: ButtonProps) {
return (
<button className={`btn btn-${variant} btn-${size}`} {...props}>
{children}
</button>
);
}
// packages/ui/src/index.ts
export { Button } from "./button";
export { Card } from "./card";
export { Input } from "./input";
Shared Config Package
// packages/config/eslint-preset.js
module.exports = {
extends: ["next", "turbo", "prettier"],
rules: {
"@next/next/no-html-link-for-pages": "off",
},
parserOptions: {
babelOptions: {
presets: [require.resolve("next/babel")],
},
},
};
// apps/web/.eslintrc.js — ใช้ Config จาก Package
module.exports = {
root: true,
extends: ["@myorg/config/eslint-preset"],
};
Shared TypeScript Config
// packages/config/tsconfig.base.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true
}
}
// apps/web/tsconfig.json
{
"extends": "@myorg/config/tsconfig.base.json",
"compilerOptions": {
"jsx": "preserve",
"plugins": [{ "name": "next" }]
},
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
CI/CD สำหรับ Monorepo (GitHub Actions)
การตั้งค่า CI/CD สำหรับ Monorepo ต้องฉลาดพอที่จะ Build เฉพาะสิ่งที่เปลี่ยนแปลง ไม่งั้น CI จะช้ามากเมื่อ Repo โตขึ้น
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # สำหรับ turbo --filter
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
# Turborepo จะใช้ Remote Cache อัตโนมัติ
- run: pnpm turbo run lint test build
# หรือถ้าใช้ Nx
# - run: npx nx affected --target=lint --base=origin/main
# - run: npx nx affected --target=test --base=origin/main
# - run: npx nx affected --target=build --base=origin/main
Task Dependencies และ Pipeline
การเข้าใจ Task Dependencies เป็นสิ่งสำคัญมากในการใช้งาน Turborepo ให้มีประสิทธิภาพ เพราะถ้าตั้ง Pipeline ผิด อาจ Build ล้มเหลวหรือช้ากว่าที่ควร
// turbo.json — ตัวอย่าง Pipeline ที่ซับซ้อน
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["src/**", "package.json", "tsconfig.json"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": ["src/**", "test/**"]
},
"lint": {
"outputs": []
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"outputs": [],
"cache": false
},
"db:migrate": {
"cache": false
}
}
}
ลำดับการทำงาน: เมื่อรัน turbo run deploy Turborepo จะวิเคราะห์ Pipeline แล้วรัน lint (ไม่มี Dependency) ก่อนพร้อมกับ build ของ Package ที่ไม่ขึ้นกับใคร จากนั้น build Package ที่ต้องรอ แล้ว test (ต้องรอ build) แล้วสุดท้ายคือ deploy ที่ต้องรอทุกอย่างเสร็จ ทั้งหมดนี้เกิดขึ้นอัตโนมัติ
Code Ownership ด้วย CODEOWNERS
ใน Monorepo ที่มีหลายทีมทำงานร่วมกัน CODEOWNERS File ช่วยกำหนดว่าใครเป็นเจ้าของโค้ดส่วนไหน เมื่อมี PR ที่แก้ไขโค้ดในส่วนที่มีเจ้าของ GitHub จะเพิ่ม Reviewer อัตโนมัติ
# .github/CODEOWNERS
# ทีม Frontend เป็นเจ้าของ Web App
apps/web/ @myorg/frontend-team
packages/ui/ @myorg/frontend-team
# ทีม Backend เป็นเจ้าของ API
apps/api/ @myorg/backend-team
packages/database/ @myorg/backend-team
# ทีม Platform เป็นเจ้าของ Config และ CI
packages/config/ @myorg/platform-team
.github/ @myorg/platform-team
turbo.json @myorg/platform-team
# Shared Utils ต้องได้รับ Review จากทั้งสองทีม
packages/utils/ @myorg/frontend-team @myorg/backend-team
เมื่อ Monorepo เป็น Overkill
Monorepo ไม่ใช่คำตอบสำหรับทุกสถานการณ์ มีหลายกรณีที่ Polyrepo เหมาะกว่า และการใช้ Monorepo โดยไม่จำเป็นอาจสร้างปัญหามากกว่าแก้ปัญหา
ไม่ควรใช้ Monorepo เมื่อ:
- ทำงานคนเดียวหรือทีมเล็กมาก (1-3 คน) ที่มีแค่ 1-2 Package — ความซับซ้อนของ Monorepo ไม่คุ้มกับประโยชน์ที่ได้
- แต่ละโปรเจกต์ไม่เกี่ยวข้องกัน และไม่ได้แชร์โค้ดร่วมกันเลย ไม่มีเหตุผลที่จะเอามาอยู่ Repo เดียว
- ใช้คนละภาษา Programming ที่มี Build System ต่างกันสิ้นเชิง เช่น Python Project กับ Go Project ที่ไม่แชร์อะไรเลย
- ต้องการ Permission แยกเด็ดขาด เช่น Outsource ทีมที่ไม่ควรเห็นโค้ดของทีมอื่น
- ทีมยังไม่คุ้นเคยกับ Git การเพิ่ม Complexity ของ Monorepo อาจทำให้ทีมสับสนมากขึ้น
ควรใช้ Monorepo เมื่อ:
- มีหลาย Package ที่แชร์โค้ดร่วมกัน
- ต้องการ Atomic Commits ข้าม Package
- ทีมมีขนาดกลางขึ้นไป (5+ คน) ที่ทำงานกับหลาย Package
- ต้องการ Consistent Tooling ทั้งโปรเจกต์
- มี Shared UI Library ที่หลาย App ใช้ร่วมกัน
Turborepo Filter และ Scoped Tasks
เมื่อ Monorepo มีหลาย Package บางทีคุณต้องการรัน Task เฉพาะบาง Package ไม่ใช่ทั้งหมด Turborepo มี --filter Flag ที่ทรงพลังมาก
# รัน build เฉพาะ web app
turbo run build --filter=@myorg/web
# รัน build เฉพาะ web app และ Dependencies ของมัน
turbo run build --filter=@myorg/web...
# รัน test เฉพาะ Package ที่เปลี่ยนตั้งแต่ main
turbo run test --filter=...[origin/main]
# รัน lint เฉพาะ Package ใน apps/ Directory
turbo run lint --filter="./apps/*"
# Combine filters
turbo run build --filter=@myorg/web --filter=@myorg/api
Docker กับ Monorepo (Pruned Subsets)
เมื่อต้อง Deploy App จาก Monorepo ด้วย Docker คุณไม่ต้องการ COPY ทั้ง Monorepo เข้า Image เพราะจะทำให้ Image ใหญ่มาก Turborepo มีคำสั่ง turbo prune ที่ช่วยสร้าง Subset ที่มีเฉพาะ Package ที่จำเป็น
# Dockerfile
FROM node:22-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app
RUN npm install -g turbo pnpm
COPY . .
RUN turbo prune @myorg/web --docker
FROM base AS installer
WORKDIR /app
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
COPY --from=builder /app/out/full/ .
RUN pnpm turbo run build --filter=@myorg/web
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer /app/apps/web/public ./apps/web/public
USER nextjs
EXPOSE 3000
CMD ["node", "apps/web/server.js"]
Best Practices สำหรับ Monorepo ในปี 2026
- ใช้ pnpm เป็น Package Manager เพราะเร็วและประหยัดพื้นที่กว่า npm/yarn ใน Monorepo
- ตั้ง Remote Caching ตั้งแต่วันแรก เพื่อให้ทุกคนในทีมได้ประโยชน์จาก Cache ร่วมกัน
- กำหนด inputs และ outputs ใน turbo.json ให้ถูกต้อง เพื่อให้ Cache แม่นยำ
- ใช้ CODEOWNERS เพื่อกำหนดความรับผิดชอบและ Auto-assign Reviewers
- แยก Internal Packages ที่ไม่จำเป็นต้อง Build ออกจาก Published Packages ที่ต้อง Build
- ตั้ง Changeset Bot ใน CI เพื่อเตือนเมื่อ PR ไม่มี Changeset สำหรับ Published Packages
- ใช้ Turborepo --dry-run เพื่อดูว่า Task ไหนจะถูกรันก่อน Commit จริง
- ตั้ง Type Checking แยกเป็น Task ต่างหากจาก Build เพื่อให้ Cache ทำงานได้ดีกว่า
สรุป
Monorepo เป็นแนวทางที่ทรงพลังสำหรับการจัดการ Multi-Package Project โดยเฉพาะเมื่อมีหลาย App ที่แชร์โค้ดร่วมกัน ในปี 2026 เครื่องมืออย่าง Turborepo และ Nx ทำให้ความท้าทายดั้งเดิมของ Monorepo เช่น Build Time ที่นาน หรือ CI ที่ซับซ้อน กลายเป็นเรื่องที่จัดการได้ง่ายด้วย Caching, Parallel Execution และ Affected Analysis
สำหรับทีมที่เพิ่งเริ่มต้นแนะนำให้ลอง Turborepo ก่อนเพราะตั้งค่าน้อยและเรียนรู้เร็ว จากนั้นถ้าต้องการฟีเจอร์ขั้นสูงอย่าง Affected Commands หรือ Code Generators ค่อยย้ายไป Nx การจัดการ Monorepo ที่ดีจะช่วยให้ทีมของคุณทำงานเร็วขึ้น ลดข้อผิดพลาด และสร้างผลิตภัณฑ์ที่มีคุณภาพสูงขึ้นในที่สุด
