Home/Learn/JavaScript & TypeScript/Workspaces, Monorepos and Shipping a Package

Workspaces, Monorepos and Shipping a Package

Advanced
Tooling

How a frontend, a backend and a shared library live in one repository — and what changes when you publish one of them.

Overview

Once a product has more than one deployable, the shared code question arrives: duplicate it, publish it, or put everything in one repository. Workspaces make the third option practical — several packages, one install, and local packages linked as if they were published. It is worth understanding even if you never configure one, because a large share of professional codebases are laid out this way and knowing the shape makes them navigable rather than intimidating.

Workspaces

One install, several packages, local links instead of publishing.

workspace:* links the local package
// root package.json
{ "private": true, "workspaces": ["apps/*", "packages/*"] }

// apps/web/package.json
{ "dependencies": { "@aicancode/shared": "workspace:*" } }

// Layout
//   apps/web        Next frontend
//   apps/api        Node backend
//   packages/shared types + zod schemas used by both
//   packages/ui     component library

npm install                       // installs everything once
npm run build -w apps/web         // run a script in one workspace

// The payoff: change a schema in packages/shared and both apps
// fail to typecheck immediately — no publish step, no version drift.

// The cost: slower CI unless you cache and only build what changed.
// Turborepo or Nx exist for exactly that.

Publishing a Package

The fields that decide whether your package works for consumers.

exports, files, types, sideEffects
{
  "name": "@aicancode/shared",
  "version": "1.2.0",
  "type": "module",
  "files": ["dist"],              // what is actually published
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "sideEffects": false            // lets consumers tree-shake it
}

// exports replaces "main" and is a hard boundary — consumers can
// import only the paths you list, so internals stay internal.

// Ship the .d.ts files or TypeScript consumers get 'any'.
// tsup or unbuild handle the ESM + CJS + types build in one step.

// Versioning is a promise: patch = fix, minor = additive,
// major = something broke. Changesets automates the changelog.

Working on a Dependency Locally

Testing a change to a library against a real app before publishing it.

npm link, and why npm pack is safer
# In the library
npm link
# In the consuming app
npm link @aicancode/shared

# Common failure: two copies of React in the tree, which produces
# "Invalid hook call". Check with:
npm ls react
# Fix by marking react a peerDependency in the library and
# pointing the bundler at the app's copy.

# Often simpler than linking — pack and install the real tarball,
# which tests what consumers will actually receive:
npm pack                      # -> aicancode-shared-1.2.0.tgz
npm install ../shared/aicancode-shared-1.2.0.tgz

Key Points to Remember

  • 1Workspaces install once and link local packages, so a shared schema change breaks both apps at compile time
  • 2The exports field defines the package's public surface and replaces main — unlisted paths cannot be imported
  • 3Publish the .d.ts files or TypeScript consumers see any; set sideEffects: false so they can tree-shake
  • 4Semver is a promise to consumers: patch fixes, minor adds, major breaks
  • 5npm link can produce duplicate React copies and "Invalid hook call" — npm pack tests the real published artifact

Interview Questions

Sign in to ask Aria
1

What problem do npm workspaces solve in a multi-app repository?

Medium
2

What does the exports field in package.json do?

Hard
3

Why does npm link sometimes cause an "Invalid hook call" error?

Hard

Ask Aria about Workspaces, Monorepos and Shipping a Package

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…