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
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 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)
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.