Building a Monorepo with Turborepo and pnpm
$ cat --info "building-a-monorepo.mdx"
> published: 2026-05-20 | read_time: 2 min read | category: engineering
Why a Monorepo?
When my portfolio grew beyond a single Next.js app — adding a backend API for project data, a chatbot service, and shared utilities — managing separate repositories became painful. Every change that touched shared types required coordinating deploys across repos.
I chose Turborepo with pnpm workspaces for three reasons:
- Incremental builds — Turbo caches build outputs and only rebuilds what changed
- Shared configuration — ESLint, TypeScript, and Prettier configs live in one place
- Atomic changes — A single PR can update the API contract and the frontend consumer
The Structure
The monorepo splits into two main packages:
packages/frontend— Next.js 14 with Chakra UI, Three.js for 3D elements, and Framer Motionpackages/backend— Express API serving project data, connected to MongoDB
Shared TypeScript interfaces live in the root and are referenced by both packages.
Deployment Strategy
Each package deploys independently:
- Frontend → Vercel (automatic deploys on push to main)
- Backend → Railway (Dockerfile-based, auto-scales)
The key insight was configuring Vercel's root directory to packages/frontend and letting Turbo handle the build graph. One pnpm turbo build --filter=frontend command resolves all internal dependencies before bundling.
Lessons Learned
The biggest surprise was dependency hoisting. pnpm's strict node_modules structure caught several packages that relied on phantom dependencies — packages that worked in flat node_modules but broke under pnpm's isolated structure. Fixing these early saved debugging time later.