Workspaces
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/*",
"packages/*"
]
}
my-monorepo
├─ 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:
- Specify which workspace a package should be installed to
- Use this workspace in other workspaces
- 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:
- The workspace dependencies you have installed are checked
- Any workspaces are symlinked (opens in a new tab) into
node_modules
, meaning that you can import them like normal packages - 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.