--- id: ts-monorepo-patterns title: TS Monorepo — pnpm workspace / Turborepo / Nx category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [typescript, monorepo, pnpm, turborepo, vibe-coding] tech_stack: { language: "TypeScript / pnpm", applicable_to: ["Backend", "Frontend"] } applied_in: [] aliases: [pnpm workspace, Turborepo, Nx, project references, workspaces] --- # TS Monorepo > 여러 패키지 한 repo. **pnpm workspace + TS project references + Turborepo (cache) 가 표준 조합**. Nx 는 더 강력하지만 무겁다. shared 패키지 = source 그대로 import. ## 📖 핵심 개념 - Workspace: pnpm/yarn/npm 의 다중 패키지 등록. - Hoisting: pnpm 은 strict (의도하지 않은 의존성 차단). - Project references: tsc 가 의존 graph 알고 점진 build. - Cache: Turbo/Nx 가 입력 hash 동일 시 결과 재사용. ## 💻 코드 패턴 ### 폴더 구조 ``` my-monorepo/ package.json pnpm-workspace.yaml turbo.json tsconfig.base.json apps/ api/ (Node) web/ (Next/Vite) packages/ shared/ (TS lib) ui/ (React lib) config/ (tsconfig/eslint base) ``` ### pnpm workspace ```yaml # pnpm-workspace.yaml packages: - 'apps/*' - 'packages/*' ``` ```jsonc // apps/api/package.json { "name": "@app/api", "dependencies": { "@app/shared": "workspace:*" } } ``` ### Turborepo ```json // turbo.json { "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }, "lint": {}, "typecheck": { "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] }, "dev": { "cache": false, "persistent": true } } } ``` ```bash turbo build --filter=@app/web... turbo dev --filter=@app/api ``` ### TS project references ```jsonc // tsconfig.base.json { "compilerOptions": { "strict": true, "isolatedModules": true, "composite": true, "declaration": true, "declarationMap": true, "incremental": true } } ``` ```jsonc // packages/shared/tsconfig.json { "extends": "../../tsconfig.base.json", "include": ["src/**/*"], "compilerOptions": { "outDir": "dist", "rootDir": "src" } } ``` ```jsonc // apps/api/tsconfig.json { "extends": "../../tsconfig.base.json", "references": [ { "path": "../../packages/shared" } ], "include": ["src/**/*"] } ``` ### shared 패키지 — source 직접 export ```jsonc // packages/shared/package.json { "name": "@app/shared", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { ".": "./src/index.ts" } } ``` bundler/Vite/Next 는 monorepo 내부 패키지 source 직접 컴파일. dist 빌드 불필요. 배포 패키지 (npm publish) 는 dist 빌드 필요. ### Tooling 공유 ```jsonc // packages/config/tsconfig.base.json (위와 동일) // packages/config/eslint.cjs module.exports = { extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], // ... }; // 다른 패키지에서 { "extends": "@app/config/tsconfig.base.json" } ``` ### Filter (특정 패키지만) ```bash pnpm --filter @app/web add zod pnpm --filter @app/web build turbo build --filter=...[origin/main] # 변경된 것 + 의존 ``` ### CI 캐시 (Turbo + GitHub Actions) ```yaml - uses: actions/cache@v4 with: path: .turbo key: turbo-${{ github.sha }} restore-keys: turbo- - run: pnpm turbo build typecheck test --cache-dir=.turbo ``` Remote cache (Vercel / self-hosted) 가 multi-machine 공유. ### Versioning + publish (Changesets) ```bash pnpm changeset # add changeset pnpm changeset version # bump versions pnpm changeset publish ``` ## 🤔 의사결정 기준 | 규모 | 도구 | |---|---| | 작은 (5 패키지) | pnpm workspace 만 | | 중간 | + Turborepo | | 큰 / 복잡 의존 | + Nx (codegen, generators) | | Polyglot (TS + Go + Python) | Bazel / Pants | | Library publish | + Changesets | | Type 정확 빌드 | + project references | ## ❌ 안티패턴 - **node_modules 매 패키지**: 디스크 / 시간 낭비. pnpm hoist. - **Symlink 안 씀 — copy 기반**: 변경이 다른 패키지 못 봄. - **Project references 없이 거대 단일 tsconfig**: tsc 매번 전부 검사. - **Shared 패키지 dist 빌드 강제 dev**: dev 부담. source export. - **Versioning 수동**: 망가짐. Changesets. - **CI 캐시 미사용**: 매번 5-10분. - **모든 패키지 한꺼번에 build**: filter 로 변경된 것만. ## 🤖 LLM 활용 힌트 - pnpm + Turbo + Changesets + project references 4종 조합. - Source 직접 export (dev), dist (publish). - CI 는 변경 그래프 기반 filter. ## 🔗 관련 문서 - [[TS_tsconfig_Strategy]] - [[TS_Build_Bundler_Patterns]] - [[DevOps_Monorepo_Patterns]]