The maintenance tax
An unmaintained kit is slower than no kit, because the first hour goes to discovering what broke — so the kit needs a stated freshness budget and an automated update path before it is worth cloning twice.
Picture the good outcome. You finish this module, build the kit, apply the inclusion test honestly, and it is excellent. Then a quarter passes with no prototypes, because that is how quarters go. Then somebody at Vista asks for an agentic surface this afternoon, you clone the kit, and the first fifty minutes go to a build that will not start.
That is not a hypothetical shape, and it is not a punishment for carelessness. A kit is a snapshot of an ecosystem that keeps moving after the snapshot is taken. The dangerous thing about the failure is its timing: it is invisible for months and then lands entirely inside the one three-hour window where you had promised speed. The kit is not merely less useful in that moment. It is worse than an empty directory, because an empty directory would at least have started you on current versions.
The decay is measurable, and it has a name
Dependency staleness is not a vibe. The academic term is dependency freshness, defined by Cox and colleagues as the difference between the currently used version of a dependency and the version the system would ideally use. The popular operationalisation is libyear — a single number, summed across your dependencies, measuring how many years behind current you are in total. A dependency one year behind contributes one libyear; a project with a one-year-old and a three-year-old dependency is four libyears old.
Two findings from that paper are worth carrying, with their caveats:
- Systems using outdated dependencies were reported as four times as likely to have security issues as up-to-date ones.
- Staleness is the norm, not the exception: 64.1% of dependencies had an update lag of over 365 days, and only 16.7% showed no lag at all.
Be careful with the first of those. It is a correlation across 75 Java/Maven systems from 30 clients, published by researchers at a company that sells code-quality assessment — not a causal finding, and not measured on npm. The second finding is the one that actually applies to you, because it establishes the base rate: falling a year behind is what normally happens to a codebase nobody is actively updating, which is the precise description of a starter kit between prototypes.
There is a further honesty note the paper itself supplies and most libyear write-ups omit. The authors considered a time-based distance measure and rejected it, because it heavily penalises a dependency whose maintainers simply went quiet for a long stretch. Libyear is inspired by this work rather than endorsed by it. Use it as a prompt to look, not as a score to defend.
Semantic versioning will not save you
The reassuring model is that patch and minor releases are safe, so a kit can sit on caret ranges and drift harmlessly until a major arrives. The empirical picture is worse than that. Venturini and colleagues, studying breaking changes that actually manifested in client packages on npm, found that around 12% of dependent packages and 14% of their releases were impacted by a breaking change during updates of non-major releases, and that 44% of all the manifesting breaking changes they observed were introduced in minor and patch releases — the ones that are supposed to be backward compatible.
The practical consequence for a kit is specific: the state you clone into is not the state you committed. A lockfile pins it and an unpinned range does not, and neither choice is free. Pinned, the kit is reproducible and gets progressively further from current. Floating, it is closer to current and can break without any change of yours. The kit needs a stated position on that, because leaving it implicit means finding out which one you picked during a three-hour run.
What one major version actually costs
Abstract decay is easy to nod along with, so here is the concrete version, in the exact stack a kit like yours would sit on. Next.js 16 — 16.3.4 at the time of writing, with the upgrade guide last updated 25 August 2026 — changed, among many other things:
middleware.tsis deprecated and renamed toproxy.ts, with theedgeruntime explicitly unsupported there.- Synchronous access to
cookies,headers,paramsandsearchParamsis fully removed — the Next.js 15 compatibility period is over. next lintis removed entirely;next buildno longer runs linting.serverRuntimeConfigandpublicRuntimeConfigare removed.- Turbopack is the default for both
next devandnext build, and a project with a custom webpack config now fails the build rather than falling back. revalidateTagrequires a second argument; the single-argument form is a TypeScript error.
Every one of those is a line your kit would plausibly contain. And the churn is not confined to the framework: the Vercel AI SDK, the obvious candidate for the streaming layer this kit will need, is on version 7 as of this writing, where the helpers are toUIMessageStream and createUIMessageStreamResponse. Code written against earlier majors calls differently-named functions. Tailwind v4 removed the config file, the init command and the @tailwind directives that every v3 tutorial still shows. All three of those are vendors documenting their own products, which is worth saying — but a vendor has no incentive to exaggerate how much its own upgrade will break, so the direction of any bias runs the other way.
The version numbers on this page are already decaying
Everything in the list above was verified against the vendors’ own documentation in early September 2026, and some of it will be wrong by the time you next clone your kit. That is not a flaw in the lesson; it is the lesson. If a version here disagrees with the live page it links to, the live page is right and this course is stale — which is a much smaller version of exactly the failure the kit will have.
A budget, a cadence, and a drill
Three mechanisms, in increasing order of how much they actually protect you.
A stated freshness budget
Pick a number of libyears you are willing to carry and write it in the manifest. The number matters far less than its existence, because an unstated budget is always “however stale it currently is.” A budget converts a vague unease into a check you either pass or fail.
Automated update pull requests
Both Dependabot version updates and Renovate will open update PRs on a schedule against a repository you are not otherwise touching — both vendor-documented, neither making any empirical claim about outcomes. This is genuinely useful and genuinely insufficient: it keeps the dependency graph moving and tells you nothing about whether a fresh clone still runs, since nothing about an automated bump exercises the path a new prototype takes.
The cold-clone drill — the one that actually works
On a fixed cadence, clone the kit from cold, on a machine state you did not prepare, and time how long it takes to reach a running app with the mock backend responding. That elapsed time is the maintenance health of the kit. Nothing else measures it, because everything else measures the repository rather than the experience of starting from it.
The drill is not optional busywork, and the reason is structural. A repository generated from a template has an unrelated history and no merge path back to its template. So the prototypes you build never exercise the kit, and fixes you make while building them never flow back into it. If the template itself is not deliberately run, it is never run at all — it just sits there accumulating libyears while you feel productive in its descendants.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
What single number best describes the maintenance health of your kit, and why is it not a libyear count?
Check your answer
The elapsed wall-clock time, from a cold clone on an unprepared machine, to a running app with the mock backend responding. That is the number, because it is the same quantity the kit exists to reduce — measured under the conditions it will actually be used in.
A libyear count is a useful prompt and a bad verdict. It measures the repository rather than the experience of starting from it, it treats a quiet maintainer as equivalent to your own neglect — the exact reason the ICSE authors rejected the time-based measure — and a kit can be perfectly current and still fail to start for a reason that has nothing to do with versions, like a Node minimum you did not notice moving.
Hands on
Put a bill against the kit
Done when: The manifest has a Maintenance section stating a freshness budget, a review cadence with the next date on the calendar, an automated-update configuration committed to the kit repo, and one completed cold-clone drill with its elapsed time recorded.
- Add a Maintenance section to the manifest you wrote in the inclusion-test lesson, Decisions you have already made. State your position on pinning explicitly — lockfile-pinned or floating ranges — and one sentence on what you are trading away by picking it.
- Write a freshness budget as an actual number and a check you can run, not as an intention. Then run the check on the kit as it stands today and record the result, so the budget has a starting position rather than only a target.
- Commit a Dependabot or Renovate configuration to the kit repository and set its schedule. Pick one, not both. Note in the manifest what it does not cover, so the next reader does not mistake green PRs for a healthy kit.
- Run the cold-clone drill once now, from the template, with a stopwatch: clone, install, start, confirm the app renders. Record the elapsed time in the manifest as the baseline. If it is already unpleasant on a kit you built this month, that is the finding.
- Put the next drill on your actual calendar with a date, and write that date into the manifest. A cadence that lives only in a document is a cadence that runs zero times.
- Bring the Maintenance section into the chat. I will push hardest on the freshness budget if it is a sentiment rather than a runnable check, and on the drill if its pass condition stops at “it installs” rather than “the app renders and the backend responds.”
What this does not cover
The cold-clone drill’s pass condition mentions a mock backend responding, and you do not have one yet — which means the drill is currently measuring less than it should. Building that mock is the whole of the next module: a scripted agent backend that emits the same event stream shape as a real graph, so the surface can be built and demoed before any graph exists, starting with the lesson on a graph that does not exist yet.
This module has also said nothing about whether the kit works, in the only sense that counts. A manifest and a maintenance budget are claims; the evidence is timed runs against unfamiliar domains, with a written cut list and an honest record of what got dropped. That is what the practice log is for, and the three-hour-rule lesson is where it starts getting filled in.
Read this next — primary source
Measuring Dependency Freshness in Software SystemsJoël Cox, Eric Bouwers, Marko van Eekelen, Joost Visser — ICSE 2015 — free PDF
The lesson takes the headline correlation and the update-lag distribution. What the paper adds is the part that changes how you would build a freshness metric yourself: section IV works through several candidate measures and explains why the authors rejected the time-based one that later became popular as “libyear” — it penalises a dependency whose maintainers simply went quiet for a while, which is a different condition from a dependency you have fallen behind on. Reading that argument is what stops you from treating a single freshness number as a verdict rather than a prompt.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.