Special Offer

Get 3 months free of PEO*

articleIcon-icon

Article

1 min read

Build only what changed: NX + pnpm

AI

Tomasz Bialecki

Author

Tomasz Bialecki

Last Update

September 08, 2026

Generated Image September 08, 2026 3 12PM
Table of Contents

CI is just inputs and outputs

How NX caches work

Why pnpm is the perfect partner

What this buys you

Your monorepo works great until it doesn't. Everyone says split into separate repos when CI starts choking, but that just trades one mess for another. Now you're managing scattered dependencies across five different places, coordinating updates by hand, and nobody knows which version of your shared library is actually running where.

There's a different approach. Instead of splitting the repository, you can make your build system smart enough to only run what changed. That's where pnpm and NX come in. pnpm keeps your dependency versions consistent across all projects in one place, and NX watches your dependency graph and caches task outputs so you don't rebuild what hasn't moved. Together, they eliminate the waste without losing the monorepo's real advantages.

The setup is straightforward. Let's walk through how it works.

CI is just inputs and outputs

Look closely at any step in a CI pipeline and you will see the same shape; something goes in, something comes out.

Building turns TypeScript files into a JavaScript bundle. Linting turns source files plus lint configuration into a verdict. Asset processing turns large images into checksummed, optimized files.

If a step is a pure function of its inputs, then a simple question follows. What if a tool could check whether any input changed, and if nothing did, just hand back the previous output?

That tool exists, and it is NX.

How NX caches work

To know whether a task's already been done, it looks at your source code, configuration, tool versions, and the outputs of tasks that feed into it. Then it computes a hash of all that. It stores the resulting output under that hash in a remote cache server.

The next time the task is about to run, NX computes the hash again and asks the cache: have we seen this before? If yes, the output is restored in seconds and the task is skipped. If no, the task runs and the result is uploaded for everyone else.

Because the cache key is a checksum and not a branch name or a commit, results are shared across branches, across pull requests and across developer machines.

All of this only works under one condition. The project must be split into small units, and those units must form a directed acyclic graph. If package A depends on B and B depends on A, NX cannot tell where one task's inputs end and another's begin. Every change invalidates everything, and you are back where you started.

Why pnpm is the perfect partner

pnpm’s workspace configuration defines the shape of the project: which directories are packages and how they relate to each other. The catalog protocol keeps dependency versions in sync across all of them. And a single setting, disallowWorkspaceCycles, makes the package manager refuse to install anything that would create a cycle.

So the division of labor is clear: pnpm defines the project graph and guarantees it stays acyclic and consistent. NX walks that graph, runs the tasks, and decides whether to execute or restore from cache. The combination works because neither tool tries to do the other's job

What this buys you

The result is a CI pipeline that does only the work that actually needs doing. A change to one leaf package runs that package's tasks and its dependents, and nothing else. Everything upstream comes straight from the cache.

The savings are twofold: CI bills drop because machines stop repeating work, and feedback loops shrink because developers stop waiting for it.

Can you imagine eight million lines of code being tested in minutes? With this setup, it is not a heroic feat. It is the default.

Tomasz Bialecki

Staff engineer at Deel. Passionate about software development, clean architecture, clean code and pragmatism.