How to Set Up pnpm Workspaces in a Monorepo: Complete Configuration Guide
Configure pnpm workspaces by creating a pnpm-workspace.yaml file at your repository root that defines glob patterns for your apps/* and packages/* directories, then run pnpm install to link dependencies across all workspaces.
Setting up pnpm workspaces in a monorepo allows you to manage multiple applications and shared libraries from a single repository while maintaining a unified dependency store. The yangshun/tech-interview-handbook repository demonstrates this architecture by organizing runnable code into apps/ and packages/ directories, using pnpm to handle cross-package linking and dependency deduplication. This guide walks through the exact configuration files and commands used in that production monorepo.
Understanding the Monorepo Structure
The yangshun/tech-interview-handbook organizes code into two primary directories that pnpm treats as workspace members.
The apps/ Directory
The apps/* glob pattern captures standalone applications that can be deployed independently. In this repository, this includes the Docusaurus website and the Next.js portal, each with their own package.json and entry points.
The packages/ Directory
The packages/* glob pattern contains reusable libraries and configuration packages shared across applications. This includes shared ESLint configurations (eslint-config-tih), Tailwind CSS presets (tailwind-config), and centralized TypeScript configurations (tsconfig).
Configuring pnpm Workspaces
Setting up pnpm workspaces requires two configuration files at the repository root to define workspace boundaries and ensure tooling compatibility.
Step 1: Create the Workspace Manifest
Create a pnpm-workspace.yaml file at the repository root to declare which directories contain workspace packages:
packages:
- 'apps/*'
- 'packages/*'
This manifest tells pnpm to treat every subdirectory within apps/ and packages/ as an independent workspace member while sharing a single node_modules store.
Step 2: Declare Workspaces in package.json
Add a workspaces field to your root package.json that mirrors the manifest patterns:
{
"workspaces": [
"packages/*",
"apps/*"
]
}
This ensures compatibility with tools like npm and Turborepo that read workspace definitions from package.json rather than the pnpm-specific manifest.
Step 3: Pin the pnpm Version
Specify the exact pnpm version in your root package.json engines field to ensure consistency across environments:
{
"engines": {
"pnpm": "8.15.8"
}
}
Enable and install this version using Corepack:
corepack enable
corepack prepare pnpm@8.15.8 --activate
Installing Dependencies and Running Commands
Once configured, pnpm manages dependencies across all workspaces from the repository root.
Initial Installation
Run the following command at the repository root to install all dependencies:
pnpm install
This creates a single .pnpm-store at the root and links each workspace's node_modules to the appropriate versions, automatically hoisting shared dependencies to save disk space.
Running Scripts Across Workspaces with Turborepo
The yangshun/tech-interview-handbook uses Turborepo to orchestrate commands across workspaces. The root package.json defines scripts that invoke turbo:
{
"scripts": {
"dev": "turbo dev",
"build": "turbo build"
}
}
Run development servers for all workspaces:
pnpm dev
Turborepo respects the workspace dependency graph, launching the portal UI (--filter=portal...) and UI library (--filter=ui...) in the correct order based on the turbo.json pipeline configuration.
Adding New Packages to the Monorepo
To extend the monorepo with a new shared library:
- Create a directory under
packages/orapps/:
mkdir -p packages/utils
cd packages/utils
pnpm init -y
-
Write your TypeScript or JavaScript code and configure the
package.jsonwith the appropriate name and entry points. -
Return to the repository root and run:
pnpm install
The new package is instantly linked into the workspace graph and can be imported by other workspaces using the declared package name.
Shared Tooling and Configuration
The monorepo centralizes development tooling to ensure consistency across all workspaces.
Turborepo Pipeline Configuration
The turbo.json file defines reusable pipelines for build, test, lint, and dev tasks, ensuring that Turborepo caches outputs and runs tasks in dependency order.
Centralized TypeScript Configurations
Shared TypeScript presets live in packages/tsconfig/ and are referenced by individual apps via extends:
{
"extends": "tsconfig/react-library.json"
}
This guarantees identical compiler options across the Docusaurus website, Next.js portal, and shared libraries.
Unified Linting and Styling
ESLint and Tailwind configurations are packaged as packages/eslint-config-tih and packages/tailwind-config, then consumed by apps to eliminate duplication and maintain code style consistency.
Summary
Setting up pnpm workspaces in a monorepo requires configuring the workspace manifest and root package.json to define workspace boundaries, then using pnpm's unified installation to link dependencies across packages.
- Create
pnpm-workspace.yamlat the repository root with glob patterns forapps/*andpackages/* - Mirror these patterns in the root
package.jsonworkspacesfield for tooling compatibility - Pin the pnpm version (e.g.,
8.15.8) inenginesand enable via Corepack - Run
pnpm installfrom the root to create a shared store and link all workspace dependencies - Use Turborepo to orchestrate scripts across workspaces while respecting dependency graphs
Frequently Asked Questions
What is the difference between pnpm-workspace.yaml and the workspaces field in package.json?
The pnpm-workspace.yaml file is the primary manifest that pnpm reads to determine which directories contain workspace packages, while the workspaces field in package.json exists primarily for compatibility with npm and tools like Turborepo that expect workspace definitions in the package manifest. Both should contain identical glob patterns to ensure consistent behavior across tooling.
How does pnpm handle dependency installation differently than npm in a workspace?
pnpm creates a single content-addressable store (.pnpm-store) at the repository root and hard-links packages into each workspace's node_modules, automatically deduplicating shared dependencies across the monorepo. This approach uses significantly less disk space than npm's nested node_modules structure while maintaining strict isolation between packages, and it enables instant linking of local workspace packages without publishing to a registry.
Can I add a new workspace after the initial setup?
Yes, you can add new workspaces at any time by creating a new directory under the configured glob patterns (such as apps/ or packages/), initializing it with a package.json, and running pnpm install from the repository root. pnpm automatically detects the new workspace, links it into the dependency graph, and makes it available for import by other workspaces using the package name declared in its package.json.
Why should I pin the pnpm version in a monorepo?
Pinning the pnpm version (such as 8.15.8) in the root package.json engines field ensures that all developers and CI environments use the exact same package manager version, preventing inconsistencies in lockfile formats, workspace resolution logic, and dependency tree structures that could arise from version mismatches. This practice, combined with Corepack, guarantees reproducible installs across the entire team.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →