Vue.js 96 🟢 Large Scale Vue Monorepo Setup with Nx or pnpm Workspaces
A Vue monorepo at scale is not a single tool decision. It is a layered architecture: a package manager that handles dependency resolution and workspace linking, and a build system that handles task orchestration, caching, and affected-project detection. The two layers are independent, and the most common production stack combines pnpm workspaces for the first layer with either Nx or Turborepo for the second. The choice between Nx and pnpm workspaces alone is not a choice between equivalents; they solve different problems, and treating them as alternatives is the most common architectural mistake in monorepo setup.
Key point: pnpm workspaces solve the dependency problem—how packages are linked, hoisted, and resolved. Nx solves the orchestration problem—which tasks run, in what order, and what can be skipped because nothing changed. A monorepo needs both, and the question is which orchestration tool to layer on top.
Why the two layers exist
The linking problem. A monorepo contains packages that depend on each other. Without a workspace mechanism, each package would need npm link or a published version to resolve its sibling. pnpm workspaces automate this: the pnpm-workspace.yaml file defines the package directories, and pnpm install creates symlinks in node_modules so that @repo/ui resolves to the local package rather than a registry version. The workspace: protocol makes the local dependency explicit and is replaced with a version range at publish time .
The orchestration problem. A monorepo with ten packages and a build script in each requires running the scripts in dependency order, caching the results, and skipping packages that have not changed. pnpm’s -r run build runs the script in every package but does not guarantee topological order and does not cache. Nx and Turborepo exist to fill this gap. Nx provides a project graph, affected-project detection, remote caching, and code generation. Turborepo provides caching and task pipelines with a simpler configuration .
The scale problem. At small scale, the difference between the orchestration tools is minimal. At large scale—dozens of packages, multiple teams, CI pipelines that must complete in minutes rather than hours—the orchestration tool becomes the difference between a usable monorepo and an unmaintainable one. The decision matrix from the community tooling reference puts Turborepo as best for simple task running and Nx as best for code generation and plugin ecosystem, with pnpm workspaces as the best dependency manager underneath both .
a. The pnpm Workspace Foundation
The pnpm-workspace.yaml file at the repository root defines the package locations. The convention is to separate applications from shared packages.
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
A package that depends on a sibling uses the workspace: protocol. The protocol tells pnpm to link the local package and, at publish time, replaces the protocol with the actual version number .
// packages/ui/package.json
{
"name": "@repo/ui",
"dependencies": {
"@repo/types": "workspace:*"
}
}
The pnpm commands are filter-based. pnpm --filter @repo/web dev runs the dev script in the web package. pnpm -r run build runs build in every package that has it, but the execution order is not topological unless the packages are connected in a way that pnpm can detect. For dependency-ordered execution, the orchestration tool is required .
A Vue component library in this structure is a package under packages/ that builds with Vite’s library mode and exports its components from an index.ts. The consuming application depends on it with workspace:* and imports from the package name. The library’s vite.config.ts uses vite-plugin-dts to generate the type declarations, and its package.json points main, module, and types to the build output .
b. Nx on Top of pnpm
Nx is the heavier option. It provides a project graph, code generators, module boundary enforcement, and Nx Cloud for remote caching. The @nx/vue plugin generates Vue applications and libraries with the Nx conventions . A Vue app is created with nx g @nx/vue:app apps/frontend/vue-app-1, and the plugin configures Vite, path aliases, and the project’s project.json .
The module boundary enforcement is the feature that distinguishes Nx from Turborepo. Each project is assigned tags in its project.json—type:feature, type:ui, scope:orders—and the root ESLint configuration defines depConstraints that allow or forbid imports between tags. A UI library tagged type:ui can import only from other type:ui projects, and a feature library tagged scope:orders can import from scope:shared but not from scope:products . The constraint is enforced at lint time, which means an import that violates the architecture fails CI before it reaches review.
The cost is configuration complexity. Nx requires project.json files, a root nx.json, and an understanding of the project graph. The community guidance is that Nx is appropriate for large teams (dozens of engineers) or organizations that need strong architectural constraints, while Turborepo is the more common choice for smaller teams that need caching and task ordering without the platform overhead .
c. Turborepo as the Lighter Alternative
Turborepo is the orchestration tool that pairs naturally with pnpm workspaces for teams that do not need Nx’s code generation or boundary enforcement. The turbo.json file defines the task pipeline, and the caching is configured per task.
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
The dependsOn: ["^build"] means that a package’s build task runs only after its dependencies’ build tasks have completed. This is the topological ordering that pnpm does not provide. The outputs field tells Turborepo which files to cache, so a package whose source has not changed can be restored from cache instead of rebuilt.
A production-ready Electron/Vue monorepo template uses pnpm workspaces for dependency management and Turborepo for build orchestration, with the Vue renderer as one of the packages . The template’s structure is the pattern: apps/ for the deployable applications, packages/ for the shared libraries, pnpm-workspace.yaml for linking, and turbo.json for the task graph.
Complete Example Session
# ============================================
# PART 1: WORKSPACE CONFIGURATION
# ============================================
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
# ============================================
# PART 2: SHARED UI LIBRARY
# ============================================
# packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "vite build"
},
"peerDependencies": {
"vue": "^3.5.0"
}
}
# ============================================
# PART 3: APPLICATION DEPENDING ON UI
# ============================================
# apps/web/package.json
{
"name": "@repo/web",
"dependencies": {
"@repo/ui": "workspace:*",
"vue": "^3.5.0"
}
}
# ============================================
# PART 4: INSTALL AND LINK
# ============================================
pnpm install
# Links @repo/ui into apps/web/node_modules
# ============================================
# PART 5: TURBOREPO PIPELINE
# ============================================
# turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
# ============================================
# PART 6: BUILD IN DEPENDENCY ORDER
# ============================================
pnpm turbo run build
# Builds @repo/ui first, then @repo/web
# ============================================
# PART 7: NX ALTERNATIVE
# ============================================
# Create workspace
npx create-nx-workspace@latest
# Add Vue plugin
nx add @nx/vue
# Generate app
nx g @nx/vue:app apps/frontend/vue-app-1
# ============================================
# PART 8: NX TAGS AND BOUNDARIES
# ============================================
# packages/ui/project.json
{
"tags": ["type:ui", "scope:shared"]
}
# apps/web/project.json
{
"tags": ["type:app", "scope:web"]
}
# Root .eslintrc: depConstraints enforce allowed imports
# ============================================
# PART 9: AFFECTED BUILD
# ============================================
# Nx only rebuilds what changed
nx affected -t build
# Turborepo uses cache
pnpm turbo run build --filter=...[HEAD^]
# ============================================
# PART 10: PUBLISHING
# ============================================
# pnpm replaces workspace:* with the version at publish
pnpm publish -r
# Or use Changesets for versioning
The ten parts trace the workspace foundation, the library/application relationship, the Turborepo pipeline, and the Nx alternative. Parts 5 and 6 show the topological build; Parts 7 and 8 show the Nx generator and boundary tags.
Quick Reference
Tool Responsibilities
| Layer | Tool | Solves |
|---|---|---|
| Dependency management | pnpm workspaces | Linking, hoisting, resolution |
| Task orchestration | Turborepo or Nx | Ordering, caching, affected |
| Publishing | Changesets | Versioning, changelogs |
pnpm Workspace Commands
| Command | Purpose |
|---|---|
pnpm install | Install all, link workspaces |
pnpm --filter @repo/web dev | Run script in one package |
pnpm -r run build | Run script in all packages |
pnpm add zod --filter @repo/api | Add dep to one package |
pnpm add @repo/ui --filter @repo/web --workspace | Add workspace dep |
Nx vs. Turborepo
| Aspect | Nx | Turborepo |
|---|---|---|
| Task caching | Yes (Nx Cloud optional) | Yes (built-in) |
| Code generation | Yes | No |
| Module boundaries | Yes (ESLint tags) | No |
| Config complexity | Moderate to high | Minimal |
| Best for | Large teams, strict architecture | Smaller teams, simple pipeline |
The workspace: Protocol
| Syntax | Meaning |
|---|---|
"pkg": "workspace:*" | Link local, use version at publish |
"pkg": "workspace:^" | Link local, use caret range at publish |
"pkg": "workspace:~" | Link local, use tilde range at publish |
Best Practices
✅ Do This:
# Separate apps and packages
packages:
- 'apps/*'
- 'packages/*' # ✅
// Use workspace:* for local dependencies
"@repo/ui": "workspace:*" // ✅
// Define outputs in turbo.json for caching
"outputs": ["dist/**"] // ✅
// Tag Nx projects for boundary enforcement
"tags": ["type:ui", "scope:shared"] // ✅
❌ Don’t Do This:
// Use a fixed version for a local dependency
"@repo/ui": "^1.0.0" // may resolve from registry, not local // ❌
# Run pnpm -r build and expect topological order
pnpm -r run build // no dependency ordering // ❌
# Choose Nx for a two-package monorepo
# The overhead exceeds the benefit // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Local package resolves from registry | Used a version range instead of workspace: | Use workspace:* |
| Build runs in wrong order | pnpm -r has no topological guarantee | Use Turborepo or Nx |
| Cache never hits | outputs not configured | Define outputs in task pipeline |
| Boundary violation reaches main | No tags or ESLint constraints | Enable @nx/enforce-module-boundaries |
| Nx too heavy for team | Chose Nx without needing codegen | Use pnpm + Turborepo |
Real-World Examples
1. Workspace File
packages: ['apps/*', 'packages/*']
2. Workspace Dependency
"@repo/ui": "workspace:*"
3. Turborepo Pipeline
{ "build": { "dependsOn": ["^build"] } }
4. Nx Vue App
nx g @nx/vue:app apps/frontend/vue-app-1
5. Nx Tags
"tags": ["type:ui", "scope:shared"]
6. Nx Affected Build
nx affected -t build
7. pnpm Filter
pnpm --filter @repo/web dev
8. pnpm Add Workspace Dep
pnpm add @repo/ui --filter @repo/web --workspace
9. Publish with Workspace Protocol
pnpm publish -r # replaces workspace:* with version
10. Changesets for Versioning
pnpm changeset && pnpm changeset version
Visual
The Two Layers
┌──────────────────────────────────────────────────────────────┐
│ ORCHESTRATION LAYER │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Turborepo or Nx ││
│ │ Task ordering, caching, affected detection ││
│ └──────────────────────────┬──────────────────────────────┘│
│ │ │
│ ▼ │
│ DEPENDENCY LAYER │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ pnpm workspaces ││
│ │ Linking, hoisting, resolution ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ The orchestration tool runs on top of the workspace. │
│ Neither replaces the other. │
│ │
└──────────────────────────────────────────────────────────────┘
The Directory Structure
┌──────────────────────────────────────────────────────────────┐
│ monorepo/ │
│ ├── pnpm-workspace.yaml │
│ ├── turbo.json (or nx.json) │
│ ├── apps/ │
│ │ └── web/ (@repo/web, deployable) │
│ └── packages/ │
│ ├── ui/ (@repo/ui, shared components) │
│ └── types/ (@repo/types, shared types) │
│ │
│ apps/ depend on packages/ via workspace:* │
│ │
└──────────────────────────────────────────────────────────────┘
Turborepo Task Graph
┌──────────────────────────────────────────────────────────────┐
│ pnpm turbo run build │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ @repo/types │ builds first │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ @repo/ui │ depends on types │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ @repo/web │ depends on ui │
│ └─────────────────┘ │
│ │
│ ^build means "build dependencies first" │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Dependency layer | pnpm workspaces |
| Orchestration layer | Turborepo or Nx |
| pnpm workspace file | pnpm-workspace.yaml |
| Local dependency protocol | workspace:* |
| Turborepo config | turbo.json |
| Nx Vue plugin | @nx/vue |
| Nx boundary enforcement | ESLint tags + depConstraints |
| Recommended stack (most teams) | pnpm + Turborepo |
| Recommended stack (large teams) | pnpm + Nx |
Key takeaways:
- pnpm workspaces and Nx solve different problems. pnpm handles dependency linking and resolution; Nx handles task orchestration, caching, and code generation. Treating them as alternatives is a category error. The production stack layers one on top of the other .
- The
workspace:protocol is what makes local linking explicit. A dependency declared as"@repo/ui": "workspace:*"resolves to the local package during development and is replaced with the actual version at publish time. A plain version range may resolve from the registry instead of the local package . - Turborepo provides the topological ordering that pnpm lacks.
pnpm -r run buildruns the script in every package but does not guarantee that dependencies build before dependents. ThedependsOn: ["^build"]inturbo.jsonis what makes the build order correct . - Nx is the heavier option and should be chosen for the right reasons. Its distinguishing features are code generation and module boundary enforcement. A team that needs those features gets value from Nx; a team that only needs caching and task ordering gets the same benefit from Turborepo with less configuration .
- The Vue-specific tooling is mature in both paths. The
@nx/vueplugin generates Vue applications and libraries with the Nx conventions . The pnpm + Turborepo path has production-ready templates for Vue applications and component libraries . The choice is not constrained by Vue support.
Remember: A large-scale Vue monorepo is a stack, not a tool. The bottom layer is the package manager, and pnpm is the current standard because of its disk efficiency and strict workspace: protocol. The top layer is the orchestration tool, and the choice between Turborepo and Nx is a choice about how much platform the team needs. Turborepo is the minimal orchestration layer: task pipelines, caching, and dependency ordering. Nx is the full platform: project graph, generators, boundary enforcement, and remote caching. Both sit on top of pnpm, and neither replaces it. The monorepo that skips the orchestration layer has correct dependencies but no build ordering; the monorepo that skips the workspace layer has orchestration but broken local linking. The production setup has both.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!