Skip to content

Monorepo with pnpm Guide

Introduction

When we first start building projects, we usually create a separate repository for each tech stack.

For example:

  • A React frontend → one repository
  • A Node.js backend → another repository

This approach is called a polyrepo, where each service or application lives in its own repository.

But managing multiple repositories becomes repetitive and sometimes messy. We need to configure ESLint, Prettier, TypeScript, and other tools separately for each project.

A monorepo solves this problem.

In a monorepo:

  • Frontend and backend live in the same repository
  • Shared configurations can be reused
  • Shared packages (like UI components) can be consumed easily
  • No need to manage multiple repositories

In this guide, we’ll build a simple monorepo using pnpm.


What is pnpm?

pnpm is a fast and efficient package manager.

The “p” stands for performant npm.

Unlike npm or yarn, pnpm:

  • Stores dependencies in a global store
  • Avoids duplicate installations
  • Saves disk space
  • Works very well for monorepos

Project Structure

We will create this structure:

pnpm-tutorial/
├── apps/
│ └── frontend-react/
├── packages/
│ └── shared-ui/
├── pnpm-workspace.yaml
└── package.json

Step 1: Initialize the Root Project

Terminal window
pnpm init

This creates a root package.json.


Step 2: Create Apps and Packages Folders

Terminal window
mkdir apps packages

This is a common convention:

  • apps/ → runnable applications
  • packages/ → shared libraries

Step 3: Create pnpm Workspace File

Create a file named:

pnpm-workspace.yaml

Add:

packages:
- "apps/*"
- "packages/*"

This tells pnpm which folders belong to the workspace.


Step 4: Create a Frontend App

Move into apps folder:

Terminal window
cd apps
pnpm create vite

Choose:

  • React
  • TypeScript

This creates a React app (default: localhost:5173).


Running App Scripts from Root

Instead of going inside apps/frontend-react, we can run scripts from the root using:

Terminal window
pnpm --filter <package-name> <command>

Example root package.json scripts:

"scripts": {
"dev:fe": "pnpm --filter frontend-react dev",
"build:fe": "pnpm --filter frontend-react build",
"lint:fe": "pnpm --filter frontend-react lint",
"preview:fe": "pnpm --filter frontend-react preview"
}

Here:

  • frontend-react = package name
  • dev, build = script names

Step 5: Create a Shared UI Package

Now we create a reusable UI library.

Terminal window
cd packages
mkdir shared-ui
cd shared-ui
pnpm init

Update package.json:

{
"name": "shared-ui",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "rm -rf dist && tsc"
}
}

Install Dependencies

Terminal window
pnpm add react
pnpm add typescript @types/react -D

Add TypeScript Config

Create tsconfig.json:

{
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"lib": ["ES2017", "DOM"],
"esModuleInterop": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2017",
"declaration": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}

Create a Button Component

Create folder structure:

shared-ui/
└── src/
├── Button.tsx
└── index.ts

Button.tsx

import React from "react";
export function Button(props: {
onClick: () => void;
children: React.ReactNode;
}) {
return <button onClick={() => props.onClick()}>{props.children}</button>;
}

index.ts

export * from "./Button";

Build the Shared Package

From root:

Terminal window
pnpm --filter shared-ui build

This generates a dist/ folder.


Step 6: Use Shared Package in Frontend

Install it inside frontend:

Terminal window
pnpm add shared-ui --filter frontend-react --workspace

Now frontend-react/package.json will include:

"shared-ui": "workspace:*"

Use Button in Frontend

In App.tsx:

import { Button } from "shared-ui";
import "./App.css";
function App() {
return (
<>
<h1>Home page</h1>
<Button onClick={() => alert("Hello world!")}>Click me</Button>
</>
);
}
export default App;

Run:

Terminal window
pnpm dev:fe

You should now see the shared button working. Final output


Final Thoughts

This is a basic monorepo setup using pnpm.

You can enhance this setup using tools like:

  • Turborepo
  • Nx

These tools provide:

  • Smart caching
  • Faster builds
  • Detecting only changed packages
  • Better scalability

Source Code

GitHub Repository: https://github.com/romanpoudel/pnpm-monorepo


Thank you for reading this blog.