We Audited Our Frontend Toolchain’s Paid Services — Half of Them Were Legacy Decisions

Every long-lived JavaScript project accumulates two kinds of debt. Everyone talks about the code kind. Almost nobody talks about the services kind: the paid tools wired into your build pipeline by people who left the team years ago, quietly renewing while the toolchain around them moved on.

We noticed ours by accident. A build broke because a license key expired — for a service nobody on the current team could explain. That prompted the question: what else in this pipeline are we paying for, and does any of it still earn its place?

Mapping the paid surface of a build pipeline

The audit itself was straightforward once we knew where to look. Paid services hide in four places in a typical frontend project:

  1. CI configuration — every env var ending in _TOKEN, _KEY, or _SECRET is a lead. Ours revealed nine external services; the README mentioned four.
  2. package.json and lockfiles — registries and postinstall scripts pointing at commercial endpoints, including one private registry we’d stopped publishing to in 2023.
  3. The repo’s webhook and integration settings — bots and checks that report on every PR. Familiar logos you stop seeing after a while.
  4. Finance’s list of renewals — matched against the first three lists. Anything finance pays for that no config references is a zombie. We had two.

What the audit actually found

The pattern that emerged: nearly half of the paid services were decisions made for a previous version of the toolchain.

  • A visual-regression testing service adopted when our component tests were flaky. Since migrating to a modern test runner with built-in snapshot support, the service duplicated coverage we already had. Removed; two test suites merged into one.
  • A bundle-analysis SaaS bolted on years ago, before our bundler shipped equivalent reporting natively. The native output, wired into CI as a size-budget check, replaced a paid dashboard nobody opened.
  • A paid documentation host for a docs site that a static generator on free hosting now serves faster. The migration took one afternoon; the redirect map took longer than the build setup.
  • An uptime/monitoring plan sized for a marketing promise (“sub-minute checks worldwide”) that no stakeholder ever asked for. Downgraded two tiers.

Two services survived on merit: the error tracker (irreplaceable in practice) and the CI provider itself — though even there, caching dependencies properly cut paid minutes by about 40%, which is the least glamorous performance win a build engineer can ship.

The “config as inventory” rule

The fix that keeps this from recurring is treating configuration as the single source of truth for spend. Concretely:

  • Every external service must appear in a services.md at the repo root: what it does, what breaks without it, who owns the relationship, what it costs.
  • The CI config links to that file in a comment block at the top.
  • A quarterly pipeline job fails with a friendly reminder if services.md hasn’t been touched in six months — a nagging test, in the best tradition of build tooling.

It sounds bureaucratic. It’s fifteen lines of markdown and one scheduled job, and it means the next engineer who inherits this pipeline gets an inventory instead of an archaeology project.

The wider point

Build toolchains evolve fast; the services attached to them are renewed on autopilot. Whenever a major piece of your stack changes — bundler, test runner, hosting model — the paid services chosen for the old stack deserve a fresh look. Half of ours were solving problems the current toolchain had already absorbed.

The audit took one engineer two days. It cut our tooling spend roughly in half, deleted three CI steps, and made the pipeline both faster and easier to explain. Legacy code at least sits still. Legacy subscriptions bill you monthly.