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.jsonStep 1: Initialize the Root Project
pnpm initThis creates a root package.json.
Step 2: Create Apps and Packages Folders
mkdir apps packagesThis is a common convention:
apps/→ runnable applicationspackages/→ shared libraries
Step 3: Create pnpm Workspace File
Create a file named:
pnpm-workspace.yamlAdd:
packages: - "apps/*" - "packages/*"This tells pnpm which folders belong to the workspace.
Step 4: Create a Frontend App
Move into apps folder:
cd appspnpm create viteChoose:
- 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:
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 namedev,build= script names
Step 5: Create a Shared UI Package
Now we create a reusable UI library.
cd packagesmkdir shared-uicd shared-uipnpm initUpdate package.json:
{ "name": "shared-ui", "version": "1.0.0", "private": true, "main": "dist/index.js", "scripts": { "build": "rm -rf dist && tsc" }}Install Dependencies
pnpm add reactpnpm add typescript @types/react -DAdd 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.tsButton.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:
pnpm --filter shared-ui buildThis generates a dist/ folder.
Step 6: Use Shared Package in Frontend
Install it inside frontend:
pnpm add shared-ui --filter frontend-react --workspaceNow 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:
pnpm dev:feYou should now see the shared button working.

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.