Create an empty Next.js project. No features, no pages beyond the template, no business logic at all. 11 direct dependencies are declared.
438 packages arrive. 21,229 files. 468 megabytes.
Now run the checks that guard that project on every push. On an M1 Max with a gigabit line, which is faster than the machine your pipeline actually uses, it takes 12.4 seconds. Nothing in that time produces a feature, and nothing in it is your code.
What an Empty Project Costs Per Run
Then the multiplication begins, and it is not a rhetorical multiplication. A modest matrix of 3 Node versions across 3 operating systems is 9 jobs, none of which share a cache with the others, so 12 seconds becomes nearly 2 minutes of machine time for one push. A monorepo with 10 packages multiplies it again. 20 pushes in a working day, which is a quiet day for 6 people, and the pipeline has spent well over half an hour doing arithmetic that produced nothing anybody asked for.
Nobody experiences it that way, of course. Each individual wait is short enough to check messages through.
The Same Arithmetic Where the Revenue Is
An empty template is the friendly case. The systems that actually take orders keep the ratios and enlarge every number, and the point of what follows is the shape they share, not the badge on any one of them.
3 commerce platforms, 3 published manifests. Spryker’s reference shop declares 266 direct dependencies and receives 1,202 packages, 899 of them its own modules. Magento’s monorepo replaces 241 modules. Shopware declares 165 direct.
Their published pipelines show where the minutes come from. Magento’s quality gate lints PHP syntax, then runs its code-style checker, then runs it again for XML and XSD, again for HTML, again for GraphQL, again for LESS: one tool invoked 6 times because 6 notations share one project. Shopware keeps 55 workflow files, one of which exists to warm the static-analysis cache, an optimisation for the checking rather than for the product. Spryker’s documented pipeline clones a Docker SDK, boots the containers, validates style, schema, XML names and transfer objects, runs PHPStan at level 4 and PHPMD against an architecture sniffer, then hands over to Codeception, with containers seeded and torn down around the whole affair.
Now add what a real deployment brings and the vendor documentation cannot: a staging environment and an integration environment, each with its own database, each running the suite again against its own data. At that point the wall clock has stopped counting in seconds, and the engineers waiting on it can tell you which stage is slowest without looking it up.
None of this is incompetence, and reading it that way would be cheap. These platforms carry real money for real merchants, they solve genuinely hard problems in tax, pricing and catalogue structure, and every check in those pipelines was added by somebody who had been burned by its absence.
Why Every Step Starts From Nothing
The reason those seconds cannot be optimised away by better hardware is visible further down, in the smallest measurement available: the emptiest program that can be written. Nothing is read and nothing is printed. Start, and stop.
64 milliseconds of Node to accomplish nothing, 19 times what awk needs for the same absence of work. On its own that is no scandal. A web server starts once and then runs for weeks, and Node was designed for exactly that shape, where the bootstrap is a rounding error and the event loop is the product.
The trouble is that a build pipeline is the opposite shape. Every step is a fresh process in a fresh container, and each one pays the full price of arriving before it does anything.
Watch it land in the ordinary case. A commit in a small repository with the usual hook setup takes 824 milliseconds; the same commit with hooks disabled takes 100. Of that difference, npx lint-staged alone accounts for 464, of which 262 is the wrapper starting a Node process to find a binary that a relative path locates for nothing. Then prettier runs on one file for 135 milliseconds, 64 of which is Node arriving for the second time. Subtract every bootstrap and roughly 70 milliseconds of actual formatting remains, 8.5 per cent of the wait.
Memory follows suit. An empty Node process holds 46.1 MB before it has been told what to do, prettier peaks at 64.0 and eslint at 76.2, against 1.4 MB for awk making a comparable pass over the same file. Multiply that by the number of containers on a build host and the pattern stops being about time at all.
What Is Actually Being Checked
Here is the part that ought to give a technical director pause, and it has nothing to do with milliseconds.
Look at what those 12 seconds consist of. 7 of them fetch and unpack 438 packages, a number that exists because 11 wishes were expressed and the ecosystem answered with a supply chain. 1 second checks types, a step required because the language has none of its own. Another applies rules that a compiler would enforce as a matter of course. 3 seconds translate the result into something a browser will accept, because what was written is not what runs.
The commerce pipelines decompose the same way, in a different dialect. There is a code style pass, because the language has no canonical form. There is static analysis at level 4, because types are optional and therefore missing wherever nobody insisted. There is an architecture sniffer, because the architecture is a convention that no framework can enforce by itself. Schema and transfer validation follow, because those objects are generated from declarations that nothing verifies at compile time. And 6 style passes for 6 notations, because a single project is written in 6 languages at once.
Every one of those steps is a repair, and every check stands in for a guarantee the tooling declined to give. Nobody made a mistake to warrant any of it; the property being repaired arrived with the choice of tooling, and each repair now needs its own tool, its own configuration, its own place in the pipeline, and its own runtime to start up before it can begin.
This is where the arrangement turns circular. A pipeline of this kind spends most of its life measuring and correcting consequences of the framework it was built to serve. Add a container per service and the whole apparatus is instantiated again, with its own base image, its own dependency tree, its own audit and its own set of the same checks, all of it running on every push, all of it scaffolding for scaffolding.
The industry has a word for what remains when you remove all of that. It calls it the business logic, which is a curious way to describe the only part anybody wanted.
The Limit
The counter-arguments are real and deserve better than a subordinate clause.
Caching removes a good deal of this. A warm dependency cache took npm ci from 6,962 milliseconds down to 738, and a pipeline configured by somebody who cares looks considerably better than the figures above. Node ships --build-snapshot, which restores a prepared heap instead of repeating the bootstrap, saving 50 to 150 milliseconds per start. Editors keep a language server resident rather than launching a formatter per keystroke. To its credit lint-staged bundles every staged path into one invocation rather than spawning per file, which is the difference between 325 milliseconds and 13,417 for the same 100 files.
The checks also buy something. A type error caught before review is worth more than the second it cost, and the framework buys real things too: routing, rendering, a hiring pool, an answer to questions nobody on the team wants to solve twice.
Nor is the compiled side a free lunch, and it would be dishonest to imply otherwise. Rust and Go run their own check, format, lint and test steps, so the number of stages barely moves. What moves is where the guarantee lives. A large Rust workspace is measured in minutes rather than seconds, and anybody reading this as an advertisement for compilation has not sat through a release build.
And the measurement has boundaries: a single machine, warm caches throughout, a project with nothing in it, and a network most pipelines would envy. A large application changes the ratios, mostly by making them worse.
What the Second Actually Buys
None of this argues that the seconds are unaffordable. They plainly are affordable, which is precisely why nobody has counted them.
The question worth putting to a team is a different one, and it is not about performance. Of the time your pipeline spends on every push, what fraction goes to the thing you are selling, and what fraction goes to repairing the consequences of what you chose to build it with? The figure is measurable in an afternoon, and most teams have never seen it.
There is a second cost, and it lands on the checks themselves. The reports on slow hooks converge on one behaviour, which is that developers reach for --no-verify, a flag Git makes trivial and Husky does nothing to discourage. Somebody who waits a second on every commit finds that flag within a fortnight, and from then on the guarantee runs on the machines of the people who never disabled it. Which is to say it is no longer a guarantee.