Local package development
Use workspaces with clear boundaries
Develop related packages together without erasing the fact that each package is published and versioned separately.
Workspaces let you develop @acme/slugify-title and a demo app in one repository. npm installs linked copies locally. Each package still publishes and versions on its own.
Root package.json:
{
"name": "slugify-title-monorepo",
"private": true,
"workspaces": [
"packages/slugify-title",
"apps/demo-blog"
]
}
The demo app declares a normal dependency:
{
"name": "demo-blog",
"dependencies": {
"@acme/slugify-title": "0.1.0"
}
}
From the repo root, run npm install. npm symlinks the workspace package into apps/demo-blog/node_modules/@acme/slugify-title.
Hoisting hides missing declarations
Workspaces hoist dependencies to the root node_modules. Your package might import unicode-properties even though you forgot to list it in packages/slugify-title/package.json. The demo still works locally because the dependency is hoisted from somewhere else.
Catch that with a production-only install inside the package directory:
cd packages/slugify-title
npm install --omit=dev
node -e "import('@acme/slugify-title')"
If that fails, add the missing runtime dependency to the package that imports it, not to the root.
Pack each publishable unit separately
Before release, run npm pack inside packages/slugify-title, not only at the repo root. The tarball must contain everything the package needs without the monorepo layout around it.
My rule: every workspace that publishes to npm gets its own package.json, files list, tests, and pack smoke test. Shared tooling can live at the root, but ownership stays per package.
Run workspace scripts from the root when you can:
npm test -w @acme/slugify-title
npm run build -w @acme/slugify-title
That keeps CI commands short while still targeting one publishable unit.
When the demo app imports the workspace package, treat that import like an external consumer. If the demo needs a feature, add it through the public export map, not through a deep relative path into packages/slugify-title/src/.
Version each workspace package independently. A breaking change in the library gets a major bump even if the demo app in the same repo stays on 0.x for now.
Lesson completed