Skip to content
Back to Knowledge Base

Yarn Workspaces: Setup, Commands, and Best Practices

Yarn workspaces let a single repository (or a monorepo) hold multiple packages that are developed together. You declare them in the root package.json workspaces field, and yarn install resolves the whole project at once, hoisting shared dependencies to the root and linking workspace packages to each other.

For the full configuration reference, see the Yarn workspaces documentation.

A Yarn workspace is a repository whose root package.json has a workspaces field listing the directories that hold package.json files. Mark the root private so it can't be published:

package.json
{
"name": "my-workspace",
"private": true,
"workspaces": ["apps/*", "packages/*"],
}

Every directory matched by those globs that contains a package.json becomes a workspace. A common layout separates applications from shared packages:

  • Directorymy-workspace/
    • Directoryapps/
      • Directoryweb/
        • package.json
    • Directorypackages/
      • Directoryshared-ui/
        • package.json
    • package.json
Section titled “3. Link workspaces with the workspace: protocol”

To depend on another workspace, reference it with the workspace: protocol (Yarn 2 and later) so Yarn always resolves it to the local package. On publish, Yarn replaces workspace:* with the package's exact version:

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

On Yarn 1 (Classic) there's no workspace: protocol, so reference the package by a version range that matches the local version, as npm workspaces do.

Run yarn install once at the root to install every package's dependencies and link the internal ones.

yarn workspaces foreach runs a command across workspaces. The -A (--all) flag selects every workspace, -p runs in parallel, and -t orders execution topologically so dependencies build first:

Terminal window
yarn workspaces foreach -A run build # every workspace
yarn workspaces foreach -Apt run build # parallel, dependencies first
yarn workspace web run build # a single workspace

Yarn 4 ships foreach out of the box. On Yarn 3, add it first with yarn plugin import workspace-tools. On Yarn 1 (Classic), the equivalent is yarn workspaces run build.

Yes. With the default node-modules linker, Yarn hoists shared dependencies to the root node_modules, so each version is installed once and packages resolve it by walking up the directory tree. Only conflicting versions get nested inside an individual workspace's node_modules folder.

Hoisting means a workspace can import a dependency it never declared, which breaks the moment that package is published or moved. Declare every import in the workspace's own package.json. To restrict hoisting, set nmHoistingLimits in .yarnrc.yml (Yarn 2+). Yarn 1 used the nohoist field instead. Yarn PnP sidesteps hoisting entirely by resolving imports from a lockfile-driven map instead of node_modules.

Do Yarn workspaces replace a monorepo tool?

Section titled “Do Yarn workspaces replace a monorepo tool?”

No. Yarn workspaces install and link packages, foreach -t orders scripts, and --since selects workspaces changed since a git ref, but nothing is cached and that change detection compares files, not task inputs, so CI still reruns tasks a change couldn't have affected.

When that becomes the bottleneck, add Nx on top without changing how Yarn works:

Terminal window
npx nx@latest init

Your existing package.json scripts keep working and Yarn keeps managing installs.

Before Nx, Yarn runs a script across workspaces, but it runs every task every time and has no notion of which workspaces a change affects:

Terminal window
yarn workspaces foreach -A run build # every workspace
yarn workspace web run build # a single workspace

After nx init, run the same scripts through Nx:

Terminal window
nx run-many -t build # every project
nx build web # a single project
nx affected -t build # only projects touched by your changes

The first run executes your scripts. A second run with no changes is restored from the cache, and nx affected skips the projects your change does not touch.

nx init writes an nx.json with targetDefaults that control how targets behave:

nx.json
{
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
},
"dev": {
"continuous": true,
},
},
}

cache: true makes a target cacheable: Nx hashes each project's inputs (source files, dependencies, and config) and restores its outputs from the cache when nothing has changed. Nothing is cached unless you opt in, so you stay in control. dependsOn: ["^build"] builds a project's dependencies first. continuous: true marks tasks that don't exit, like dev servers and watchers, so tasks that depend on them don't wait for them to complete.

Nx Cloud adds more ways to speed up CI for a Yarn workspace: remote caching shares the cache across CI runs and teammates, Nx Agents distribute tasks across machines, and self-healing CI proposes fixes when tasks fail. For the full walkthrough, see Adding Nx to an existing monorepo and CI setup.

Working with a different package manager? See pnpm workspaces, npm workspaces, or Bun workspaces. New to monorepos? Start with what a monorepo is and why teams use one.

Last updated: