Back to Blog

Next.js Monorepo + Turborepo: pnpm -w, Workspaces & CI (2026)

Production Next.js monorepo with Turborepo + pnpm workspaces. Covers pnpm -w / --workspace-root, turbo dev, catalogs, shared packages, remote caching, GitHub CI.

Viprasol Tech Team
16 min read
Updated 2026

Next.js Monorepo + Turborepo: pnpm -w, Workspaces & CI (2026)

TL;DR. The -w flag in pnpm (long form --workspace-root) tells pnpm to operate on the monorepo root instead of the current workspace package. Run pnpm add -D -w turbo to install Turborepo at the root so every app and package in the monorepo can use it. Without -w, pnpm installs into whichever package directory you happen to be standing in — which is rarely what you want for shared dev tools. This guide covers the full Next.js + Turborepo + pnpm workspaces stack: the -w flag, turbo dev, shared packages, pnpm 9 catalogs, remote caching, and a working CI pipeline.

As products grow, the "one repo per app" approach breaks down. Your web app, marketing site, and admin panel share the same design system. Your API types need to be available in both the frontend and the backend CLI. Without a monorepo, you are copying code or publishing private packages — both create drift. Turborepo solves this with task pipelines that understand the dependency graph, build caching that skips unchanged work, and remote caching that makes CI fast even as the repo grows. Combined with pnpm workspaces and the --workspace-root flag for root-level installs, it is the standard setup for production Next.js monorepos in 2026.


What Does pnpm -w / --workspace-root Do?

The -w flag (short for --workspace-root, also written -W) is one of the most-searched pnpm CLI options for a reason: it controls where pnpm installs a package when you run pnpm add inside a workspace.

Without -wWith -w
pnpm add zod inside apps/web/ adds Zod to apps/web/package.jsonpnpm add -w zod adds Zod to the root package.json
Each package can have its own version of ZodAll packages share one version (via hoisting)
Used for app-specific runtime depsUsed for shared dev tools like turbo, eslint, typescript, prettier

Concretely:

```bash

WRONG: installs turbo into apps/web only

cd apps/web pnpm add -D turbo

RIGHT: installs turbo at the monorepo root so every app + package can use it

cd .. pnpm add -D -w turbo

or from anywhere in the repo:

pnpm add -D --workspace-root turbo ```

If you try to install something at the root without -w, pnpm refuses:

``` ERR_PNPM_ADDING_TO_ROOT Running this command will add the dependency to the workspace root, which might not be what you want — if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). ```

That error message is exactly why developers Google "-w, --workspace-root" pnpm — they hit the warning and want to confirm what to do. Use -w when the dependency is a build tool (turbo, eslint, prettier, typescript), a workspace-wide dev script, or a type definition that belongs to the root. Use no flag (or --filter <pkg>) when the dependency is app-specific runtime code.


Repository Structure

``` my-app/ ├── apps/ │ ├── web/ # Main Next.js app │ ├── admin/ # Admin Next.js app │ └── docs/ # Documentation site ├── packages/ │ ├── ui/ # Shared React components │ ├── config/ │ │ ├── eslint/ # Shared ESLint config │ │ ├── typescript/ # Shared tsconfig bases │ │ └── tailwind/ # Shared Tailwind config │ ├── database/ # Prisma schema + client │ └── validators/ # Shared Zod schemas ├── turbo.json # Turborepo pipeline config ├── pnpm-workspace.yaml └── package.json ```


🌐 Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 1000+ projects.

  • React, Next.js, Node.js, TypeScript — production-grade stack
  • Fixed-price contracts — no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

Initial Setup

```bash

Create monorepo with Turborepo

npx create-turbo@latest my-app cd my-app

Or add Turborepo to an existing pnpm workspace (note the -w / --workspace-root flag)

pnpm add -D -w turbo

Add apps and packages

mkdir -p apps/web apps/admin packages/ui \ packages/config/eslint packages/config/typescript packages/config/tailwind \ packages/database packages/validators ```

Verify the workspace install:

```bash

Lists everything pnpm sees as a workspace package

pnpm -w list --depth -1 --json | jq '.[].name'

or human-readable

pnpm m ls ```


pnpm Workspace Configuration

```yaml

pnpm-workspace.yaml

packages:

  • "apps/*"
  • "packages/*"
  • "packages/config/*" ```

```json // package.json (root) — all dev tools live here, installed with pnpm add -D -w { "name": "my-monorepo", "private": true, "engines": { "node": ">=22", "pnpm": ">=9" }, "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "lint": "turbo run lint", "type-check": "turbo run type-check" }, "devDependencies": { "turbo": "^2.5.0", "typescript": "^5.5.0", "@types/node": "^22.0.0", "eslint": "^9.0.0", "prettier": "^3.3.0" } } ```


nextjs - Next.js Monorepo + Turborepo: pnpm -w, Workspaces & CI (2026)

🚀 Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4–8 weeks, full platforms in 3–5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

pnpm 9 Catalogs: Shared Dependency Versions Across Workspaces

pnpm 9.5+ introduced catalogs — a way to declare dependency versions once at the root and reference them from every workspace package. This solves the "React 18 in one package, React 19 in another" drift that plagues monorepos.

```yaml

pnpm-workspace.yaml

packages:

  • "apps/*"
  • "packages/*"

catalog: react: ^19.0.0 react-dom: ^19.0.0 typescript: ^5.5.0 next: ^15.0.0 zod: ^3.23.0 ```

In each workspace package:

```json // apps/web/package.json { "dependencies": { "react": "catalog:", "react-dom": "catalog:", "next": "catalog:", "zod": "catalog:" } } ```

`catalog:` is a special protocol that resolves to the version declared in pnpm-workspace.yaml. Update once at the root, every package follows. This makes catalogs visible across workspaces — which is exactly what devs searching "database workspace" catalogs visible across workspaces are looking for.


Turborepo Pipeline Configuration

```json // turbo.json { "$schema": "https://turbo.build/schema.json", "globalDependencies": ["/.env.*local"], "tasks": { "build": { "dependsOn": ["^build"], "outputs": [".next/", "!.next/cache/", "dist/"], "env": ["NODE_ENV", "NEXT_PUBLIC_*", "DATABASE_URL"] }, "dev": { "cache": false, "persistent": true }, "lint": { "outputs": [] }, "type-check": { "dependsOn": ["^build"], "outputs": [] }, "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"] } } } ```

dependsOn: ["^build"] means "wait for every internal dependency's build task to finish before running this task." The ^ is Turborepo's syntax for topological dependencies. Outputs let Turborepo know what to cache.


pnpm turbo dev — Running All Apps in Parallel

A common dev workflow: spin up all apps simultaneously while sharing the dependency graph.

```bash

Run dev on every workspace that defines a "dev" script, in parallel

pnpm turbo dev

Or limit to specific apps via --filter

pnpm turbo dev --filter=web --filter=admin

Persistent mode keeps watchers alive (already set in turbo.json above)

```

Behind the scenes this runs turbo run dev --parallel. Each app boots on its assigned port (set in each apps/*/package.json's dev script with next dev -p 3001, etc.). Turbo streams logs from all of them, prefixed with the package name. pnpm turbo dev is one of the most-searched pnpm + turbo queries because the official Turborepo docs explain turbo dev but not how it interacts with pnpm workspaces.


Shared Packages: UI, Config, Database

packages/ui — Shared React Components

```json // packages/ui/package.json { "name": "@my-app/ui", "version": "0.0.0", "private": true, "main": "./src/index.ts", "types": "./src/index.ts", "scripts": { "lint": "eslint .", "type-check": "tsc --noEmit" }, "dependencies": { "react": "catalog:", "clsx": "^2.1.0" }, "devDependencies": { "@my-app/config": "workspace:*", "@types/react": "catalog:" } } ```

`workspace:*` is pnpm's protocol for "always use the local workspace version." When you publish, pnpm replaces it with the actual version number.

packages/database — Prisma Client

```json // packages/database/package.json { "name": "@my-app/database", "private": true, "main": "./src/index.ts", "scripts": { "generate": "prisma generate", "migrate": "prisma migrate dev" }, "dependencies": { "@prisma/client": "^5.20.0" }, "devDependencies": { "prisma": "^5.20.0" } } ```


Consuming Shared Packages in Apps

```json // apps/web/package.json { "name": "web", "private": true, "dependencies": { "@my-app/ui": "workspace:", "@my-app/database": "workspace:", "@my-app/validators": "workspace:*", "next": "catalog:", "react": "catalog:" } } ```

```tsx // apps/web/app/page.tsx import { Button } from '@my-app/ui'; import { prisma } from '@my-app/database'; import { CreateUserSchema } from '@my-app/validators';

export default async function Home() { const users = await prisma.user.findMany(); return <Button onClick={() => console.log(users)}>Click; } ```


Remote Caching with Vercel

```bash

Link to a Vercel team for remote cache

pnpm turbo login pnpm turbo link ```

After linking, every turbo run build checks the remote cache first. On the first machine to run a build, the output is uploaded. On every subsequent machine — including CI runners — the cache is hit instantly, often saving 5-15 minutes per CI run.

For self-hosted caching without Vercel, use turborepo-remote-cache — an OSS-compatible cache server you can run on S3/GCS/Azure Blob.


CI Pipeline with GitHub Actions

```yaml

.github/workflows/ci.yml

name: CI

on: [push, pull_request]

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - uses: pnpm/action-setup@v4
    with:
      version: 9

  - uses: actions/setup-node@v4
    with:
      node-version: 22
      cache: 'pnpm'

  - run: pnpm install --frozen-lockfile

  - name: Build (with remote cache)
    run: pnpm turbo run build lint type-check
    env:
      TURBO_TOKEN: \${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: \${{ vars.TURBO_TEAM }}

```


Common Pitfalls

  1. Forgetting -w and getting ERR_PNPM_ADDING_TO_ROOT. Always use pnpm add -D -w <pkg> for dev tools at the root.
  2. Installing the same package in multiple workspaces. Use catalogs (pnpm 9.5+) to declare versions once.
  3. Missing dependsOn: ["^build"] in turbo.json. Without it, packages get built before their dependencies — random failures.
  4. turbo dev not stopping cleanly on Ctrl+C. Add "persistent": true to the dev task in turbo.json — Turbo will then handle SIGINT propagation to child processes.
  5. Cache misses on CI even with TURBO_TOKEN. Make sure TURBO_TEAM is set as an environment variable (not a secret) and that the team slug matches your Vercel team.

pnpm Workspaces vs npm Workspaces vs Yarn Workspaces vs Bun

FeaturepnpmnpmYarn (Berry)Bun
workspace:* protocol✅ Yes❌ Use *✅ Yes✅ Yes
--workspace-root / -w flag✅ Yes--workspaces-root (v9+)✅ Via yarn workspaces foreach✅ Yes (since 1.1)
Catalogs (shared versions)✅ Yes (v9.5+)❌ No native✅ Via resolutions field❌ No
Speed (install on cold cache)~30s~90s~45s~10s
Disk usage (50-package repo)~500 MB (hard-linked)~3 GB (duplicated)~600 MB (PnP)~700 MB
Turborepo supportFirst-classFirst-classFirst-classFirst-class
2026 recommendationUse this for production monoreposAcceptable if already investedStrong if you want PnPPromising but young

pnpm is the 2026 default because: hard-linked stores save disk, the workspace:* protocol is rigorous (no version drift), catalogs eliminate the multi-package version-bump pain, and the entire stack (turbo, next, prisma) treats pnpm as a first-class citizen.


FAQ

What does -w mean in pnpm?

-w is the short form of --workspace-root. It tells pnpm to install a package into the monorepo root's package.json instead of the current workspace package. Use it for dev tools (turbo, typescript, eslint) that every workspace should share: pnpm add -D -w turbo. Without -w, pnpm refuses root-level installs to prevent accidents.

When should I use --workspace-root in pnpm?

Use -w / --workspace-root whenever you are installing a tool that belongs to the whole repo, not a specific app. Examples: turbo, typescript, eslint, prettier, husky, lint-staged, @types/node. Do not use -w for runtime dependencies that belong to a specific app (next, react, prisma, etc.) — install those in the app workspace with pnpm --filter <app> add <pkg>.

How do I run pnpm turbo dev?

From the monorepo root: pnpm turbo dev. This runs the dev script in every workspace that defines one, in parallel. To limit scope: pnpm turbo dev --filter=web. Make sure turbo.json marks the dev task as "cache": false, "persistent": true so it does not get cached and keeps watchers alive.

What is the workspace:* protocol in pnpm?

workspace:* is a special version specifier that resolves to the local workspace package version. In apps/web/package.json you write "@my-app/ui": "workspace:*" and pnpm symlinks it to packages/ui/. On publish, pnpm rewrites it to the actual version. It is the rigorous way to depend on internal packages — no version drift between consumers.

Are pnpm catalogs visible across workspaces?

Yes. A catalog declared in pnpm-workspace.yaml is visible to every workspace package. Reference it with "react": "catalog:" in any package's dependencies block. The catalog version is the single source of truth — update it at the root and pnpm respects it everywhere. This is the canonical answer for the search "database workspace" catalogs visible across workspaces.

How do I add a dev dependency to the root with pnpm?

pnpm add -D -w <pkg> or pnpm add -D --workspace-root <pkg>. Without -w, pnpm refuses and shows the ERR_PNPM_ADDING_TO_ROOT warning. The -D makes it a devDependency.

Why is pnpm telling me ERR_PNPM_ADDING_TO_ROOT?

Because you ran pnpm add at the monorepo root without the -w flag. pnpm refuses by default to prevent you from accidentally polluting the root. Solution: rerun with -w if you meant to install at the root: pnpm add -D -w <pkg>. Or cd into a specific package first if you meant to install there.


Partnering With Viprasol

We design and ship Next.js monorepos for production — Turborepo pipelines, shared design systems, remote caching, CI tuned for sub-3-minute builds, and the operational discipline that keeps a 10-app repo from drifting. If your team is moving from a single Next.js app to a monorepo or scaling an existing one, we can help.

Talk to our team about Next.js monorepo architecture.


Continue Learning

Sharing a Design System with pnpm Workspaces, Turborepo, and Next.js

A shared UI library is where a monorepo earns its keep. Wiring up pnpm workspaces, Turborepo, and a design system in Next.js lets every app consume the same components, tokens, and Tailwind preset from one internal package instead of copy-pasted forks. Declare the design system as a workspace package, add it to each app with the workspace protocol, and let Turborepo cache its build so consumers rebuild only when the source actually changes.

Keep the package framework-agnostic where you can: export components, theme tokens, and a Tailwind config, and let apps import directly without a separate compile step. Turborepo's task graph then orders the design-system build ahead of dependent Next.js apps automatically. Our senior engineers set this up end to end, so your teams ship consistent UI with full ownership of the codebase.

nextjsturborepopnpmworkspace-rootmonorepotypescriptpnpm-catalogs
Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms — we build it all with Next.js and React.

Free consultation • No commitment • Response within 24 hours

Viprasol · Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.