Skip to content
Tutorial 2/8: Understand project dependencies

Help me understand how my Nx workspace tracks dependencies between projects.

Use my existing workspace and projects for hands-on examples.

Run in my workspace and help me interpret the results. Show me which projects depend on each other and explain how Nx detects these relationships.

If dependencies are missing or unexpected, help me debug by checking import paths, tsconfig paths, and package.json entries.

Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.

Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/managing-dependencies.md

Help me understand how my Nx workspace tracks dependencies between projects.

Use my existing workspace and projects for hands-on examples.

Run in my workspace and help me interpret the results. Show me which projects depend on each other and explain how Nx detects these relationships.

If dependencies are missing or unexpected, help me debug by checking import paths, tsconfig paths, and package.json entries.

Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.

Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/managing-dependencies.md

As your workspace grows, projects start depending on each other and on external packages. Nx automatically tracks these relationships so it can build projects in the right order, cache intelligently, and tell you what's affected by a change.

Workspace libraries are projects whose source lives in your workspace and are linked together by your package manager (see Crafting Your Workspace for how this works). In your project's package.json, workspace libraries use workspace:* (or * for npm) while external packages use version ranges:

apps/my-app/package.json
{
"dependencies": {
"react": "^19.0.0",
"@my-workspace/shared-ui": "*",
},
}

When one project imports from another, Nx detects the relationship automatically. No configuration required.

apps/my-app/src/app.tsx
import { Button } from '@my-workspace/shared-ui';

Nx analyzes your JS/TS source code and package.json dependencies to understand how projects relate to each other. Nx uses these relationships to run tasks in the correct order.

Workspace libraries come in two flavors, and the difference is in what their package.json exports field points to.

Non-buildable libraries export their source code directly. Consumers compile the source as part of their own build. This is the simpler setup and works well for most workspace libraries.

packages/shared-ui/package.json
{
"name": "@my-workspace/shared-ui",
"exports": {
".": "./src/index.ts",
},
}

Buildable libraries export compiled artifacts. They have their own build step that produces output (e.g., to dist/), and consumers import the built result. This is useful for libraries that need to be published or that benefit from independent compilation. Use a conditional export so that tooling can still resolve to the source:

packages/data-access/package.json
{
"name": "@my-workspace/data-access",
"exports": {
".": {
"development": "./src/index.ts",
"default": "./dist/index.js",
},
},
}

The development condition points to source, while default points to the built output. Configure customConditions in your root tsconfig.json so your IDE and TypeScript language server resolve the development entry, giving you go-to-definition and type checking against the actual source:

tsconfig.json
{
"compilerOptions": {
"customConditions": ["development"],
},
}

At build time, the bundler resolves the default entry and uses the compiled artifacts.

Start with non-buildable libraries. They're simpler and avoid needing to rebuild libraries during development. Switch to buildable when you need to publish a library or want faster incremental builds in large workspaces.

In a monorepo, some packages, especially frameworks like React, Angular, or Vue, must be the same version everywhere. Having two versions of React in the same app causes runtime errors.

A single version policy means defining dependency versions once at the root and having all projects use that version. This prevents version conflicts and simplifies upgrades.

Catalogs make this easier by letting you name a version once and reference it everywhere:

pnpm-workspace.yaml
catalog:
react: ^19.0.0
react-dom: ^19.0.0
package.json
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:",
},
}

For a deeper comparison of dependency strategies, see Dependency Management Strategies.

Nx tracks dependencies. It builds the project graph, determines build order, and knows what's affected by a change. But Nx does not install or resolve dependencies. That's your package manager's job (npm, pnpm, yarn, or bun).

Think of it this way:

  • Package manager: installs packages, resolves versions, manages node_modules
  • Nx: understands the relationships, orchestrates tasks in the right order, caches results