Yarn 07 ๐งถ Yarn Workspaces and Monorepos
A monorepo stores multiple packages in a single repository. Yarn Workspaces is the mechanism that makes this practical: it installs all packages’ dependencies together, links packages to each other without publishing to a registry, and provides commands to run scripts across the entire project. In Yarn Berry (2+), the feature set expands significantly with the workspace: protocol, constraints, catalogs, and focused installs. The goal is the same across versionsโtreat a collection of packages as a single project for installation and linkingโbut the tools for controlling that behavior differ.
Key point: A Yarn workspace is any directory with a package.json that is matched by the workspaces glob in the root manifest. The root itself is always a workspace, and it must be private so that it cannot be published.
Why workspaces exist
The multi-repository problem. When a project consists of a frontend, a backend, and a shared library, splitting them into separate repositories means every change to the shared library requires publishing a new version, updating the consumers, and managing three sets of pull requests. A monorepo with workspaces collapses this: the shared library is linked directly, and a change to it is immediately visible to the consuming packages without any registry round-trip .
The dependency duplication problem. Without workspaces, each package in a monorepo has its own node_modules and its own copy of React, TypeScript, and every shared dependency. With Yarn’s default node-modules linker, shared dependencies are hoisted to the root node_modules, installed once, and resolved by walking up the directory tree . One lockfile at the root governs the entire project .
The linking problem. Package A depends on Package B, both in the same repository. Without a workspace protocol, Package A must reference Package B by a version number that exists on the registry. The workspace: protocol (Yarn 2+) tells Yarn to resolve the dependency to the local workspace regardless of the version, and replaces the protocol with the actual version only when the package is published .
The orchestration problem. Running a build across ten packages requires running ten commands in dependency order. yarn workspaces foreach provides the traversal: -A selects all workspaces, -p runs in parallel, and -t orders execution topologically so that a package’s dependencies build before the package itself .
The CI problem. A full yarn install in a large monorepo installs every dependency for every package. When deploying a single application, this is wasteful. yarn workspaces focus installs only the dependencies required by a specific workspace and its transitive dependencies, skipping the rest of the monorepo entirely .
The trade-off. Workspaces reduce duplication and simplify cross-package development, but they introduce a single point of failure. A broken script in one workspace can affect the entire repository’s CI. Good hygieneโensuring every workspace has its own declared dependencies and that status checks passโis essential to keep the monorepo manageable .
a. Declaring workspaces and linking packages
Workspaces are declared in the root package.json with a workspaces array containing glob patterns. Every directory matching a pattern and containing a package.json becomes a workspace. The root manifest must have "private": true; Yarn refuses to treat a publishable package as a workspace root .
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
A common layout separates applications (apps/) from shared libraries (packages/). After adding the workspaces field, running yarn install at the root resolves and installs every workspace’s dependencies and links the internal packages together .
To depend on another workspace, Yarn 2+ uses the workspace: protocol. The range can be workspace:*, workspace:^, or an explicit version. Yarn always resolves the dependency to the local package; the range only determines what the published manifest will contain if the package is ever published .
{
"name": "@myorg/app",
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
On Yarn 1 (Classic), there is no workspace: protocol. The dependency is referenced by a version range that matches the local package’s version, and Yarn links it during install .
b. Running commands across workspaces
yarn workspaces foreach is the traversal command. It runs a script in every workspace, or a subset defined by flags. The flags control scope, ordering, and concurrency.
# Run "build" in every workspace, in dependency order
yarn workspaces foreach -Apt run build
# Run "test" only in workspaces changed since origin/main
yarn workspaces foreach -Apt --since=origin/main run test
# Run "lint" in one workspace and its dependencies
yarn workspaces foreach -Rpt --from '@myorg/app' run lint
The -A (or --all) flag includes every workspace. The -t (or --topological) flag orders execution so that dependencies run before dependents. The -p (or --parallel) flag runs scripts concurrently. The --since flag limits execution to workspaces with changes since a git reference .
In Yarn 4, foreach is built in. In Yarn 3, it requires the workspace-tools plugin: yarn plugin import workspace-tools. In Yarn 1, the equivalent is yarn workspaces run build, which runs the script sequentially across all workspaces .
c. Focused installs and monorepo hygiene
yarn workspaces focus installs only what a specific workspace needs. Given a workspace name, it resolves the workspace’s dependencies, extends the list to include transitive workspace dependencies, and excludes everything else. This is useful for CI pipelines that deploy a single application or for local development when only one package is being modified .
# Install only the dependencies for @myorg/app
yarn workspaces focus @myorg/app
# Install all workspaces but only production dependencies
yarn workspaces focus -A --production
In Yarn 4, focus works out of the box. In Yarn 3, it requires the workspace-tools plugin. The --production flag omits devDependencies, which is the correct behavior for building a deployable artifact .
Hoisting is the default with the node-modules linker. Shared dependencies are installed once at the root, and any workspace can import them without declaring them. This is convenient but dangerous: a workspace that imports a hoisted dependency it never declared will break when the package is published or moved. The fix is to declare every import in the workspace’s own package.json. To restrict hoisting, set nmHoistingLimits in .yarnrc.yml. Yarn PnP avoids the problem entirely by resolving imports from a lockfile-driven map rather than node_modules .
Complete Example Session
# ============================================
# PART 1: INITIALIZE THE MONOREPO ROOT
# ============================================
mkdir my-monorepo && cd my-monorepo
yarn init -p
# Creates package.json with "private": true
# ============================================
# PART 2: DECLARE WORKSPACES
# ============================================
# Edit package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
# ============================================
# PART 3: CREATE A WORKSPACE
# ============================================
mkdir -p packages/utils
cd packages/utils
yarn init -p
# Edit package.json: "name": "@myorg/utils", "version": "1.0.0"
# ============================================
# PART 4: LINK A WORKSPACE
# ============================================
# In apps/web/package.json:
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
# ============================================
# PART 5: INSTALL EVERYTHING
# ============================================
cd ../..
yarn install
# Hoists shared deps to root node_modules,
# links @myorg/utils into apps/web.
# ============================================
# PART 6: RUN SCRIPTS ACROSS WORKSPACES
# ============================================
yarn workspaces foreach -Apt run build
# -A: all workspaces
# -p: parallel
# -t: topological (deps first)
# ============================================
# PART 7: FOCUSED INSTALL FOR DEPLOYMENT
# ============================================
yarn workspaces focus @myorg/app --production
# Installs only app's deps, omitting devDependencies.
# ============================================
# PART 8: SHARE DEPENDENCY VERSIONS (YARN 4)
# ============================================
# .yarnrc.yml:
catalog:
react: ^19.0.0
# In workspace package.json:
"dependencies": {
"react": "catalog:"
}
# ============================================
# PART 9: ENFORCE CONSISTENCY WITH CONSTRAINTS
# ============================================
# yarn.config.cjs:
module.exports = {
async constraints({ Yarn }) {
for (const dep of Yarn.dependencies({ ident: 'react' })) {
dep.update('19.0.0');
}
}
};
# Run: yarn constraints --fix
The nine parts trace the workspace lifecycle: initializing the root, declaring globs, creating a package, linking via the workspace: protocol, installing, running scripts with foreach, using focused installs for deployment, sharing dependency versions via catalogs, and enforcing consistency with constraints.
Quick Reference
Workspace Declaration
| Element | Location |
|---|---|
workspaces array | Root package.json |
private: true | Root package.json (required) |
workspace: protocol | Workspace package.json dependencies |
catalog: protocol | Yarn 4, .yarnrc.yml |
Foreach Flags
| Flag | Purpose |
|---|---|
-A, --all | Every workspace |
-p, --parallel | Run concurrently |
-t, --topological | Dependencies first |
--since=<ref> | Changed since git ref |
-R, --recursive | Workspace and its dependencies |
Focus Flags
| Flag | Purpose |
|---|---|
| (none) | Current workspace and deps |
-A, --all | Entire project |
--production | Omit devDependencies |
Best Practices
โ Do This:
{
"private": true,
"workspaces": ["packages/*"]
}
<!-- Root must be private for workspaces --> # โ
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
<!-- Use workspace: protocol for internal deps --> # โ
yarn workspaces foreach -Apt run build
<!-- Topological order for build scripts --> # โ
โ Don’t Do This:
{
"name": "root",
"workspaces": ["packages/*"]
}
<!-- Missing "private": true --> # โ
# Rely on hoisted dependency without declaring it
<!-- Breaks when package is published --> # โ
yarn workspaces foreach -A run build
<!-- No -t; dependencies may build after dependents --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
workspaces ignored | Root not marked private: true | Add "private": true |
| Internal dep not linked | No workspace: protocol | Use workspace:^ or workspace:* |
| Hoisting hides undeclared imports | node-modules linker hoists to root | Declare every import in workspace manifest |
foreach not found in Yarn 3 | Plugin not installed | yarn plugin import workspace-tools |
| Focus installs too much | Focus includes transitive deps | Expected behavior; scope workspace correctly |
Real-World Examples
1. Root Workspace Declaration
{
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
2. Link Internal Package
{
"dependencies": {
"@myorg/ui": "workspace:*"
}
}
3. Build All, Topologically
yarn workspaces foreach -Apt run build
4. Run Test Only on Changed
yarn workspaces foreach -Apt --since=origin/main run test
5. Focus on One App
yarn workspaces focus @myorg/app
6. Focus Production Only
yarn workspaces focus -A --production
7. Catalog for Shared Version
catalog:
react: ^19.0.0
8. Reference Catalog
{
"dependencies": {
"react": "catalog:"
}
}
9. Constraints Fix
yarn constraints --fix
10. Yarn 1 Equivalent
yarn workspaces run build
Visual
Monorepo Structure with Workspaces
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ my-monorepo/ โ
โ โโโ package.json (private: true) โ
โ โ โโโ workspaces: ["apps/*", "packages/*"]โ
โ โโโ yarn.lock โ
โ โโโ node_modules/ (hoisted) โ
โ โโโ apps/ โ
โ โ โโโ web/ โ
โ โ โ โโโ package.json โ
โ โ โโโ api/ โ
โ โ โโโ package.json โ
โ โโโ packages/ โ
โ โโโ utils/ โ
โ โโโ package.json โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ yarn install
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Root node_modules/ โ
โ โโโ react/ (hoisted, shared) โ
โ โโโ typescript/ (hoisted, shared) โ
โ โโโ @myorg/utils/ (symlink to packages) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Every package shares the root node_modules and the single yarn.lock.
Foreach Execution Order
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ yarn workspaces foreach -Apt run build โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Topological sort (dependencies first) โ
โ โ
โ 1. @myorg/utils (no deps) โ
โ 2. @myorg/ui (depends on utils) โ
โ 3. @myorg/app (depends on ui, utils) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Build runs in order, in parallel where โ
โ dependencies permit. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Without -t, the build order is arbitrary. With -t, a package’s dependencies are built before the package itself.
Focused Install
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Full install โ
โ apps/web + apps/api + packages/utils โ
โ All dependencies installed. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ yarn workspaces focus @myorg/app โ
โ โ
โ apps/web only. โ
โ packages/utils (dependency) included. โ
โ apps/api excluded. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Focused installs reduce CI time and disk usage when deploying a single application.
Summary
| Item | Value |
|---|---|
| Workspace declaration | workspaces array in root package.json |
| Root requirement | "private": true |
| Internal dependency | workspace:^ or workspace:* |
| Hoisting | Default with node-modules linker |
| Traversal command | yarn workspaces foreach |
| Topological flag | -t, --topological |
| Focused install | yarn workspaces focus <name> |
| Production focus | --production |
| Shared versions (Yarn 4) | catalog: in .yarnrc.yml |
| Consistency rules | yarn constraints |
Key takeaways:
- The root must be private. Yarn workspaces only function when the root
package.jsonhas"private": true. The root itself is always a workspace . - The
workspace:protocol links internal packages. It resolves to the local workspace regardless of registry versions, and the range determines the published manifest . - Hoisting is the default. Shared dependencies are installed once at the root. This is efficient but permits undeclared imports. Declare every dependency in the workspace that uses it .
foreachorchestrates across workspaces. The-Aflag selects all,-pruns in parallel, and-torders execution by dependency .focusinstalls only what a workspace needs. It is the command for CI and deployment when a full install is unnecessary .- Yarn 4 adds catalogs and constraints. Catalogs share dependency versions across workspaces; constraints enforce consistency rules .
Remember: A Yarn workspace is a directory with a package.json that the root manifest points to. The root owns the lockfile and the hoisted node_modules. The workspace: protocol links packages without a registry. The foreach command runs scripts across the project. The focus command installs only what a specific workspace needs. Together, these features turn a collection of packages into a single, coherent project.
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!