Workspaces are the building blocks of your monorepo. Each app and package you add to your monorepo will be inside its own workspace.

Workspaces are managed by your package manager, so make sure you've set that up first.

Configuring workspaces

To use workspaces, you must first declare their file system locations to your package manager.

A common convention we recommend is having top-level apps/ and packages/ directories. This isn't a requirement - just a suggested directory structure.

The apps folder should contain workspaces for launchable apps, such as a Next.js (opens in a new tab) or Svelte (opens in a new tab) app.

The packages folder should contain workspaces for packages used by either an app or another package.

Add the folders you want to configure as workspaces to the workspaces field in your root package.json file. This field contains a list of workspace folders in the form of globs:

  "name": "my-monorepo",
  "version": "1.0.0",
  "workspaces": [
├─ docs
├─ apps
│  ├─ api
│  └─ mobile
├─ packages
│  ├─ tsconfig
│  └─ shared-utils
└─ sdk

In the example above, all directories inside my-monorepo/apps/ and my-monorepo/packages/ are workspaces, and the my-monorepo/docs directory itself is also a workspace. my-monorepo/sdk/ is not a workspace, as it is not included in the workspace configuration.

Naming workspaces

Each workspace has a unique name, which is specified in its package.json:

  "name": "shared-utils"

This name is used to:

  1. Specify which workspace a package should be installed to
  2. Use this workspace in other workspaces
  3. Publish packages: it'll be published on npm under the name you specify

You can use an npm organization or user scope to avoid collisions with existing packages on npm. For instance, you could use @mycompany/shared-utils.

Workspaces which depend on each other

To use a workspace inside another workspace, you'll need to specify it as a dependency, using its name.

For instance, if we want apps/docs to import packages/shared-utils, we'd need to add shared-utils as a dependency inside apps/docs/package.json:

  "dependencies": {
    "shared-utils": "*"

The * allows us to reference the latest version of the dependency. It saves us from needing to bump the versions of our dependency if the versions of our packages change.

Just like a normal package, we'd need to run install from root afterwards. Once installed, we can use the workspace as if it were any other package from node_modules. See our section on sharing code for more information.

Managing workspaces

In a monorepo, when you run an install command from root, a few things happen:

  1. The workspace dependencies you have installed are checked
  2. Any workspaces are symlinked (opens in a new tab) into node_modules, meaning that you can import them like normal packages
  3. Other packages are downloaded and installed into node_modules

This means that whenever you add/remove workspaces, or change their locations on the filesystem, you'll need to re-run your install command from root to set up your workspaces again.

You don't need to re-install every time your source code changes inside a package - only when you change the locations (or configuration) of your workspaces in some way.

If you run into issues, you may have to delete each node_modules folder in your repository and re-run install to correct it.