
Shared Monorepo Turborepo
- 37 installs
- 19 repo stars
- Updated July 19, 2026
- agents-inc/skills
shared-monorepo-turborepo is a Claude Code skill that teaches Turborepo monorepo orchestration: task pipelines, caching, and internal package architecture.
About
A Claude Code skill for orchestrating monorepos with Turborepo. It covers turbo.json task pipelines with dependsOn ordering, local and remote caching, workspace package linking, syncpack for version consistency, @repo/* naming, and package.json exports for tree-shaking. A developer uses it when configuring Turborepo build pipelines and shared internal packages.
- Turborepo 2.x task pipelines with dependsOn topological ordering
- Local + remote caching and env-based cache invalidation
- @repo/* internal packages, workspace:* linking, and syncpack
Shared Monorepo Turborepo by the numbers
- 37 all-time installs (skills.sh)
- Ranked #834 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
shared-monorepo-turborepo capabilities & compatibility
- Capabilities
- monorepo setup · build caching · task orchestration · package architecture
- Works with
- github
- Use cases
- ci cd · devops
- IDEs
- vscode · cursor ide
- Pricing
- Free
What shared-monorepo-turborepo says it does
Turborepo 2.x for monorepo orchestration. Task pipelines with dependency ordering. Local + remote caching for massive speed gains.
You MUST use `@repo/*` naming convention for ALL internal packages
npx skills add https://github.com/agents-inc/skills --skill shared-monorepo-turborepoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 37 |
|---|---|
| repo stars | ★ 19 |
| Last updated | July 19, 2026 |
| Repository | agents-inc/skills ↗ |
What it does
Configure Turborepo task pipelines, caching, and shared internal packages for a JS/TS monorepo.
Who is it for?
JS/TS monorepos needing cached, dependency-ordered task pipelines, remote cache sharing, and clean internal package boundaries.
Skip if: Single-app projects, repos without shared packages, or projects already using Nx or Lerna.
When should I use this skill?
Configuring monorepo pipelines, workspaces, or package architecture with Turborepo.
What you get
A Turborepo pipeline with topological task ordering, cache-invalidating env config, and well-bounded @repo/* packages.
- turbo.json pipeline config
- @repo/* internal packages
- package.json exports fields
By the numbers
- Turborepo 2.x referenced
- 4 example resource files (caching, workspaces, packages, core)
Files
Monorepo Orchestration with Turborepo
Quick Guide: Turborepo 2.x for monorepo orchestration. Task pipelines with dependency ordering. Local + remote caching for massive speed gains. Workspaces for package linking. Syncpack for dependency version consistency. Internal packages use@repo/*naming, explicitexportsfields, andworkspace:*protocol.
---
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST define task dependencies using `dependsOn: ["^build"]` in turbo.json to ensure topological ordering)
(You MUST declare all environment variables in the `env` array of turbo.json tasks for proper cache invalidation)
(You MUST set `cache: false` for tasks with side effects like dev servers and code generation)
*(You MUST use `workspace:` protocol for internal package dependencies)**
*(You MUST use `@repo/` naming convention for ALL internal packages)**
(You MUST define explicit `exports` field in package.json - never allow importing internal paths)
(You MUST mark React as `peerDependencies` NOT `dependencies` in component packages)
</critical_requirements>
---
Auto-detection: Turborepo configuration, turbo.json, monorepo setup, workspaces, Bun workspaces, syncpack, task pipelines, @repo/\* packages, package.json exports, workspace dependencies, shared configurations
When to use:
- Configuring Turborepo task pipeline and caching strategies
- Setting up workspaces for monorepo package linking
- Enabling remote caching for team/CI cache sharing
- Synchronizing dependency versions across workspace packages
- Creating new internal packages in
packages/ - Configuring package.json exports for tree-shaking
- Setting up shared configuration packages (@repo/eslint-config, @repo/typescript-config)
When NOT to use:
- Single application projects (use standard build tools directly)
- Projects without shared packages (no monorepo benefits)
- Very small projects where setup overhead exceeds caching benefits
- Polyrepo architecture is preferred over monorepo
- Projects already using Nx or Lerna (don't mix monorepo tools)
- App-specific code that won't be shared (keep in app directory)
Key patterns covered:
- Turborepo 2.x task pipeline (dependsOn, outputs, inputs, cache)
- Local and remote caching strategies
- Workspaces for package linking
- Syncpack for dependency version consistency
- Environment variable handling in turbo.json
- Package structure and @repo/\* naming conventions
- package.json exports for tree-shaking
- Named exports and barrel file patterns
- Internal dependencies with workspace protocol
Detailed Resources:
- For code examples, see examples/core.md (always start here)
- examples/caching.md - Remote caching, CI/CD integration
- examples/workspaces.md - Workspace protocol, syncpack, dependency boundaries
- examples/packages.md - Internal package conventions, exports, creating packages
- For decision frameworks and anti-patterns, see reference.md
---
<philosophy>
Philosophy
Turborepo is a high-performance build system designed for JavaScript/TypeScript monorepos. It provides intelligent task scheduling, caching, and remote cache sharing to dramatically reduce build times. Combined with workspaces, it enables efficient package management with automatic dependency linking.
</philosophy>
---
<patterns>
Core Patterns
Pattern 1: Turborepo Task Pipeline with Dependency Ordering
Define task dependencies and caching behavior in turbo.json to enable intelligent build orchestration and caching.
Key Concepts
dependsOn: ["^build"]- Run dependency tasks first (topological order)outputs- Define what files to cacheinputs- Specify which files trigger cache invalidationcache: false- Disable caching for tasks with side effectspersistent: true- Keep dev servers running
Minimal Example
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": ["DATABASE_URL", "NODE_ENV"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": { "cache": false, "persistent": true }
}
}Key: dependsOn: ["^build"] ensures topological execution, env declares variables for cache invalidation, cache: false for side-effect tasks.
See examples/core.md for full good/bad comparison examples.
---
Pattern 2: Caching Strategies
Turborepo's caching system dramatically speeds up builds by reusing previous task outputs when inputs haven't changed.
What Gets Cached
- Build outputs (
dist/,.next/, framework-specific directories) - Test results (when
cache: true) - Lint results
What Doesn't Get Cached
- Dev servers (
cache: false) - Code generation (
cache: false- generates files) - Tasks with side effects
Cache Invalidation Triggers
- Source file changes
- Dependency changes
- Environment variable changes (when in
envarray) - Global dependencies changes (
.env,tsconfig.json)
Setup: Link a Vercel account (or self-hosted cache), then set TURBO_TOKEN and TURBO_TEAM environment variables to enable remote cache sharing.
See examples/caching.md for remote caching configuration and CI integration examples.
---
Pattern 3: Workspaces for Package Management
Configure workspaces to enable package linking and dependency sharing across monorepo packages.
Key Concepts
- Root
package.jsondeclares"workspaces": ["apps/*", "packages/*"] - Internal deps use
"@repo/ui": "workspace:*"protocol for automatic linking - Standard structure:
apps/for deployable apps,packages/for shared code
See examples/workspaces.md for full good/bad comparison examples and syncpack configuration.
</patterns>
---
<performance>
Performance Optimization
Cache Hit Metrics:
- First build: ~45s (5 packages, no cache)
- Cached build: ~1s (97% faster with local cache)
- Affected build: ~12s (73% faster, only changed packages rebuild)
- Team savings: Hours per week with remote cache enabled
Optimization Strategies:
- Set `globalDependencies` for files affecting all packages (
.env,tsconfig.json) to prevent unnecessary cache invalidation - Use `inputs` array to fine-tune what triggers cache invalidation for specific tasks
- Enable remote caching to share artifacts across team and CI
- Use `--filter` with affected detection (
--filter=...[HEAD^]) to only run tasks for changed packages - Set `outputs` carefully to exclude cache directories (e.g.,
!.next/cache/**)
Force Cache Bypass:
# Ignore cache when needed
bun run build --force
# Only build affected packages
bun run build --filter=...[HEAD^1]</performance>
---
<decision_framework>
Decision Framework
New code? → Shared across 2+ apps? → packages/ (else keep in app)
Monorepo? → Builds > 30s or caching matters? → Use TurborepoFor comprehensive decision trees and package creation criteria, see reference.md.
</decision_framework>
---
<red_flags>
RED FLAGS
High Priority Issues:
- Missing
dependsOn: ["^build"]for build tasks (breaks topological ordering) - Missing
envarray in turbo.json (causes cache misses across environments) - Caching dev servers or code generation (incorrect outputs reused)
- Default exports in library packages (breaks tree-shaking)
- Missing
exportsfield in package.json (allows internal path imports)
Common Mistakes:
- Hardcoded versions instead of
workspace:*for internal deps - React in
dependenciesinstead ofpeerDependencies - Giant barrel files re-exporting everything (negates tree-shaking)
- Running full test suite without
--filter=...[HEAD^]affected detection
Gotchas:
dependsOn: ["^task"]runs dependencies' tasks;dependsOn: ["task"]runs same package's task--filter=...[HEAD^]requiresfetch-depth: 2in GitHub Actions- Exclude cache directories in outputs:
!.next/cache/**
For detailed anti-patterns and checklists, see reference.md.
</red_flags>
---
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST define task dependencies using `dependsOn: ["^build"]` in turbo.json to ensure topological ordering)
(You MUST declare all environment variables in the `env` array of turbo.json tasks for proper cache invalidation)
(You MUST set `cache: false` for tasks with side effects like dev servers and code generation)
*(You MUST use `workspace:` protocol for internal package dependencies)**
*(You MUST use `@repo/` naming convention for ALL internal packages)**
(You MUST define explicit `exports` field in package.json - never allow importing internal paths)
(You MUST mark React as `peerDependencies` NOT `dependencies` in component packages)
Failure to follow these rules will cause incorrect builds, cache misses, broken dependency resolution, and tree-shaking failures.
</critical_reminders>
Turborepo - Caching Examples
Remote caching, CI/CD integration, and cache behavior patterns. See ../SKILL.md for core concepts and core.md for essential task pipeline patterns.
Related Examples:
- core.md - Essential task pipeline patterns (prerequisite)
- workspaces.md - Workspace protocol, syncpack
- packages.md - Internal package conventions
---
Remote Caching Configuration
Good Example - Remote caching with signature verification
{
"remoteCache": {
"signature": true
},
"tasks": {
"build": {
"env": ["DATABASE_URL", "NODE_ENV", "API_URL"]
}
}
}Why good: signature: true enables cache verification for security, env array declares all environment variables so different values trigger rebuilds, remote cache shares artifacts across team and CI reducing redundant builds
---
Advanced Caching Configuration
Good Example - Full turbo.json with advanced caching
// turbo.json - Advanced caching configuration (Turborepo 2.x)
{
"$schema": "https://turborepo.dev/schema.json",
"globalDependencies": [".env", "tsconfig.json", "eslint.config.js"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**", "!.next/cache/**"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"cache": true,
"inputs": ["src/**/*.ts", "src/**/*.tsx", "**/*.test.ts", "**/*.test.tsx"]
},
"lint": {
"cache": true,
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
},
"remoteCache": {
"signature": true
}
}Why good: globalDependencies ensures changes to shared config files invalidate all caches, inputs array fine-tunes what triggers test cache invalidation, outputs: [] for lint means it caches the result without storing files, remote cache with signature verification is secure
---
Cache Hit Examples
# Local development - uses local cache
turbo run build
# Cache miss - Building...
# Packages built: 5
# Time: 45.2s
# Second run - hits cache
turbo run build
# Cache hit - Skipping...
# Packages restored: 5
# Time: 1.2s (97% faster)
# Only rebuilds changed packages
# Edit packages/ui/src/Button.tsx
turbo run build
# Cache hit: @repo/types, @repo/config, @repo/api-client
# Cache miss: @repo/ui (changed)
# Cache miss: web, admin (depend on @repo/ui)
# Time: 12.4s (73% faster)---
CI/CD Integration Examples
Good Example - Remote caching in GitHub Actions
# .github/workflows/ci.yml - Remote caching in CI
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Needed for --filter
# Install dependencies with your package manager
- run: bun install
# Remote cache with Vercel
- name: Build
run: turbo run build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# Only run affected tests on PRs
- name: Test affected
if: github.event_name == 'pull_request'
run: turbo run test --filter=...[HEAD^]
# Run all tests on main
- name: Test all
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: turbo run testWhy good: fetch-depth: 2 enables affected detection with --filter=...[HEAD^], remote cache tokens shared via secrets, affected tests run only on PRs to save CI time, full tests run on main for comprehensive coverage
---
Package.json Scripts Examples
Good Example - Remote cache setup scripts
// package.json - Remote cache setup
{
"scripts": {
"build": "turbo run build",
"build:fresh": "turbo run build --force",
"build:affected": "turbo run build --filter=...[HEAD^1]",
"test:affected": "turbo run test --filter=...[HEAD^1]"
}
}Why good: :fresh script bypasses cache when needed, :affected scripts only run tasks for changed packages, clear naming convention indicates purpose
---
Turborepo - Core Examples
Essential task pipeline patterns for Turborepo. See SKILL.md for core concepts and reference.md for decision frameworks.
Additional Examples:
- caching.md - Remote caching, CI/CD integration
- workspaces.md - Workspace protocol, syncpack, dependency boundaries
- packages.md - Internal package conventions, exports, creating packages
---
Task Pipeline Examples
Good Example - Proper task configuration with dependencies
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": ["DATABASE_URL", "NODE_ENV"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["^build"],
"inputs": [
"$TURBO_DEFAULT$",
"src/**/*.tsx",
"src/**/*.ts",
"test/**/*.ts",
"test/**/*.tsx"
]
},
"dev": {
"cache": false,
"persistent": true
},
"generate": {
"dependsOn": ["^generate"],
"cache": false
},
"lint": {}
}
}Why good: dependsOn: ["^build"] ensures topological task execution (dependencies build first), env array includes all environment variables for proper cache invalidation, cache: false prevents caching tasks with side effects (dev servers, code generation), outputs specifies cacheable artifacts while excluding cache directories
Bad Example - Missing critical configuration
{
"tasks": {
"build": {
"outputs": ["dist/**"]
// BAD: No dependsOn - dependencies may not build first
// BAD: No env array - environment changes won't invalidate cache
},
"dev": {
"persistent": true
// BAD: Missing cache: false - dev server output gets cached
},
"generate": {
"dependsOn": ["^generate"]
// BAD: Missing cache: false - generated files get cached
}
}
}Why bad: Missing dependsOn breaks topological ordering (packages may build before their dependencies), missing env array causes stale builds when environment variables change, caching dev servers or code generation tasks causes incorrect cached outputs to be reused
---
Environment Variable Examples
Good Example - All env vars declared
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": ["API_URL", "NODE_ENV", "DATABASE_URL"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true,
"env": ["API_URL", "NODE_ENV"]
}
}
}Why good: All environment variables explicitly declared in env array, cache invalidates when env values change, ESLint can validate undeclared usage, different environments (dev/staging/prod) properly trigger rebuilds
Bad Example - Missing env declarations
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
// BAD: No env array - using DATABASE_URL won't invalidate cache
}
}
}Why bad: Missing env array means environment variable changes don't invalidate cache, stale builds with wrong config get reused across environments, ESLint can't catch undeclared variable usage
---
ESLint Integration Example
Good Example - Turborepo ESLint plugin
// packages/eslint-config/base.js
export const baseConfig = [
{
plugins: {
turbo: turboPlugin,
},
rules: {
"turbo/no-undeclared-env-vars": "warn",
},
},
];Why good: ESLint warns when env vars are used but not declared in turbo.json, prevents cache invalidation bugs at development time
Alternative: Biome 2.3.10+ includes a native noUndeclaredEnvVars rule in its Turborepo domain, eliminating the ESLint dependency for this check.
---
Turborepo - Internal Package Examples
Internal package conventions, exports, and creation patterns. See ../SKILL.md for core concepts and ../reference.md for decision frameworks.
Related Examples:
- workspaces.md - Workspace protocol (prerequisite)
- core.md - Essential task pipeline patterns
- caching.md - Remote caching, CI/CD integration
---
Naming Conventions
Package Naming
// Good Example - Package naming
// package.json
{
"name": "@repo/ui", // @repo/* prefix, kebab-case
"name": "@repo/api-client", // Multi-word: kebab-case
"name": "@repo/eslint-config" // Config package: kebab-case
}
// Good Example - File naming
// button.tsx (NOT Button.tsx)
// use-auth.ts (NOT useAuth.ts)
// api-client.ts (NOT apiClient.ts or api_client.ts)
// Good Example - Export naming
export { Button } from "./button"; // PascalCase for components
export { useAuth, formatDate } from "./utils"; // camelCase for functions/hooks
export { API_TIMEOUT_MS } from "./constants"; // SCREAMING_SNAKE_CASE for constantsWhy good: Consistent naming enables predictable imports, kebab-case files work across all OS filesystems, @repo prefix prevents namespace collisions with npm packages
// Bad Example - Inconsistent naming
{
"name": "ui", // BAD: Missing @repo/ prefix
"name": "@repo/API-Client", // BAD: PascalCase package name
"name": "@mycompany/ui" // BAD: Custom namespace (use @repo)
}
// Button.tsx // BAD: PascalCase file name
// useAuth.ts // BAD: camelCase file name
// api_client.ts // BAD: snake_case file name
export default Button; // BAD: Default exportWhy bad: Missing @repo prefix causes namespace confusion, PascalCase files break on case-sensitive filesystems, default exports prevent tree-shaking and cause naming conflicts
---
package.json Configuration
Essential Fields
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./button": "./src/components/button/button.tsx",
"./switch": "./src/components/switch/switch.tsx",
"./hooks": "./src/hooks/index.ts",
"./styles/*": "./src/styles/*"
},
"scripts": {
"lint": "eslint .",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"typescript": "^5.7.0"
}
}Why good: Explicit exports enable tree-shaking, workspace protocol ensures local versions always used, peerDependencies prevent React version conflicts, private true prevents accidental publishing
// Bad Example - Missing exports and wrong dependencies
{
"name": "@repo/ui",
"version": "0.0.0",
// BAD: No exports field - allows importing internal paths
"main": "./src/index.ts",
"dependencies": {
"react": "^19.0.0", // BAD: Should be peerDependencies
"@repo/eslint-config": "^1.0.0" // BAD: Should use workspace:*
}
}Why bad: Missing exports allows importing internal paths breaking encapsulation, React in dependencies causes version duplication, hardcoded versions create version conflicts in monorepo
---
Exports Field Pattern
Define explicit exports for each public API to enable tree-shaking and encapsulation.
{
"exports": {
"./button": "./src/components/button/button.tsx",
"./switch": "./src/components/switch/switch.tsx",
"./hooks": "./src/hooks/index.ts"
}
}Why good: Explicit exports enable aggressive tree-shaking, prevents coupling to internal file structure, makes API surface clear to consumers
// Bad Example - No exports or barrel file anti-pattern
{
// BAD: No exports - allows deep imports
"main": "./src/index.ts"
}
// OR worse - barrel file anti-pattern
{
"exports": {
".": "./src/index.ts" // BAD: Giant barrel file that re-exports everything
}
}Why bad: No exports allows deep imports like @repo/ui/src/internal/utils breaking encapsulation, barrel files bundle all code even if only one component is imported
---
Usage Pattern
// Good Example - Import from explicit exports
import { Button } from "@repo/ui/button";
import { Switch } from "@repo/ui/switch";
import { useClickOutside } from "@repo/ui/hooks";Why good: Each import maps to a single file, bundler can tree-shake unused components, clear and predictable import paths
// Bad Example - Import from internal paths
import { Button } from "@repo/ui/src/components/button/button";
import { Switch } from "@repo/ui/src/components/switch/switch";Why bad: Couples to internal file structure, breaks when package refactors, bypasses intended public API, tree-shaking may fail
---
Barrel Files (Use Sparingly)
Barrel files for small groups only, prefer package.json exports for tree-shaking.
// Good Example - Small barrel file for related items
// packages/ui/src/hooks/index.ts
export { useClickOutside } from "./use-click-outside";
export { useDebounce } from "./use-debounce";
export { useMediaQuery } from "./use-media-query";
export type { DebounceOptions } from "./use-debounce";Why good: Small barrels (<10 exports) group related items, package.json exports still controls public API, manageable cognitive load
When to use: Only for grouping 3-10 tightly related exports (e.g., hooks, utils)
// Bad Example - Giant barrel file
// packages/ui/src/index.ts
export * from "./components/button/button";
export * from "./components/switch/switch";
export * from "./components/dialog/dialog";
export * from "./components/input/input";
// ... 50 more exportsWhy bad: Giant barrels break tree-shaking (bundler loads entire file), slow TypeScript compilation, IDE struggles with autocomplete, defeats purpose of explicit exports
When not to use: For large numbers of exports, prefer explicit package.json exports field instead
---
Package Types
Component Library Package
{
"name": "@repo/ui",
"exports": {
"./button": "./src/components/button/button.tsx",
"./switch": "./src/components/switch/switch.tsx"
},
"peerDependencies": {
"react": "^19.0.0"
},
"sideEffects": ["*.css"]
}API Client Package
{
"name": "@repo/api",
"exports": {
".": "./src/client.ts",
"./types": "./src/types.ts"
},
"sideEffects": false
}Configuration Package
{
"name": "@repo/eslint-config",
"exports": {
"./base": "./base.js",
"./react": "./react.js"
},
"dependencies": {
"eslint": "^9.0.0",
"typescript-eslint": "^8.0.0"
}
}TypeScript Config Package
{
"name": "@repo/typescript-config",
"exports": {
"./base.json": "./base.json",
"./react-library.json": "./react-library.json"
}
}---
Directory Layout
packages/
├── ui/ # Shared UI components
│ ├── src/
│ │ ├── components/
│ │ │ ├── button/
│ │ │ │ └── button.tsx
│ │ │ └── switch/
│ │ │ └── switch.tsx
│ │ └── hooks/
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
│
├── api/ # API client package
│ ├── src/
│ │ ├── client.ts
│ │ └── types.ts
│ ├── package.json
│ └── tsconfig.json
│
├── eslint-config/ # Shared ESLint config
│ ├── base.js
│ └── package.json
│
├── prettier-config/ # Shared Prettier config
│ ├── prettier.config.mjs
│ └── package.json
│
└── typescript-config/ # Shared TypeScript config
├── base.json
├── react-library.json
└── package.json---
Turborepo - Workspace Examples
Workspace protocol, syncpack, and dependency boundary patterns. See ../SKILL.md for core concepts and core.md for essential task pipeline patterns.
Related Examples:
- core.md - Essential task pipeline patterns (prerequisite)
- caching.md - Remote caching, CI/CD integration
- packages.md - Internal package conventions
---
Workspace Protocol Examples
Good Example - Properly configured workspaces
{
"workspaces": ["apps/*", "packages/*"],
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/types": "workspace:*"
}
}Why good: workspace:* protocol links local packages automatically, glob patterns apps/* and packages/* discover all packages dynamically, Bun hoists common dependencies to root reducing duplication
Bad Example - Hardcoded versions instead of workspace protocol
{
"workspaces": ["apps/*", "packages/*"],
"dependencies": {
"@repo/ui": "1.0.0",
"@repo/types": "^2.1.0"
}
}Why bad: Hardcoded versions break local package linking (installs from npm instead of linking), version mismatches across packages cause duplicate dependencies, changes to internal packages require manual version updates everywhere
---
Syncpack Examples
Good Example - Syncpack configured for version checking
// package.json
{
"scripts": {
"deps:check": "syncpack list-mismatches",
"deps:fix": "syncpack fix-mismatches"
}
}Why good: deps:check identifies version mismatches across packages, deps:fix auto-updates to consistent versions, runs in CI to prevent version drift
Good Example - Syncpack configuration
// .syncpackrc.json - Enforce workspace protocol and consistent versions
{
"versionGroups": [
{
"label": "Use workspace protocol for internal packages",
"dependencies": ["@repo/*"],
"dependencyTypes": ["prod", "dev"],
"pinVersion": "workspace:*"
}
],
"semverGroups": [
{
"range": "^",
"dependencyTypes": ["prod", "dev"],
"dependencies": ["**"],
"packages": ["**"]
}
]
}Why good: versionGroups enforces workspace protocol for internal packages, semverGroups enforces consistent version ranges across all packages
Usage Example
# Check for mismatches
$ bun run deps:check
react: 18.2.0, 18.3.0, 19.0.0 (3 versions across packages!)
@types/react: 18.2.0, 18.3.0 (2 versions!)
# Auto-fix to consistent versions
$ bun run deps:fix
Updated react to 19.0.0 across all packages
Updated @types/react to 18.3.0 across all packages---
Dependency Boundary Examples
Allowed vs Forbidden Dependencies
ALLOWED:
apps/web -> @repo/ui -> @repo/types
apps/admin -> @repo/api-client -> @repo/types
FORBIDDEN:
@repo/ui -> apps/web (packages cannot depend on apps)
@repo/types -> apps/admin (packages cannot depend on apps)
@repo/ui -> @repo/api-client -> @repo/ui (circular dependency)Circular Dependency Detection
# Using madge to detect circular dependencies
npx madge --circular --extensions ts,tsx ./packages
npx madge --circular --extensions ts,tsx ./apps/web/src
# Using dpdm
npx dpdm --circular ./packages/*/src/index.tsCI Integration for Dependency Checks
// package.json - Add to CI pipeline
{
"scripts": {
"check:circular": "madge --circular --extensions ts,tsx ./packages",
"check:deps": "bun run deps:check"
}
}Why good: Automated checks prevent circular dependencies from being merged, clear boundary rules enforce clean architecture
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/agents-inc/cli/main/src/schemas/metadata.schema.json
category: shared-monorepo
slug: turborepo
domain: shared
author: "@vince"
displayName: Turborepo
cliDescription: Monorepo orchestration
usageGuidance: Use when configuring monorepo pipelines, workspaces, or package architecture.
Monorepo Reference
Decision frameworks, anti-patterns, and red flags for Turborepo and monorepo development. See SKILL.md for core concepts and examples/core.md for code examples.
---
Decision Framework
When to Create a New Package
New code to write?
│
├─ Is it a deployable application?
│ └─ apps/ (web app, API server, admin dashboard)
│
├─ Is it shared across multiple apps?
│ └─ packages/ (ui, api-client, types)
│
├─ Is it app-specific but significant?
│ └─ Feature folder within the app (not a package)
│
└─ Is it a build tool or generator?
└─ tools/ (code generators, custom scripts)Package Creation Criteria
Create package when:
- Code is used by 2+ apps
- Logical boundary exists (UI library, API client)
- Independent versioning would be valuable
- Clear ownership/team boundary
Keep code in app when:
- Only one app uses it
- Tightly coupled to app-specific logic
- Frequently changes with app features
- No clear reuse potential
When to Use Turborepo vs Standard Tools
Is this a monorepo?
├─ NO → Use standard build tools directly
└─ YES → Are there multiple packages/apps?
├─ NO → Use standard build tools
└─ YES → Do builds take > 30 seconds?
├─ YES → Use Turborepo
└─ NO → Is caching important for your team?
├─ YES → Use Turborepo
└─ NO → Standard tools may be sufficient---
RED FLAGS
High Priority Issues
- Running full test suite on every PR without affected detection (wastes CI time and money)
- Not using caching at all (missing
outputsconfiguration) - Missing
dependsOn: ["^build"]for tasks that need dependencies built first - Forgetting to declare environment variables in
envarray (causes cache misses across environments)
Medium Priority Issues
- Not setting
cache: falsefor dev servers and code generation tasks - Not using remote caching for teams (everyone rebuilds everything locally)
- Missing
globalDependenciesfor shared config files affecting all packages - Using
latestDocker tags in CI (non-deterministic builds)
Common Mistakes
- Building dependencies separately instead of letting Turborepo handle topological ordering
- Rebuilding for each environment instead of building once and deploying many
- Not setting GitHub Actions concurrency limits (multiple CI runs on same PR)
- Hardcoding package versions instead of using
workspace:*protocol
Gotchas & Edge Cases
- Cache invalidation requires ALL affected inputs to be declared - missing
envvars orinputscauses stale builds - Remote cache requires Vercel account or self-hosted solution - not automatic
dependsOn: ["^task"]runs dependencies' tasks,dependsOn: ["task"]runs same package's task first- Excluding cache directories in
outputsis critical:!.next/cache/**prevents caching the cache --filter=...[HEAD^]syntax requires fetch-depth: 2 in GitHub Actions checkout
---
Anti-Patterns
Missing dependsOn for Build Tasks
// ANTI-PATTERN: No dependency ordering
{
"tasks": {
"build": {
"outputs": ["dist/**"]
// Missing dependsOn: ["^build"]
}
}
}Why it's wrong: Dependencies may not build first causing build failures, topological ordering broken.
What to do instead: Always use dependsOn: ["^build"] for build tasks.
---
Hardcoded Package Versions
// ANTI-PATTERN: Hardcoded versions for workspace packages
{
"dependencies": {
"@repo/ui": "1.0.0",
"@repo/types": "^2.1.0"
}
}Why it's wrong: Breaks local package linking (installs from npm instead), version mismatches cause duplicate dependencies.
What to do instead: Use workspace protocol: "@repo/ui": "workspace:*"
---
Missing Environment Variable Declarations
// ANTI-PATTERN: Env vars not declared
{
"tasks": {
"build": {
"outputs": ["dist/**"]
// Missing env array - DATABASE_URL changes won't invalidate cache
}
}
}Why it's wrong: Environment variable changes don't invalidate cache, stale builds with wrong config get reused.
What to do instead: Declare all env vars in the env array.
---
Caching Side-Effect Tasks
// ANTI-PATTERN: Dev server gets cached
{
"tasks": {
"dev": {
"persistent": true
// Missing cache: false
}
}
}Why it's wrong: Dev servers and code generation should not be cached, causes incorrect cached outputs to be reused.
What to do instead: Set cache: false for dev servers and code generation tasks.
---
Quick Reference
turbo.json Task Checklist
- [ ]
dependsOn: ["^build"]for tasks needing dependencies built first - [ ]
envarray lists all environment variables used - [ ]
outputsarray specifies files to cache - [ ]
cache: falsefor dev servers and code generation - [ ]
persistent: truefor long-running tasks like dev servers - [ ]
inputsarray fine-tunes cache invalidation triggers
Workspace Checklist
- [ ] Root package.json has
workspacesarray - [ ] Internal packages use
workspace:*protocol - [ ] Syncpack configured for version consistency
- [ ] Circular dependency checks in CI
Remote Cache Checklist
- [ ]
TURBO_TOKENsecret configured in CI - [ ]
TURBO_TEAMsecret configured in CI - [ ]
remoteCache.signature: truefor security - [ ]
fetch-depth: 2in GitHub Actions checkout for affected detection
---
Turborepo 2.x Features
Recent Additions
Devtools (2.7 — Visual Graph Exploration):
# Launch visual devtools for Package/Task Graph exploration
turbo devtools`turbo docs` (2.8 — CLI Documentation Search):
# Search docs from terminal
turbo docs "package configurations"Composable Configuration (`$TURBO_EXTENDS$`):
Package configurations can extend and append to inherited arrays instead of overwriting:
// packages/web/turbo.json - Extend root config and ADD to arrays
{
"extends": ["//"],
"tasks": {
"build": {
"inputs": ["$TURBO_EXTENDS$", "public/**"]
}
}
}Task Options Added in 2.x:
| Option | Type | Purpose |
|---|---|---|
description | string | Human-readable task documentation |
interruptible | boolean | Allow turbo watch to restart persistent tasks |
with | string[] | Sibling tasks to run alongside this task |
Package Boundaries (Tags) — experimental:
// Root turbo.json - Define boundary rules for tags
{
"boundaries": {
"tags": {
"ui": {
"dependencies": {
"allow": ["shared"],
"deny": ["api"]
}
}
}
}
}
// packages/ui/turbo.json - Assign tags to package
{
"tags": ["ui"]
}
// packages/api/turbo.json
{
"tags": ["api"]
}Run turbo boundaries to validate dependency rules against tag assignments.
Special Microsyntax:
| Syntax | Purpose |
|---|---|
$TURBO_DEFAULT$ | Restores default input behavior while customizing |
$TURBO_ROOT$ | Makes globs relative to repo root (not package) |
$TURBO_EXTENDS$ | Appends to arrays instead of replacing in pkg configs |
Breaking Changes (Turborepo 2.0)
If migrating from Turborepo 1.x, run the codemod:
npx @turbo/codemod migrateKey changes:
pipelinerenamed totasksoutputModerenamed tooutputLogsglobalDotEnvanddotEnvremoved (useinputsinstead)- Strict Mode for environment variables is now default
--ignoreremoved (use--filterinstead)packageManagerfield required in root package.json
---
Resources
Official documentation:
- Turborepo: https://turborepo.dev/docs
- Turborepo Configuration: https://turborepo.dev/docs/reference/configuration
- Turborepo CI/CD: https://turborepo.dev/docs/ci
- Turborepo Caching: https://turborepo.dev/docs/core-concepts/caching
- Turborepo Upgrading: https://turborepo.dev/docs/crafting-your-repository/upgrading
- Bun Workspaces: https://bun.sh/docs/install/workspaces
Tools:
- Syncpack: https://github.com/JamieMason/syncpack
- Turborepo Remote Cache: https://turborepo.dev/docs/core-concepts/remote-caching
---
Internal Package Conventions Reference
The following sections cover decision frameworks and anti-patterns for internal packages.
---
Package Decision Framework
Creating new code in monorepo?
├─ Is it shared across 2+ apps?
│ ├─ YES → Create internal package
│ └─ NO → Keep in app directory
│
└─ Creating internal package?
├─ Component library? → @repo/ui with React peerDeps
├─ API client? → @repo/api with sideEffects:false
├─ Config (ESLint/TS/Prettier)? → @repo/*-config
└─ Utils? → @repo/utils with sideEffects:false
Configuring package.json?
├─ Set "exports" field → Explicit API surface
├─ Set "sideEffects" → false (or ["*.css"] if styles)
├─ Internal deps → Use "workspace:*"
└─ React dependency → Use "peerDependencies"
Importing from packages?
├─ Types only? → import type { }
├─ Components/functions → import { } from "@repo/*/export-name"
└─ NEVER → import from internal paths---
Package Red Flags
High Priority Issues
- Default exports in library packages - breaks tree-shaking and naming consistency
- Missing `exports` field in package.json - allows importing internal paths
- *Hardcoded versions for internal deps instead of `workspace:`** - version conflicts
- React in `dependencies` instead of `peerDependencies` - version duplication
Medium Priority Issues
- Giant barrel files re-exporting everything (negates tree-shaking benefits)
- Missing
sideEffectsfield (prevents aggressive tree-shaking) - Importing from internal paths instead of package exports
- PascalCase file names (breaks on case-sensitive filesystems)
Common Mistakes
- Using custom namespace like
@mycompany/*instead of@repo/* - Creating internal packages for app-specific code (over-abstraction)
- Missing
private: true(can accidentally publish to npm) - Using star imports
import *(breaks tree-shaking)
Gotchas & Edge Cases
workspace:*is replaced with actual version on publish (if you ever publish)- CSS files must be marked as
sideEffectseven if package is otherwise pure - TypeScript
pathsmapping may be needed for some bundlers (some handle it automatically) - Barrel files slow down hot module replacement (HMR) in development
- Package.json
exportsfield is strict - missing exports cannot be imported
---
Package Checklist
- [ ] Directory in
packages/ - [ ]
package.jsonwith@repo/prefix name - [ ]
private: trueset - [ ]
exportsfield configured - [ ]
sideEffectsfield set - [ ]
workspace:*for internal dependencies - [ ]
peerDependenciesfor React (if applicable) - [ ]
tsconfig.jsonextending shared config - [ ] kebab-case file naming
- [ ] Named exports only
Related skills
FAQ
How do I enforce build order in Turborepo?
Define task dependencies using dependsOn: ["^build"] in turbo.json to ensure topological ordering.
How should internal packages be named?
Use the @repo/* naming convention for all internal packages and the workspace:* protocol for their dependencies.