← Back to Blog

Monorepo Architecture with pnpm and Turborepo

Why Monorepo?

Every project I've started in the last two years is a monorepo. Not because it's trendy, but because the alternative — maintaining multiple repos with cross-dependencies — becomes a coordination nightmare the moment you have more than two packages that need to stay in sync.

@webhouse/cms is 8 packages. CodePromptMaker is 5. fysiodk-aalborg-sport has a web app and shared packages. In each case, a monorepo means one PR, one CI run, one version bump, and one mental model.

The Stack: pnpm + Turborepo

I use pnpm workspaces for package management and Turborepo for build orchestration. The combination gives you:

- Strict dependency isolation — pnpm's non-flat node_modules catches missing dependency declarations that npm and yarn silently allow
- Cached builds — Turborepo hashes inputs and skips unchanged packages. On a cold CI run, the CMS monorepo builds in ~45 seconds. On a warm cache, it's under 10.
- Parallel executionturbo run build builds all packages in the correct dependency order, maximizing parallelism

Project Structure Pattern

Every monorepo follows the same layout:

``
packages/
core/ → shared logic
web/ → Next.js app
cli/ → command-line tools
shared/ → shared types and utilities
scripts/ → automation scripts
turbo.json → pipeline config
pnpm-workspace.yaml
`

The packages/ directory contains publishable or deployable units. The scripts/ directory contains automation — deploy scripts, code audits, migration helpers. Everything is TypeScript, everything is built with tsup, and every package has a consistent tsconfig.json that extends a root config.

Lessons Learned

- Version all packages together. Keeping independent versions per package sounds flexible but creates dependency hell. All @webhouse/cms-* packages share the same version number.
- Use
tsup` for library packages. It handles CJS/ESM dual output, declaration files, and tree-shaking with zero config.
- Automate the boring parts. A single script bumps all versions, and GitHub Actions publishes everything via OIDC. No manual npm publish, ever.
- The monorepo is the documentation. When all related code lives together, the import graph IS the architecture diagram.