Workspace Architecture
Understand how OneApp uses pnpm workspaces to organize 40+ packages into a scalable, maintainable monorepo.
Already know workspaces?
Why workspace architecture matters
Managing multiple packages without a clear workspace strategy creates problems:
- Dependency chaos — Packages can't find each other or have version conflicts
- Slow installs — npm/yarn duplicate dependencies across packages
- Phantom dependencies — Packages accidentally access undeclared dependencies
- No isolation — Teams interfere with each other's work
- Manual linking — Running
npm linkeverywhere is error-prone
OneApp's workspace architecture uses pnpm's native workspace support with the workspace:* protocol — automatically
linking internal packages, preventing phantom dependencies, and providing team isolation — so you can develop multiple
packages simultaneously without manual setup.
Production-ready with 40+ workspaces across 5 categories (apps, packages, teams, personal, platform), strict dependency resolution, content-addressable storage (3x faster installs), and automatic linking during development.
Use cases
Workspace architecture enables:
- Multi-package development — Work on multiple packages simultaneously with instant changes
- Team autonomy — Teams develop in isolation without conflicts
- Shared infrastructure — Common packages available to all apps
- Safe experimentation — Personal workspaces for prototyping
- Efficient CI/CD — Build only changed packages
How it works
Workspaces are defined in pnpm-workspace.yaml:
packages:
# Core applications
- "apps/*"
# Shared packages
- "packages/*"
# Team workspaces
- "teams/*/apps/*"
- "teams/*/packages/*"
- "teams/*/infra/*"
# Personal workspaces
- "personal/*/apps/*"
- "personal/*/packages/*"
- "personal/*/infra/*"
# Platform applications
- "platform/apps/*"Each pattern matches directories containing package.json files. pnpm automatically:
- ✅ Links internal packages via symlinks
- ✅ Hoists shared dependencies to save disk space
- ✅ Prevents phantom dependencies with strict mode
- ✅ Resolves
workspace:*to local versions
Workspace configuration
pnpm workspace patterns
Pattern syntax
packages:
- "apps/*" # All direct children of apps/
- "teams/*/apps/*" # All apps/ directories within team directories
- "packages/**" # All packages recursively (not recommended)Wildcards:
*— Matches one directory level**— Matches any number of directory levels (use sparingly)
Our workspace categories
| Pattern | Matches | Purpose |
|---|---|---|
apps/* | apps/web/ | Core deployable applications |
packages/* | packages/ui/, packages/utils/ | Shared internal packages |
teams/*/apps/* | teams/ai/apps/playground/ | Team applications |
teams/*/packages/* | teams/ai/packages/utils/ | Team shared packages |
personal/*/apps/* | personal/andy/apps/dashboard/ | Personal applications |
platform/apps/* | platform/apps/docs/ | Infrastructure applications |
Workspace categories explained
Shared Packages (packages/)
Core packages used across all workspaces:
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts"
}
}Characteristics:
- Scoped with
@repo/prefix - Private (not published to npm)
- Consumed by apps and team packages
Team Workspaces (teams/)
Isolated spaces for team development:
teams/ai/
├── apps/
│ ├── playground/ # AI experimentation app
│ └── prompt-studio/ # Prompt engineering tool
├── packages/
│ └── ai-utils/ # AI-specific utilities (team-only)
└── infra/
└── scripts/ # AI team automationBenefits:
- ✅ Teams control dependencies
- ✅ No conflicts with other teams
- ✅ Can consume shared
@repo/*packages - ✅ Experiment without affecting production
Personal Workspaces (personal/)
Individual developer experimentation:
personal/andy/
├── apps/
│ └── dashboard/ # Personal dashboard
├── packages/
│ └── andy-utils/ # Personal utilities
└── infra/
└── scripts/ # Personal automationUse cases:
- Prototyping new features
- Learning new technologies
- Building personal tools
- Testing ideas
Platform Applications (platform/apps/)
Infrastructure apps available to all:
platform/apps/
├── docs/ # Documentation site
├── storybook/ # Component library (Storybook)
├── oneapp-onstage/ # Main consumer app
├── oneapp-backstage/ # AI workflow designer
├── oneapp-api/ # REST API server
├── mobile-app/ # Expo + React Native
└── email/ # React Email templatesDependency resolution
The workspace:* protocol
Internal packages use the workspace:* protocol:
{
"name": "web",
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*",
"react": "^19.0.0"
}
}How it works:
- Development:
workspace:*links to local package via symlink - Production: Converts to actual version when publishing (if applicable)
- Type safety: TypeScript sees local types immediately
Benefits:
- ✅ No manual
npm linkneeded - ✅ Changes reflect instantly across packages
- ✅ Version consistency guaranteed
- ✅ Works in CI/CD without special setup
Dependency hierarchy
Root (workspace-wide tools)
├── Apps (can depend on packages)
├── Packages (can depend on other packages)
├── Teams (can depend on packages)
└── Personal (can depend on packages)Rules:
- ✅ Apps → Packages
- ✅ Packages → Other packages
- ✅ Teams → Shared packages
- ❌ Apps → Apps (not allowed)
- ❌ Teams → Other teams (discouraged)
Working with workspaces
Targeting specific workspaces
# By package name
pnpm --filter=@repo/ui build
pnpm --filter=web dev
# By path
pnpm --filter=./apps/web dev
pnpm --filter=./packages/ui build
# Multiple packages
pnpm --filter=@repo/ui --filter=@repo/utils build
# All in a directory
pnpm --filter="./packages/*" lint
pnpm --filter="./teams/ai/**" buildAdding dependencies
# Add to specific workspace
pnpm --filter=@repo/ui add clsx
# Add internal package (automatically uses workspace:*)
pnpm --filter=web add @repo/ui
# Add dev dependency
pnpm --filter=web add -D vitest
# Add to root (workspace-wide tools only)
pnpm add -D -w typescriptRunning scripts
# Run in specific workspace
pnpm --filter=web dev
# Run across all workspaces that have the script
pnpm -r build
# Run in parallel
pnpm -r --parallel build
# Run only in changed packages
pnpm --filter="[origin/main]" buildBest practices
1. Keep shared packages focused
Each package should have a single responsibility:
✅ Good - focused packages
@repo/ui → UI components only
@repo/utils → Utility functions only
@repo/types → Type definitions only
❌ Bad - unfocused package
@repo/common → Too broad, hard to maintain2. Minimize cross-team dependencies
Teams should primarily depend on @repo/* packages:
✅ Good
teams/ai/apps/myapp → @repo/ui
❌ Bad (creates tight coupling)
teams/ai/apps/myapp → teams/istudio/packages/somethingIf a team package is useful elsewhere:
- Promote it to
packages/ - Or create a shared package that both teams use
3. Use consistent naming
✅ Good - scoped names
@repo/ui
@repo/db-prisma
@repo/auth
❌ Bad - unscoped names
ui
database
authentication4. Avoid circular dependencies
// ❌ Bad - circular dependency
// packages/a/index.ts
import { foo } from "@repo/b";
// packages/b/index.ts
import { bar } from "@repo/a"; // Circular!
// ✅ Good - extract shared code
// packages/shared/index.ts
export const shared = () => {
/* ... */
};
// packages/a/index.ts
import { shared } from "@repo/shared";
// packages/b/index.ts
import { shared } from "@repo/shared";Next steps
- Manage dependencies: Dependency Management →
- Type safety patterns: Type Safety →
- Explore packages: Shared Packages →