Stockio

Turborepo Monorepo With pnpm Workspaces

Set up pnpm workspace boundaries and Turborepo task caching with explicit package dependencies, reliable outputs, and a structure that stays understandable as teams grow.

By Sofia Marchetti

JavaScript Engineer ·

On this page

A monorepo helps when several applications and packages need coordinated changes. It does not automatically create good boundaries, faster builds, or shared ownership. pnpm workspaces manage related packages and their dependencies; Turborepo schedules tasks and caches their results. Keeping those responsibilities separate makes the setup easier to reason about.

Start with a real coordination problem

A useful first repository might contain a web application, an internal documentation site, and a shared component package. The benefit is atomic change: a component API and both consumers can be updated in one review. If the projects rarely share code or ownership, separate repositories may remain simpler.

Before adding orchestration, inspect package manifests with the JSON Formatter. Format a sanitized package.json and check that names, scripts, and dependency fields are clear. The tool helps with JSON readability and syntax; it cannot validate the workspace graph or Turborepo's configuration semantics.

Document the motivation in your developer tools guide. State which changes should become easier and who owns shared packages. Monorepo adoption should solve an observable coordination problem, not simply standardize the appearance of unrelated projects.

Define packages before defining task pipelines

Keep the root private and give every workspace package a unique name. Use apps for deployable products and packages for reusable libraries or configuration. These directory labels are conventions, not enforced architecture; dependency declarations create the graph that tools actually understand.

packages:
- 'apps/*'
- 'packages/*'

Save that configuration as pnpm-workspace.yaml at the root. Pin a tested pnpm release through your provisioning process and the packageManager field, then commit the workspace lockfile. Follow the pnpm workspace documentation for the behavior of the version your team uses.

A consuming application should declare its internal dependency explicitly. The workspace protocol makes local-package intent clear and can prevent accidentally resolving a similarly named registry package when local resolution is required.

{
"name": "@studio/web",
"private": true,
"dependencies": {
  "@studio/ui": "workspace:*"
},
"scripts": {
  "build": "vite build",
  "check-types": "tsc --noEmit"
}
}

Give the UI package an exports map and a deliberate consumption model. Either consumers compile its source or the package produces built artifacts. Mixing those models accidentally leads to development succeeding while production packaging fails. Test consumption through the public package entry point, not a relative path into another package's source tree.

Build a task graph that matches dependencies

Install a pinned Turborepo version as a root development dependency. Add root scripts that invoke turbo run build and other shared tasks. Each package still owns its actual build command; Turborepo orchestrates those commands rather than replacing the framework's build system.

A minimal turbo.json can describe upstream ordering and cached artifacts. This example assumes every build emits dist; adjust outputs for the real framework before using it.

{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
  "build": {
    "dependsOn": ["^build"],
    "outputs": ["dist/**"]
  },
  "check-types": {
    "dependsOn": ["^build"],
    "outputs": []
  },
  "dev": {
    "cache": false,
    "persistent": true
  }
}
}

The upstream dependency marker means dependent packages build before the current package. It follows declared package relationships, not arbitrary imports discovered in source. If web imports UI without declaring it, scheduling cannot reliably repair that missing relationship.

Type checking may depend on upstream builds when it consumes emitted declarations. If packages use source directly, another task model may be more appropriate. Model the artifacts each command actually reads. Consult the Turborepo configuration reference rather than copying a pipeline designed for a different framework.

Keep long-running development servers out of ordinary finite build dependencies. A task waiting for a persistent process to finish cannot progress normally. Document the command developers should use to start the relevant apps and shared watchers together.

Treat cache correctness as a dependency problem

A cache hit is valid only when the task's effective inputs match the inputs that produced the stored result. Source files are only part of that picture. Environment variables, generated files, root configuration, and external data can also change the output.

Declare relevant environment inputs explicitly according to your Turborepo version and environment mode. A variable being available to a process is not necessarily the same as its value participating in the cache key. Never use a public-looking variable name as evidence that its value is safe to publish.

Specify output artifacts precisely. Missing outputs can produce a reported cache hit without restoring files that a later step needs. Overly broad outputs can capture temporary data or inflate storage. Verify restoration by removing local build artifacts and rerunning an unchanged build.

Initially keep caching local. Add remote caching once the task graph is trustworthy, and review access control, artifact contents, and retention. Do not cache tasks that publish packages, modify production state, or depend on live data without a suitable reproducibility strategy.

Run an intentional invalidation test: change a shared token, rebuild, and confirm affected consumers rerun. Change unrelated documentation and confirm it does not invalidate every production artifact unnecessarily. These tests reveal whether the graph is both correct and usefully selective.

Enforce ownership and useful package boundaries

A shared package is a public contract inside the organization. Define its maintainers, review expectations, and supported consumers. The design tokens guide provides a useful model for treating shared naming and ownership as product decisions rather than file organization.

Avoid a giant utils package that every application imports for unrelated reasons. Prefer cohesive packages with narrow entry points. Forbid cross-package relative imports through lint rules or import restrictions, and expose only the APIs consumers should use.

Start automation with a full clean install and full build. Introduce filtered execution after you can verify its selection against the dependency graph. A faster check that silently omits an affected package is not an optimization. Preserve a full validation path for periodic verification and risky structural changes.

Keep onboarding instructions in development documentation: tool versions, install command, common task commands, and how to add a package. Test the instructions on a clean machine or disposable environment. Monorepos earn their complexity when contributors can confidently change several packages without learning hidden setup rituals.

Key takeaways

  • Use pnpm for workspace dependencies and Turborepo for task orchestration.
  • Declare internal dependencies explicitly and choose a clear artifact model.
  • Match task ordering and outputs to the files commands actually consume.
  • Test cache invalidation and restoration before enabling shared caches.
  • Maintain package ownership, public entry points, and clean onboarding instructions.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Dev Tools & AI Coding
Dev Tools & AI Coding

8 Best AI Code Review Tools

Compare eight AI-assisted code review tools for pull requests, security checks, and repository workflows, with practical cautions about false alarms, context, and human responsibility.

Includes: JSON formatterSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

15 Best VS Code Extensions for Web Development

Find useful VS Code extensions for formatting, debugging, writing and remote work. This practical list explains what each add-on does, who benefits, and when built-in features are enough.

Includes: JSON formatterSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Chrome DevTools Performance Panel: From Trace to Fix

Record focused Chrome Performance traces, identify expensive main-thread work, and verify targeted fixes with repeatable scenarios instead of guessing from a flame chart.

Includes: JSON formatterRavi DesaiRavi Desai
Dev Tools & AI Coding

direnv and .env Files: Managing Local Environment Variables

Make local project configuration predictable with .env templates, explicit loading rules, direnv approval, and checks that catch missing values before startup.

Includes: JSON formatterRavi DesaiRavi Desai
Dev Tools & AI Coding

AI Tailwind CSS: Get Clean Styles From AI Assistants

AI assistants can produce a convincing interface quickly, but the stylesheet often reveals how little was specified. Arbitrary spacing, duplicated utilities and brittle positioning accumulate when every prompt.

Includes: JSON formatterRavi DesaiRavi Desai
Dev Tools & AI Coding

AI Pair Programming for Junior Developers: Learn First

Use AI as a coding tutor with hint ladders, behaviour predictions, regression tests, and independent teach-back checks that build junior developers’ skills instead of hiding gaps.

Includes: JSON formatterSofia MarchettiSofia Marchetti