When your project has a Next.js frontend, NestJS API, shared types, and validation schemas, a monorepo keeps everything in sync. We've refined this structure across multiple projects.
Why pnpm Over npm or Yarn
pnpm's hard-linked node_modules save 60%+ disk space. Strict dependency resolution prevents phantom dependencies — if a package isn't in your package.json, you can't import it. The workspace protocol makes local package references explicit and reliable.
Shared Packages
The key insight of monorepos is sharing code between frontend and backend. Our shared packages include TypeScript type definitions (used by both Next.js and NestJS), Zod validation schemas (same rules for frontend forms and backend API validation), and configuration (shared ESLint and TypeScript configs).
Turborepo Build Orchestration
Turborepo understands the dependency graph between packages. When you change a shared type, it rebuilds only the packages that depend on it. Build caching means unchanged packages skip compilation entirely. CI runs only affected tests.
Handling Non-Node Packages
In Errandoo, the Flutter mobile app lives in the monorepo but isn't managed by pnpm. We add a minimal package.json shim so Turborepo can include it in the dependency graph for build ordering, while Flutter's own toolchain handles the actual build.
When Monorepos Hurt
Monorepos add complexity. If your frontend and backend teams deploy independently and rarely share code, separate repos are simpler. The monorepo pays off when teams share types, validation logic, or configuration — which is most full-stack TypeScript projects.