A build gets slow the way a room gets full: nobody ever decides to fill it. Ours went from four minutes to fourteen over about a year, one reasonable addition at a time, and every one of those additions had a good argument behind it on the day it landed.
In April we stopped adding and started reading. Not profiling — reading. Every step in the pipeline, out loud, with the question what breaks if this is gone. Two thirds of the answers were some version of “nothing, but it feels safer.”
- before
- 14:20
- after
- 3:05
- build tools
- 6 → 2
median CI run, main branch
same commit range, same runner
counting anything with its own config file
What actually cost the time#
The honest answer was not the compiler. It was the layers around it — a wrapper that existed to configure the bundler, a second test runner kept for one legacy suite, a lint pass that ran again inside the type-check because nobody was sure the first one had.
| Step | Before | After |
|---|---|---|
| Install | 3:10 | 0:40 |
| Lint | 2:05 | 0:25 |
| Type-check | 1:50 | 1:05 |
| Test | 4:35 | 0:55 |
| Build | 2:40 | — |
The build row is empty because the build now happens inside the deploy, once, instead of twice in CI and again on the platform. That was the single largest win and it came from deleting a job, not from tuning one.
The install was the easy half#
Moving to pnpm with a locked store took the install from three minutes to forty seconds, and it did it by not copying: a content-addressed store plus hard links means the second install of the same dependency is a link, not a download.
# what the runner does now, in full
corepack enable
pnpm install --frozen-lockfile
pnpm lint && pnpm typecheck && pnpm testThe rule we kept#
Deleting is easy once. Staying deleted is the hard part, because the next tool always arrives attached to a real problem. So we wrote down the test it has to pass, and it is deliberately annoying to satisfy:
- 01Name the failure
Which specific bug reached production, or would have? “Best practice” is not a failure. If nobody can point at the incident, the tool is insurance against an imagined risk.
- 02Name what it replaces
Additive tooling compounds. Anything new either takes over a step or it does not come in — two linters is a decision nobody ever makes on purpose.
- 03Name its budget
Seconds of CI time, and a number we check a month later. A step that outgrows its budget goes back to review instead of quietly becoming the new normal.
Three tools have been proposed since. One passed. That ratio feels about right — the goal was never zero tools, it was tools that can each explain themselves.