The Web Framework Bus Factor Hidden in 87 Abandoned Dependencies
May 29, 2026 By Yusuke Tanaka

Run npm audit on a mid-sized React project and you might see a list of 87 packages that haven't been touched in 18 months or more. Each one was once someone's side project, a utility library, a plugin, a tiny helper. Each one now has a bus factor of exactly one. The bus factor is the smallest number of team members that, if hit by a bus, would cause the project to fail. For these packages, that number is one. The maintainer is a single person who may have moved on, burned out, or simply lost interest. The rest of the ecosystem depends on their code, often without knowing it.

The 87-Dependency Wake-Up Call

An NPM audit on a typical React application built with Create React App or a similar starter reveals something sobering. Out of hundreds of transitive dependencies, roughly 10–15% show no commits in over a year. In a project with 800 total dependencies, that's around 80–120 packages. The exact number varies, but it's not unusual to see 87 packages flagged as abandoned. For instance, a 2022 analysis of a real-world React project on GitHub (the popular react-boilerplate repository) found that out of 1,247 transitive dependencies, 94 had not been updated in over two years. These aren't niche libraries; they include things like lodash.merge, postcss-calc, and various Babel plugins that have been superseded or simply neglected.

Each of these packages is a single point of failure. If a security vulnerability is discovered in one, there may be no one to patch it. The maintainer might not even respond to issues. Some packages have been forked by the community, but forks take time to propagate and may introduce compatibility breaks. The React ecosystem is especially vulnerable because of its deep dependency tree. A project using React, React Router, Redux, and a few common tools can easily pull in over 1,400 transitive packages. Many of those are maintained by individuals or small teams with no formal backing.

The problem isn't new. The left-pad incident of 2016 was a wake-up call: a single package removed by its author broke thousands of builds. But the scale has grown. Today, an abandoned package might not cause an immediate build failure, but it silently accumulates risk. The longer it sits unmaintained, the more likely it is to contain a vulnerability or become incompatible with newer versions of Node.js or browsers.

Some projects try to mitigate this by pinning exact versions or using lockfiles. But pinning only freezes the risk. It doesn't fix the underlying issue: the dependency is still unmaintained. The only real solution is to replace the package or take over maintenance, both of which require effort that few teams have budgeted for.

Why Maintainers Walk Away

Burnout is the number one reason maintainers abandon their projects. According to the 2023 Tidelift survey of open source maintainers, 62% reported experiencing burnout, and 38% said they had considered quitting their role. The work is unpaid, the expectations are high, and the feedback is often negative. A maintainer might spend weekends fixing bugs and reviewing pull requests, only to receive demands for features or complaints about breaking changes. There's no HR department, no paid time off, no recognition beyond a few stars on GitHub.

Funding rarely reaches transitive dependencies. Even well-funded open source projects like React or Vue.js have budgets that cover core team members, not the hundreds of packages their ecosystems depend on. Open Collective data shows that the median open source project receives $0 per month. A small utility library might have thousands of users but not a single sponsor. The maintainer pays for hosting, CI, and domain names out of pocket.

The left-pad repeat is always a possibility. A maintainer can unpublish their package or push a breaking change. NPM has policies to prevent this, but they're reactive. The damage is done before the policy kicks in. Some maintainers have deliberately sabotaged their own packages to protest corporate free-riding. A notable incident occurred in 2022 when the maintainer of the colors.js and faker.js libraries intentionally broke their packages, causing widespread build failures. The act was a protest against unpaid labor and corporate reliance on open source. Others simply stop responding. The result is the same: a single point of failure that cascades through the dependency tree.

There is no institutional support for volunteers. A maintainer who gets sick, changes jobs, or loses interest has no backup. The community might fork the project, but forking is a poor substitute for a maintained upstream. Forks create fragmentation and confusion. Users don't know which fork to trust. The abandoned package becomes a zombie: still used, still depended on, but no longer alive.

The Hidden Cost of Abandoned Packages

The most obvious cost is security. When a package is abandoned, security patches stop. CVEs pile up. Tools like Snyk and GitHub's Dependabot will flag vulnerabilities, but they can't fix them. The only option is to update to a patched version, but if no patched version exists, the user is stuck. They can either accept the risk, fork the package and patch it themselves, or find an alternative. All three options take time and expertise that many teams lack.

Build times degrade with stale dependencies. Older packages may use deprecated APIs or slower algorithms. They might not support modern JavaScript features or tree-shaking. Over time, the build becomes slower and the bundle larger. This is a gradual decay that's hard to measure until it becomes a problem. Teams often don't notice until a new developer tries to set up the project and runs into cryptic errors from outdated dependencies.

Lockfile rot is another hidden cost. As dependencies are updated, the lockfile grows and becomes harder to audit. A change in one transitive dependency can cause a cascade of breaking changes. The maintainer of a popular utility library might release a new major version that changes the API, and every package that depends on it must update. If that package is abandoned, it never updates, and the entire chain is frozen. New projects can't use the latest version of the library because it would break the abandoned dependency.

New contributors can't grok unmaintained code. Abandoned packages often lack documentation, tests, or modern coding standards. They might use outdated patterns like callbacks instead of promises, or rely on deprecated Node.js features. A developer who wants to contribute a fix has to first understand the codebase, which may be poorly structured. The barrier to entry is high, so fixes never come. The package remains broken, and the ecosystem suffers.

Consider a lodash-style utility library that hasn't been updated in four years. It still works, but it doesn't support ES modules, it uses var instead of const, and it has a known prototype pollution vulnerability. The maintainer has moved on. The package has millions of downloads per week. Every project that depends on it is carrying that risk. Some teams have replaced it with native JavaScript functions, but that requires a codebase-wide refactor that few have time for.

How Frameworks Exacerbate the Problem

Modern web frameworks encourage deep dependency trees. React's ecosystem, for example, can pull in over 1,400 transitive packages. That's not an exaggeration; a fresh Create React App project with a few common libraries can easily reach that number. Each package is a potential failure point. The framework itself is well-maintained, but its dependencies are a patchwork of individual efforts. Similarly, Angular CLI, when initializing a new project, pulls in around 87 packages at startup. Many of these are development tools, test runners, and polyfills. Some are maintained by the Angular team, but many are third-party. The CLI abstracts away the complexity, but it doesn't eliminate the risk. A developer who runs ng new gets a working project, but they also inherit a tree of dependencies they didn't choose and may not understand.

Vite plugins are often single-person efforts. Vite's plugin ecosystem is vibrant, but many plugins are written by individuals for their own projects and then published for others to use. If the author loses interest, the plugin becomes abandoned. Vite itself is maintained by a core team, but the plugins are not. Users who rely on a specific plugin may find themselves stuck when a new version of Vite breaks compatibility.

Tree-shaking can't fix semantic drift. Tree-shaking removes unused code, but it doesn't remove unused dependencies. A package that is imported but never called still appears in the dependency tree. More importantly, tree-shaking can't address the semantic drift that occurs when a dependency's behavior changes subtly over time. An abandoned package might have a bug that is never fixed, or it might rely on a browser API that is deprecated. Tree-shaking doesn't help with that.

Monorepos mask dependency count. A monorepo might contain dozens of packages, each with its own dependency tree. The total number of transitive dependencies can be staggering, but it's hidden because the monorepo tooling manages them centrally. Developers see only the packages they directly import. The rest are invisible until something breaks. The bus factor of the monorepo's dependencies is the same as the individual packages, but it's harder to audit.

Real-World Mitigations That Work

Socket.dev is a tool that flags abandoned dependencies before they are merged. It scans pull requests for new dependencies and checks their maintenance status, security history, and bus factor. If a package hasn't been updated in a year, Socket.dev warns you. It's a proactive approach that catches the problem before it becomes part of the codebase. Teams that use it report fewer incidents of abandoned packages in production.

Dependabot, GitHub's automated dependency updater, can also help by nudging maintainers to take over abandoned packages. If a package has a fork that is actively maintained, Dependabot can suggest switching to the fork. This is not a perfect solution—forks can introduce breaking changes—but it's better than staying on an unmaintained package. Some projects have successfully migrated to community forks after the original maintainer stepped away.

The GitHub Archive Program preserves snapshots of open source code, but that's a long-term solution. In the short term, bundler-level vendoring can reduce the dependency surface. Vendoring copies the source code of a dependency into the project's own repository, making it immune to upstream changes. This is common in Go and Rust ecosystems, where dependencies are vendored by default. In JavaScript, tools like npm pack or yarn pack can create vendored copies, but it's not the default behavior and adds maintenance burden.

Deno's import map mechanism is a promising alternative. By using URLs for imports, Deno avoids the deep transitive dependency tree that NPM creates. Each import is explicit, and there's no central package manager that can be disrupted. The import map can be locked to specific versions, and the dependency tree is flat. This doesn't eliminate the bus factor entirely, but it makes it visible. You know exactly what you're importing and who maintains it.

What the Next Framework Generation Must Change

Bundle-first design minimizes the dependency tree. Frameworks like Astro and SolidJS prioritize shipping less JavaScript to the browser, which reduces the number of runtime dependencies. If the framework handles most of the work at build time, the runtime dependency tree is smaller. Fewer dependencies mean fewer bus factors to worry about. This is a design philosophy that the next generation of frameworks should adopt.

Rust-based tooling like Turbopack and Rome shrinks the dependency tree by replacing multiple JavaScript tools with a single binary. Turbopack, for example, is a bundler written in Rust that can replace Webpack, Babel, and other tools. Because it's a single binary, it has no JavaScript dependencies. The entire toolchain is one package. This dramatically reduces the number of transitive dependencies in the development environment. Rome, a linter and formatter, does the same for code quality tools.

WebAssembly runtimes promise a zero-dependency core. A WASM module can be compiled from any language and run in the browser without any JavaScript dependencies. If a framework's core is written in WASM, the JavaScript layer is thin and has fewer dependencies. This is still experimental, but projects like the wasm-bindgen library show it's possible. The bus factor of a WASM module is the same as any other package, but the dependency tree is shallower.

Funding pools per ecosystem, modeled after the OpenJS Foundation, could provide sustained support for critical dependencies. The OpenJS Foundation hosts projects like Node.js and jQuery, providing legal, financial, and infrastructure support. A similar model for transitive dependencies would require a collective funding mechanism. Companies that rely on open source would contribute to a pool that pays maintainers a salary. This is not a new idea—GitHub Sponsors and Open Collective exist—but the scale is insufficient. A per-ecosystem fund, like the one proposed by the Open Source Security Foundation, could make a difference.

The bus factor must be reported in package.json. Imagine a field like busFactor that lists the number of active maintainers. Tools could use this to warn users when a dependency has a bus factor of one. It's a simple change that would make the problem visible. Some package managers already show maintainer count, but it's not standardized. A standard field would allow automated checks and encourage projects to recruit additional maintainers. It's not a silver bullet, but it's a step toward transparency.

The next framework generation must also reconsider the culture of open source maintenance. Burnout is not inevitable. If maintainers are paid, if they have support, if the bus factor is acknowledged, the ecosystem can be more resilient. But that requires a shift in how we value open source work. For now, the 87 abandoned dependencies in your project are a reminder that the bus factor is real, and it's not going away on its own. As a practical next step, you can run an audit today using npm audit --production to identify abandoned packages in your own project, then prioritize replacing or forking those with the highest risk. Alternatively, consider contributing to a community fork or sponsoring the maintainer. Small actions by individual teams can collectively reduce the bus factor across the ecosystem.

Related Articles