Monorepo Structure

Understanding Turborepo, PNPM workspaces, and workspace organization

Monorepo Structure

Portfolio OS uses a monorepo architecture powered by Turborepo and PNPM workspaces for efficient development and code sharing.

Why Monorepo?

Benefits:

  • Code Sharing: Share components, utilities, and types across apps
  • Atomic Changes: Update multiple apps in a single commit
  • Consistent Tooling: Single ESLint, TypeScript, and Prettier config
  • Faster Development: Changes to packages immediately available
  • Better Collaboration: See the full system in one place

Workspace Configuration

PNPM Workspaces

packages:
  - "apps/*"
  - "packages/*"

This tells PNPM to treat apps/* and packages/* as workspaces.

Root package.json

{
  "name": "portfolio-os",
  "private": true,
  "scripts": {
    "dev": "turbo run dev --parallel",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "^2.5.8",
    "prettier": "^3.0.3",
    "typescript": "^5.3.3"
  }
}

Note:

The root is marked "private": true to prevent accidental publishing to npm.

Complete Directory Structure

Turborepo Configuration

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    }
  }
}

Pipeline Explained

TaskdependsOnMeaning
build^buildBuild dependencies first
dev-Run independently
lint^lintLint dependencies first
test^buildBuild before testing

Note:

The ^ symbol means "run this task in dependencies first"

Package Dependencies

How Packages Reference Each Other

Use workspace:* to link to workspace packages:

{
  "name": "@mindware-blog/site",
  "dependencies": {
    "@mindware-blog/ui": "workspace:*",
    "@mindware-blog/lib": "workspace:*",
    "@mindware-blog/utils": "workspace:*",
    "next": "^14.2.32",
    "react": "^18.3.1"
  }
}

This links to the local package, not npm.

Dependency Graph

Working with the Monorepo

Running Commands

# Run in all workspaces
pnpm dev
pnpm build
pnpm lint
pnpm test

Adding Dependencies

# Add to site app
pnpm add react-hook-form --filter=@mindware-blog/site

# Add dev dependency
pnpm add -D @types/node --filter=@mindware-blog/dashboard

Creating New Workspaces

# Create new app directory
mkdir -p apps/admin
cd apps/admin

# Initialize package
pnpm init

# Edit package.json
{
  "name": "@mindware-blog/admin",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3003",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@mindware-blog/ui": "workspace:*",
    "next": "^14.2.32",
    "react": "^18.3.1"
  }
}

Shared Configuration

TypeScript

{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
{
  "extends": "@mindware-blog/tsconfig/nextjs.json",
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

ESLint

module.exports = {
  extends: ["next", "prettier"],
  rules: {
    "@next/next/no-html-link-for-pages": "off",
  },
}
module.exports = {
  root: true,
  extends: ["@mindware-blog/eslint-config-custom"],
}

Cache Management

Turborepo Cache

# View cache info
pnpm turbo run build --dry-run

# Clear Turbo cache
pnpm turbo run build --force

# Disable cache for one run
pnpm turbo run dev --no-cache

PNPM Cache

# View store info
pnpm store status

# Prune store
pnpm store prune

# Clear cache
rm -rf node_modules
pnpm store prune
pnpm install

Performance Tips

1. Use Filters Wisely

# Only build what you need
pnpm build --filter=@mindware-blog/site

# Build with dependencies
pnpm build --filter=@mindware-blog/site...

2. Leverage Turbo Cache

# First run (slow)
pnpm build

# Second run (instant if no changes)
pnpm build

3. Parallel Development

# Start multiple apps in parallel
pnpm dev --parallel

Troubleshooting

Dependency Not Found

# Reinstall dependencies
rm -rf node_modules
pnpm install

Circular Dependencies

Error: Circular dependency detected

Solution: Refactor code to break the cycle or move shared code to a common package.

Build Failures

# Clean Turbo cache
rm -rf .turbo

# Clean Next.js cache
rm -rf apps/site/.next
rm -rf apps/dashboard/.next

# Rebuild
pnpm build

Note:

For persistent issues, see Troubleshooting Guide

Best Practices

  1. Keep packages focused: Each package should have a single responsibility
  2. Avoid circular dependencies: Structure packages in layers
  3. Use workspace protocol: Always use workspace:* for internal dependencies
  4. Leverage caching: Let Turborepo cache builds and tests
  5. Filter commands: Use --filter to work on specific workspaces

Next Steps

Note:

The monorepo structure provides powerful development benefits. Master it to work efficiently across the entire codebase.