How to Bootstrap a Monorepo with PNPM: A Complete Guide

Are you struggling with managing multiple projects, each with its own dependency maze? You've probably heard about monorepos as a solution, but setting one up feels like navigating through a complex labyrinth of configuration files and tooling decisions. Perhaps you've tried different package managers and found yourself switching back to npm out of frustration.

Don't worry – you're not alone. Many developers face these exact challenges when trying to set up a monorepo. The good news is that PNPM offers a straightforward solution that addresses common pain points like dependency management, performance issues, and workspace organization.

Why PNPM for Your Monorepo?

Before diving into the setup, let's understand why PNPM stands out. According to discussions in the Node.js community, developers consistently praise PNPM for its superior performance and efficient dependency management. Unlike traditional package managers, PNPM uses a unique symlink strategy that:

  • Saves significant disk space by storing dependencies in a content-addressable store

  • Provides true dependency isolation between packages

  • Offers faster installation times compared to npm and Yarn

  • Prevents the dreaded "dependency hell" through strict package isolation

Prerequisites

Before we begin, ensure you have:

  1. Node.js installed (version 16.13 or higher recommended)

  2. Git for version control

  3. Basic familiarity with package managers and JavaScript/TypeScript development

Initial Setup

Let's start by creating our monorepo structure. Open your terminal and follow these steps:

# Create and navigate to your project directory
mkdir pnpm-monorepo
cd pnpm-monorepo

# Initialize git
git init

# Create initial .gitignore
echo "node_modules" > .gitignore

Installing PNPM

First, let's install PNPM globally. The recommended way is using Corepack, which comes with Node.js 16.13+:

corepack enable
corepack prepare pnpm@latest --activate

Verify your installation:

pnpm -v

Setting Up Your Monorepo Structure

Now that we have PNPM installed, let's create the basic structure for our monorepo. One of the most common pain points developers face is managing multiple package.json files with different peer dependencies. We'll address this by setting up a clear workspace structure.

  1. Initialize your project:

pnpm init

# Set the Node.js version requirement
npm pkg set engines.node=">=16.13.0"

# Specify the package manager
npm pkg set packageManager="pnpm@latest"

# Set type as module for ES modules support
npm pkg set type="module"
  1. Create a workspace configuration file:

# Create pnpm-workspace.yaml
echo "packages:
  - 'packages/*'
  - 'apps/*'" > pnpm-workspace.yaml

# Create directories for your packages and applications
mkdir packages apps

Setting Up Development Tools

To ensure consistency across your monorepo, let's set up essential development tools. Based on community recommendations, we'll implement:

  1. Prettier for code formatting:

# Install Prettier
pnpm add -D prettier

# Create Prettier configuration
echo '{
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 80,
  "tabWidth": 2
}' > .prettierrc.json

# Create Prettier ignore file
echo "coverage
public
dist
pnpm-lock.yaml
pnpm-workspace.yaml" > .prettierignore
  1. ESLint for code quality:

# Install ESLint and its dependencies
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Create ESLint configuration
echo '{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "root": true
}' > .eslintrc.json

Creating Your First Packages

Now that we have our basic structure and tools in place, let's create our first packages. A common setup includes shared utilities and frontend applications. We'll address the need for efficient type-sharing between frontend and backend in this example.

Creating a Shared Package

  1. Navigate to the packages directory and create a common utilities package:

cd packages
mkdir common
cd common
pnpm init
  1. Update the package.json for the common package:

{
  "name": "@your-org/common",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w"
  }
}
  1. Add TypeScript configuration:

# Create tsconfig.json
echo '{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}' > tsconfig.json

Creating a Frontend Application

  1. Navigate to the apps directory and create a new React application:

cd ../../apps
mkdir web-app
cd web-app
pnpm init
  1. Install necessary dependencies:

pnpm add react react-dom
pnpm add -D @types/react @types/react-dom typescript vite @vitejs/plugin-react
  1. Update the package.json to include the common package:

{
  "name": "@your-org/web-app",
  "dependencies": {
    "@your-org/common": "workspace:*",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

Managing Dependencies Effectively

One of the biggest challenges in monorepos is managing dependencies across multiple packages. Here are some best practices to follow:

  1. Use Workspace Protocol: Always use the workspace:* protocol when referencing internal packages:

{
  "dependencies": {
    "@your-org/common": "workspace:*"
  }
}
  1. Manage Peer Dependencies: For shared packages, clearly specify peer dependencies to avoid version conflicts:

{
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

Advanced Configuration and Best Practices

To address the concerns about dependency control and package isolation, let's implement some advanced configurations:

Setting Up Git Hooks and Commit Validation

  1. Install necessary tools:

pnpm add -D husky @commitlint/cli @commitlint/config-conventional lint-staged
  1. Initialize Husky:

pnpm exec husky init
  1. Create commit message validation:

# Create commitlint config
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs

# Add commit-msg hook
echo "npx --no -- commitlint --edit \${1}" > .husky/commit-msg

Scripts for Common Tasks

Add these helpful scripts to your root package.json:

{
  "scripts": {
    "build": "pnpm -r build",
    "dev": "pnpm -r dev",
    "lint": "pnpm -r lint",
    "test": "pnpm -r test",
    "clean": "pnpm -r clean"
  }
}

Troubleshooting Common Issues

Here are solutions to common problems you might encounter:

  1. Dependency Resolution Issues If you're experiencing dependency conflicts, try:

pnpm store prune  # Clean up the store
pnpm install --force  # Force reinstall dependencies
  1. Type Definition Problems For TypeScript projects experiencing type definition issues:

  • Ensure your tsconfig.json includes proper path mappings

  • Use project references for better type resolution

  • Consider using tRPC for full-stack type safety

  1. Performance Optimization To improve build and development performance:

  • Use pnpm instead of npm install in CI/CD pipelines

  • Implement proper caching strategies

  • Consider using Turborepo for build caching

Conclusion

Setting up a monorepo with PNPM doesn't have to be overwhelming. By following this guide, you've created a solid foundation that addresses common pain points like dependency management, type sharing, and workspace organization. The structure we've set up provides:

  • Efficient dependency management through PNPM's unique approach

  • Clear separation of concerns with the workspace structure

  • Strong typing support across packages

  • Consistent code quality through shared tooling

Remember to regularly update your dependencies and maintain your workspace configuration as your project grows. The effort you put into proper setup will pay dividends in improved development efficiency and easier maintenance.

Additional Resources

Raymond Yeh

Raymond Yeh

Published on 22 January 2025

Choosing a CMS?

Wisp is the most delightful and intuitive way to manage content on your website. Integrate with any existing website within hours!

Choosing a CMS
Related Posts
How to Bootstrap a Monorepo with Turborepo: A Comprehensive Guide

How to Bootstrap a Monorepo with Turborepo: A Comprehensive Guide

Tired of slow builds and dependency nightmares? Learn to set up a streamlined monorepo with Turborepo and tackle common development challenges like a pro!

Read Full Story
How to Bootstrap a Monorepo with Nx: A Comprehensive Guide

How to Bootstrap a Monorepo with Nx: A Comprehensive Guide

Simplify your project management! This guide on bootstrapping a monorepo with Nx covers best practices to tackle common pain points in development.

Read Full Story
Monorepo Tooling in 2025: A Comprehensive Guide

Monorepo Tooling in 2025: A Comprehensive Guide

Struggling with monorepo management? Our 2025 guide reveals essential tools and techniques for smoother CI/CD processes, improved performance, and better organization.

Read Full Story
Loading...