OneApp Docs
Core Concepts

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 link everywhere 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:

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

PatternMatchesPurpose
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:

packages/ui/package.json
{
  "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 automation

Benefits:

  • ✅ 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 automation

Use 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 templates

Dependency resolution

The workspace:* protocol

Internal packages use the workspace:* protocol:

apps/web/package.json
{
  "name": "web",
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*",
    "react": "^19.0.0"
  }
}

How it works:

  1. Development: workspace:* links to local package via symlink
  2. Production: Converts to actual version when publishing (if applicable)
  3. Type safety: TypeScript sees local types immediately

Benefits:

  • ✅ No manual npm link needed
  • ✅ 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/**" build

Adding 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 typescript

Running 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]" build

Best 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 maintain

2. 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/something

If 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
authentication

4. 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

On this page