11.1 · difficulty 2/4 · 12 min read
pnpm Workspace Basics
pnpm workspace is the foundation of a Vite monorepo — the workspace protocol, dependency hoisting strategy, and the tradeoffs between Hoisting and Isolation.
pnpm-workspace.yaml
Declare which directories in your monorepo are workspace packages:
packages:
- "apps/*" # all directories under apps/
- "packages/*" # all directories under packages/
- "examples/*" # all directories under examples/
- "!**/node_modules/**" # exclude node_modulesGlob syntax:
apps/*: all directories one level under apps/packages/**: all levels under packages/!pattern: exclude matching directories
The workspace:* Protocol
Use the workspace:* protocol to reference local packages from one another:
{
"dependencies": {
"@vite-mastery/ui": "workspace:*",
"@vite-mastery/mdx-components": "workspace:*"
}
}What workspace:* means:
- During development: links to the local
packages/ui/directory (symlink) - When publishing: replaced with the actual version number (if publishing to npm)
- When not publishing: keep
workspace:*as-is
Common pnpm Workspace Commands
# Operate on all workspaces from the root
pnpm -r run build # recursively run build for all packages
pnpm -r run typecheck # recursively run typecheck
# Operate on a specific package only
pnpm --filter @vite-mastery/web dev
pnpm --filter ./packages/ui build
# Install a dependency in a specific package
pnpm add react --filter @vite-mastery/web
pnpm add -D typescript --filter @vite-mastery/ui
# Install dependencies for all packages
pnpm installKey .npmrc Configuration
# Do not hoist node_modules to the root (stricter isolation)
shamefully-hoist=false
# Automatically install peer dependencies
auto-install-peers=true
# Strict mode: dependencies not declared in package.json cannot be used
# (prevents phantom dependencies)
strict-peer-dependencies=falseThe Tradeoffs of Dependency Hoisting
pnpm does not hoist dependencies to the root node_modules by default:
# Hoisting off (pnpm default):
apps/web/node_modules/react ← web's own react
packages/ui/node_modules/react ← ui's own react
# Each package has its own node_modules — more isolated
# Hoisting on (shamefully-hoist=true):
node_modules/react ← one shared react for all packages
# Saves disk space, but risks phantom dependenciesRecommendation: keep shamefully-hoist=false (the pnpm default) and use auto-install-peers=true to handle peer deps.
Workspace Details That Matter For Vite
A Vite monorepo differs from a plain Node monorepo because source packages may enter the dev server directly. packages/ui/src/Button.tsx can be imported by apps/web, then transformed and hot-updated by Vite.
That creates a few rules:
- Local packages should declare
exportsclearly inpackage.json; do not rely only on path aliases. - Framework runtimes such as React or Vue belong in
peerDependenciesfor component packages. - If an app consumes local package source, make sure Vite can process that package's TS/JSX/CSS.
- If a local package is built before consumption, make sure
dist,types, andexportsagree.
The most common failure is "React became two Reacts". If apps/web and packages/ui install separate React instances, HMR, context, and hooks can break. The fix is not to blindly enable hoisting; it is to put React in the component package's peerDependencies and let the app provide the single runtime.
How To Read pnpm --filter
pnpm --filter is the daily productivity tool in a monorepo:
pnpm --filter @my/web dev
pnpm --filter @my/ui build
pnpm --filter "...@my/web" build
pnpm --filter "@my/ui..." testCommon meanings:
| Pattern | Meaning |
|---|---|
@my/web | Select only that package |
...@my/web | Select @my/web and its dependencies |
@my/ui... | Select @my/ui and packages that depend on it |
./packages/ui | Select by path |
Once you use filters well, you do not need pnpm -r run build for every change. In a large monorepo, that directly improves feedback speed.
Self-check
- What is the difference between
workspace:*andworkspace:^1.0.0? - What effect does
shamefully-hoist=falsehave on your project? Why is it called "shamefully"? - What is the difference between
pnpm -r run buildand Turborepo'sturbo build? - If
packages/uineeds to use utility functions frompackages/utils, how should you configure that?
# Configure pnpm-workspace.yaml for a monorepo with the following structure:
# - apps/web (Next.js main site)
# - apps/docs (documentation site)
# - packages/design-tokens (design tokens)
# - packages/ui (component library)
# - packages/hooks (React hooks)
# - packages/utils (utility functions)
# - examples/ contains multiple standalone examples that should NOT be part of the workspace
# Write the contents of pnpm-workspace.yaml and .npmrc