Currently viewing the human version
Switch to AI version

Why Workspaces Exist (And Why You'll Love-Hate Them)

Monorepo vs Multiple Repos

Remember trying to work on a shared component library? You'd make a change, publish a test version to npm, wait for it to sync, update your app's package.json, install, then discover your change broke everything. Repeat 47 times until you want to throw your laptop out the window.

Workspaces solve this by symlinking your packages locally. Change your component library, and your app sees it instantly. No publishing, no waiting, no version juggling. It's magic when it works.

The Pre-Workspace Nightmare

Before workspaces, managing multiple related packages was pure hell:

You'd have my-ui-lib, my-utils, and my-app in separate repos. Want to add a prop to a component? Edit the component, commit, publish to npm, bump version in your app, install, test, find a bug, repeat. Each round trip took 5-10 minutes. I've burned entire afternoons on simple changes.

Different packages inevitably ended up with conflicting dependency versions. Your UI lib uses React 17, your app uses React 18, your utils package somehow pulled in React 16. Good fucking luck debugging that mess.

CI/CD becomes a coordination nightmare. You can't just deploy your app - you need to ensure all dependencies are published first, in the right order, with compatible versions. One typo in a package name and your entire deployment pipeline explodes.

How Workspaces Actually Work

Workspace Symlink Architecture

When you run `yarn install` in a workspace repo, Yarn does something clever. It reads all the package.json files, figures out which dependencies overlap, and installs shared ones at the root. Then it creates symlinks so each package can find what it needs.

Your project structure looks like this:

my-monorepo/
├── node_modules/        # Shared dependencies live here
├── packages/
│   ├── ui-lib/          # This is symlinked as node_modules/@company/ui-lib
│   └── utils/           # This is symlinked as node_modules/@company/utils
└── apps/
    └── web-app/         # Can import from @company/ui-lib directly

The symlinking is what makes changes appear instantly. Edit a file in packages/ui-lib, and your app sees it immediately because it's just following a symbolic link.

When It Actually Helps

Component Library Architecture

Component libraries: Perfect. Build your components, storybook, and example apps all in one repo. No more test releases.

Shared utilities: Backend services sharing database models, validation schemas, or common middleware. Change once, use everywhere, deploy independently.

Design systems: UI components, design tokens, icons, and documentation sites all living together. Changes propagate instantly across all consuming applications.

Full-stack apps: Share TypeScript types between frontend and backend. Change your API response shape, and your frontend sees the new types immediately.

The Learning Curve (It's Brutal)

Developer Debugging Workspaces

Workspaces aren't intuitive. The first time you set them up, you'll spend a weekend figuring out why nothing works. Common stumbling blocks:

  • Forgetting to use workspace:^ protocol in dependencies
  • Mixing yarn/npm/pnpm commands (lockfile chaos ensues)
  • TypeScript not recognizing workspace packages (need project references)
  • Hot reloading breaking mysteriously
  • Circular dependencies that aren't obvious until everything breaks

Budget 2-3 weeks before workspaces feel natural. The payoff is worth it, but the initial frustration is real. I've seen senior engineers rage quit workspace migrations after hitting their 5th cryptic error message.

Once you get it working though, the productivity boost is addictive. No more publishing test versions. No more dependency hell. No more waiting for npm to sync. Just edit code and watch everything update in real-time.

What Actually Works vs What's Overhyped

Reality Check

Yarn Workspaces

npm Workspaces

pnpm

Lerna

Nx

Actually Easy to Set Up

Yes

Yes (if simple)

No (weird config)

Hell no

Abandon hope

Fast Enough

Fine

Slow as hell

Fast but breaks

Meh

Fast when working

Cross-package Linking

Works 90% of time

Basic but stable

Works but cryptic errors

Reliable

Overengineered

Will Make You Cry

Sometimes

Rarely

Often

Always

Definitely

TypeScript Pain

Medium

High

Medium

High

Low (when working)

Documentation Quality

Good

Decent

Confusing

Outdated

Encyclopedia nobody reads

Community Help

Active

Basic

Passionate but small

Dead

Enterprise consultants

Setting Up Workspaces (Without Losing Your Mind)

Workspace Setup Guide

The Basic Structure That Actually Works

Don't overthink the folder structure. Here's what works in practice:

my-monorepo/
├── package.json              # Root config - keep it simple
├── .yarnrc.yml              # Optional, but saves headaches later
├── apps/                    # Things you deploy
│   ├── web-app/
│   └── api-server/
├── packages/                # Things you share
│   ├── ui-components/
│   ├── shared-utils/
│   └── types/
└── tools/                   # Build stuff (optional)
    └── eslint-config/

Start simple. Add complexity when you actually need it, not because some blog post told you to.

Root package.json (The Part That Breaks Everything)

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "build": "yarn workspaces foreach -pt run build",
    "test": "yarn workspaces foreach run test",
    "dev": "yarn workspace web-app dev"
  },
  "devDependencies": {
    "typescript": "^5.2.0"
  }
}

Critical shit to remember:

.yarnrc.yml (Skip This Initially)

Most tutorials dump a giant .yarnrc.yml config at you. Don't. Start without it. Add this later when you hit specific problems:

nodeLinker: node-modules
enableGlobalCache: true

That's it. The "advanced" config can wait until you actually understand what's breaking.

Workspace Dependencies (This Is Where It Gets Weird)

Instead of regular versions, use the `workspace:` protocol:

{
  "name": "web-app",
  "dependencies": {
    "ui-components": "workspace:*",
    "shared-utils": "workspace:^"
  }
}

The protocols:

  • workspace:* - Use exact current version
  • workspace:^ - Use compatible version range
  • workspace:~ - Use compatible patch range

When you publish, Yarn converts these to real version numbers automatically. During development, they're just symlinks to your local code.

Common Setup Failures (And How to Fix Them)

"Cannot resolve module 'ui-components'"

Your workspace isn't linked properly. Run:

rm -rf node_modules yarn.lock
yarn install

If that doesn't work, check your package names. The dependency name in package.json must exactly match the "name" field in the target package.

TypeScript can't find workspace packages

You need project references. Add this to your root tsconfig.json:

{
  "references": [
    {"path": "./apps/web-app"},
    {"path": "./packages/ui-components"}
  ]
}

And in each package's tsconfig.json:

{
  "extends": "../../tsconfig.json",
  "references": [
    {"path": "../ui-components"}
  ]
}

Skip this and TypeScript compilation will be slow as hell.

Hot reload stops working

This happens constantly. First try restarting your dev server. If that doesn't work:

## Nuclear option
rm -rf node_modules .yarn/cache
yarn install

Sometimes symlinks get corrupted and only a full reinstall fixes it.

"yarn workspaces foreach" does nothing

You're using old Yarn. Upgrade to Yarn 3.x+:

yarn set version stable

Yarn 1.x uses yarn workspace (singular) with different syntax.

Commands You'll Actually Use

## Install everything
yarn install

## Add a dependency to specific workspace
yarn workspace ui-components add react

## Run script in all workspaces
yarn workspaces foreach run build

## Run script only in changed workspaces (requires git)
yarn workspaces foreach --since main run test

## Install only what's needed for one app (for Docker)
yarn workspaces focus web-app

That `--since` flag is magic for CI/CD. Only runs tests/builds in packages that actually changed.

Publishing (Don't Overthink It)

For most teams, manual publishing is fine:

## Version and publish specific packages
yarn workspace ui-components version patch
yarn workspace ui-components npm publish

If you need automated releases, install changesets. But get basic workspaces working first before adding more complexity.

The TypeScript Trap

TypeScript Project References

TypeScript Build Pipeline

TypeScript project references are mandatory for performance, but they're confusing as hell. Here's the minimal setup:

Root tsconfig.json:

{
  "references": [
    {"path": "./apps/web-app"},
    {"path": "./packages/ui-components"}
  ]
}

Each workspace tsconfig.json:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist"
  },
  "references": [
    {"path": "../ui-components"}
  ]
}

Skip the fancy path mapping. Let the symlinks do their job. Less configuration means fewer things to break.

The key insight: start simple, add complexity gradually, and expect to delete node_modules at least twice while figuring it out.

The Questions Everyone Actually Asks

Q

My workspace packages aren't updating when I change them. What the hell?

A

This is workspace linking breaking, which happens more often than anyone admits.

First, check your package names

  • the dependency name in package.json must exactly match the target package's "name" field. One typo and nothing works.If that's not it, try these in order:```bash# Step 1:

Basic cleanupyarn install# Step 2: Nuclear optionrm -rf node_modules yarn.lockyarn install# Step 3:

Check your workspace protocol# Make sure you're using "workspace:*", not "^1.0.0"```Also, some packages need building before other packages can use them. If you're importing from dist/ instead of src/, build the dependency first.

Q

Why does TypeScript take 10 minutes to check my workspace?

A

Because you didn't set up project references, and TypeScript is checking every single file every time. Fix this by adding references to your root tsconfig.json:json{"references": [{"path": "./apps/web-app"},{"path": "./packages/ui-components"}]}And to each workspace's tsconfig.json:json{"references": [{"path": "../shared-utils"}]}Without this, TypeScript will be stupid slow. With it, builds are incremental and actually fast.

Q

How do I add a dependency to just one package without breaking everything?

A

bash# Add to specific workspaceyarn workspace my-ui-lib add react# Add dev dependencyyarn workspace my-ui-lib add -D @types/react# Or cd into the package and run yarn add normallycd packages/my-ui-libyarn add reactDon't run npm install anywhere. Mixing package managers will create lockfile conflicts that take hours to untangle.

Q

Can I use different versions of React across workspaces?

A

Yes, but you'll hate yourself. Yarn will hoist compatible versions to the root, but conflicting versions stay in individual package folders:node_modules/├── react@18.2.0 # Most packages use this└── packages/legacy-app/ └── node_modules/ └── react@17.0.0 # This one needs the old versionThis works technically but kills bundle sharing and makes debugging a nightmare. Either upgrade everything to the same version or question why you're using workspaces.

Q

My hot reload stopped working again. Fix?

A

This happens constantly. Restart your dev server first. If that doesn't work:bash# Delete everything and reinstallrm -rf node_modules .yarn/cacheyarn installSometimes the symlinks get corrupted and only a full reinstall fixes them. It's annoying but takes 30 seconds.

Q

How do I only run tests in packages that actually changed?

A

Use the --since flag:bash# Only test changed packages since main branchyarn workspaces foreach --since main run test# Include packages that depend on changed onesyarn workspaces foreach --since main -R run buildThis is perfect for CI. No point testing 50 packages when you only changed one file.

Q

Docker builds are failing with workspace errors. Why?

A

Your Dockerfile is probably copying files in the wrong order. Copy all package.json files first, then install, then copy source:dockerfileFROM node:18-alpine# Copy package files first (for better caching)COPY package.json yarn.lock ./COPY apps/*/package.json apps/*/COPY packages/*/package.json packages/*/# Install dependenciesRUN yarn install --frozen-lockfile# Copy source codeCOPY . .# BuildRUN yarn buildThe key is copying all package.json files before running install.

Q

Should I use Lerna with workspaces?

A

No. Lerna is basically dead in 2025. The maintainers stopped caring years ago, and Yarn workspaces do everything Lerna did but better. Just use yarn workspaces foreach for running commands across packages.

Q

How do I publish packages without wanting to die?

A

For simple cases, just publish manually:bashyarn workspace my-package version patchyarn workspace my-package npm publishIf you need coordination across multiple packages, install changesets:bashyarn add -D @changesets/cliyarn changeset initBut get basic workspaces working before adding more complexity.

Q

Everything is broken and I want to start over

A

Delete these and reinstall:bashrm -rf node_modulesrm -rf .yarn/cacherm yarn.lockyarn installIf that doesn't work, check your workspace configuration in the root package.json. The most common issue is forgetting "private": true.Still broken? Your workspace protocols are probably wrong. Check that dependencies use workspace:* not version numbers.

Resources That Don't Suck

Related Tools & Recommendations

compare
Recommended

Pick Your Monorepo Poison: Nx vs Lerna vs Rush vs Bazel vs Turborepo

Which monorepo tool won't make you hate your life

Nx
/compare/nx/lerna/rush/bazel/turborepo/monorepo-tools-comparison
100%
tool
Recommended

Lerna - Automates the Annoying Parts of Publishing Multiple npm Packages

Stops you from publishing Package A before Package B and getting angry Slack messages about broken installs.

Lerna
/tool/lerna/overview
39%
tool
Recommended

Lerna CI/CD Production Deployment - Stop Breaking Prod with Bad Releases

How to deploy Lerna packages without getting woken up by PagerDuty at 3am because something broke.

Lerna
/tool/lerna/ci-cd-production-deployment
39%
tool
Recommended

pnpm - Fixes npm's Biggest Annoyances

competes with pnpm

pnpm
/tool/pnpm/overview
38%
integration
Recommended

Vite + React 19 + TypeScript + ESLint 9: Actually Fast Development (When It Works)

Skip the 30-second Webpack wait times - This setup boots in about a second

Vite
/integration/vite-react-typescript-eslint/integration-overview
37%
howto
Recommended

Migrate from Webpack to Vite Without Breaking Everything

Your webpack dev server is probably slower than your browser startup

Webpack
/howto/migrate-webpack-to-vite/complete-migration-guide
36%
tool
Recommended

TypeScript - JavaScript That Catches Your Bugs

Microsoft's type system that catches bugs before they hit production

TypeScript
/tool/typescript/overview
23%
pricing
Recommended

Should You Use TypeScript? Here's What It Actually Costs

TypeScript devs cost 30% more, builds take forever, and your junior devs will hate you for 3 months. But here's exactly when the math works in your favor.

TypeScript
/pricing/typescript-vs-javascript-development-costs/development-cost-analysis
23%
tool
Recommended

JavaScript to TypeScript Migration - Practical Troubleshooting Guide

This guide covers the shit that actually breaks during migration

TypeScript
/tool/typescript/migration-troubleshooting-guide
23%
alternatives
Recommended

Turborepo Alternatives - When You're Done With Vercel's Bullshit

Escaping Turborepo hell: Real alternatives that actually work

Turborepo
/alternatives/turborepo/decision-framework
21%
tool
Recommended

Turborepo - Make Your Monorepo Builds Not Suck

Finally, a build system that doesn't rebuild everything when you change one fucking line

Turborepo
/tool/turborepo/overview
21%
tool
Recommended

ESLint - Find and Fix Problems in Your JavaScript Code

The pluggable linting utility for JavaScript and JSX

eslint
/tool/eslint/overview
21%
review
Recommended

ESLint + Prettier Setup Review - The Hard Truth About JavaScript's Golden Couple

After 7 years of dominance, the cracks are showing

ESLint
/review/eslint-prettier-setup/performance-usability-review
21%
news
Popular choice

Figma Gets Lukewarm Wall Street Reception Despite AI Potential - August 25, 2025

Major investment banks issue neutral ratings citing $37.6B valuation concerns while acknowledging design platform's AI integration opportunities

Technology News Aggregation
/news/2025-08-25/figma-neutral-wall-street
21%
tool
Recommended

NGINX Ingress Controller - Traffic Routing That Doesn't Shit the Bed

NGINX running in Kubernetes pods, doing what NGINX does best - not dying under load

NGINX Ingress Controller
/tool/nginx-ingress-controller/overview
20%
tool
Recommended

Nx - Caches Your Builds So You Don't Rebuild the Same Shit Twice

Monorepo build tool that actually works when your codebase gets too big to manage

Nx
/tool/nx/overview
20%
tool
Recommended

Storybook - Build Components Without Your App's Bullshit

The tool most frontend teams end up using for building components in isolation

Storybook
/tool/storybook/overview
20%
alternatives
Recommended

Webpack is Slow as Hell - Here Are the Tools That Actually Work

Tired of waiting 30+ seconds for hot reload? These build tools cut Webpack's bloated compile times down to milliseconds

Webpack
/alternatives/webpack/modern-performance-alternatives
20%
tool
Recommended

Webpack Performance Optimization - Fix Slow Builds and Giant Bundles

compatible with Webpack

Webpack
/tool/webpack/performance-optimization
20%
howto
Recommended

Migrating CRA Tests from Jest to Vitest

compatible with Create React App

Create React App
/howto/migrate-cra-to-vite-nextjs-remix/testing-migration-guide
20%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization