# Monorepos (Turborepo / Nx) > One git repo, many packages, shared tooling and task caching. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/monorepos Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary A monorepo stores every package — apps, libs, design tokens — in a single git repository. Turborepo and Nx add task orchestration with local and remote output caching, so a build that already ran never runs again. The payoff: atomic cross-package changes, shared tooling, and CI times that don't scale with repo size. ## Definition A monorepo is not a monolith. Each package keeps its own `package.json`, build output, and public API. Shared across all packages: one lockfile, one linting config, one task runner. **Turborepo** models tasks as a DAG. A task runs only after its `dependsOn` tasks succeed; `^build` means "build every dependency package first." Outputs are hashed against inputs — a cache hit replays them without re-executing. **Nx** centers on a *project graph* — a computed map of every package and its import edges. `nx affected -t build` walks the graph from changed files and builds only transitive consumers. ## How the tooling works - `pnpm-workspace.yaml` declares package directories; `workspace:*` pins a dep to the local copy. - Turborepo hashes all task inputs; a matching hash restores cached outputs without re-running. - `turbo login && turbo link` opts the repo into Vercel's remote cache, sharing hits across machines. - Nx builds its project graph from import analysis; `nx graph` renders it visually. - Internal packages (`packages/ui`, `packages/types`) are imported by name — no publish step needed. - Nx module boundary rules and explicit `exports` fields block invisible cross-package coupling. ## turbo.json — build pipeline with remote-cache-friendly outputs `dependsOn: ["^build"]` builds every upstream package first. Listing `outputs` is required — omit it and Turbo caches nothing for that task. **turbo.json tasks** ```json { "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"], "cache": true }, "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"], "cache": true }, "lint": { "cache": true }, "dev": { "cache": false, "persistent": true } } } ``` Excluding `.next/cache/**` from outputs keeps the cached artifact small without busting the cache. ## Diagram ```mermaid flowchart TD A["apps/web (build)"] -->|dependsOn ^build| B["packages/ui (build)"] A -->|dependsOn ^build| C["packages/utils (build)"] D["apps/docs (build)"] -->|dependsOn ^build| B B -->|dependsOn ^build| C style A fill:#4f46e5,color:#fff style D fill:#4f46e5,color:#fff style B fill:#0891b2,color:#fff style C fill:#059669,color:#fff ``` Turborepo runs tasks bottom-up: `packages/utils` builds first, then `packages/ui`, then both apps — in parallel where the graph allows. ## Monorepo tradeoffs **Pros** - Atomic cross-package refactors land in one commit with one CI run. - Shared tooling (ESLint, TypeScript, Prettier config) is updated once and propagates everywhere. - Task caching means CI time grows logarithmically, not linearly, as packages accumulate. - A single lockfile eliminates version drift between apps sharing the same dependency. - Internal packages ship zero overhead — no npm publish, no version bump, just import. **Cons** - Startup cost is real: pnpm workspace setup, turbo.json, path aliases, and CI configuration all need upfront work. - Remote cache misconfigurations silently fall back to full rebuilds — hard to diagnose. - A shared lockfile means one package's dependency upgrade affects every app in the repo. - Nx project graph inference can misread dynamic imports or re-exports, producing a wrong affected set. - Permissions and ownership are harder: one repo means one set of git access controls. ## Key terms - **turbo.json tasks**: Turborepo's DAG of named tasks with dependsOn, outputs, and cache settings. - **^dependsOn**: Caret prefix: run this task in all upstream dependency packages first. - **remote cache**: Shared artifact store so a Turbo/Nx cache hit works across machines and CI. - **nx affected**: Runs a task only on packages changed since a base Git ref, plus their dependents. - **workspace: protocol**: pnpm syntax pinning a dependency to the local workspace package, not the registry. ## Related topics - [Microfrontends & Module Federation](https://fearchitect.com/topics/microfrontends-module-federation.md): Independently deployable frontends composed at runtime in the browser. - [Component Architecture & Project Structure](https://fearchitect.com/topics/component-architecture.md): Structuring components so composition beats configuration. - [CI/CD for Frontend](https://fearchitect.com/topics/ci-cd-frontend.md): Automated pipeline from commit to production with quality gates. - [Bundle Architecture & Code Splitting](https://fearchitect.com/topics/bundle-architecture-code-splitting.md): Ship only the JS a route needs, cache the rest long-term. - [Incremental Migration / Strangler Fig](https://fearchitect.com/topics/incremental-migration-strangler-fig.md): Replace a legacy frontend route-by-route behind a shared proxy. - [Design Systems](https://fearchitect.com/topics/design-system.md): A shared product — tokens, components, docs, and governance — at scale. ## Further reading - [Turborepo — configuration reference (turbo.json)](https://turbo.build/repo/docs/reference/configuration) - [Turborepo — remote caching](https://turbo.build/repo/docs/core-concepts/remote-caching) - [Nx — affected projects](https://nx.dev/concepts/affected) - [Nx — project graph](https://nx.dev/concepts/mental-model) - [pnpm — workspaces](https://pnpm.io/workspaces)