There's a running joke in web development: for every problem, there's an npm package. But increasingly, there's also a native web API that does the same thing better.
The Cost of Dependencies
Every dependency is a trade-off. We don't think about it when running npm install, but each package brings:
Bundle Size
That date formatting library? 70KB gzipped. The native Intl.DateTimeFormat? 0KB.
Security Surface Area
Every dependency is code you didn't write and don't fully understand. Each is a potential vulnerability vector, as we've seen with left-pad, event-stream, and countless others.
Maintenance Burden
Dependencies need updating. They have breaking changes. They get abandoned. You're not just adding code—you're adding ongoing work.
Build Complexity
More dependencies mean more to bundle, more to tree-shake, more version conflicts to resolve.
We've normalized reaching for libraries without asking: does the platform already solve this?
What the Platform Gives You Now
The web platform has evolved dramatically. Features that required libraries five years ago are now built-in.
Data Fetching
The Fetch API is mature and powerful:
// This is all you need for most cases
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
Do you really need Axios? For most applications, no. Fetch handles:
- All HTTP methods
- Custom headers
- Request/response streaming
- AbortController for cancellation
- Credentials and CORS
Form Handling
The Constraint Validation API handles most validation natively:
<input
type="email"
required
pattern=".*@company\.com$"
title="Must be a company email"
/>
form.addEventListener('submit', (e) => {
if (!form.checkValidity()) {
e.preventDefault();
form.reportValidity();
}
});
Custom validation, error messages, visual feedback—all without a library.
Date and Time
Intl.DateTimeFormat covers most formatting needs:
// Instead of moment.js or date-fns
const date = new Date();
new Intl.DateTimeFormat('en-US', {
dateStyle: 'long'
}).format(date);
// "January 15, 2025"
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short'
}).format(date);
// "2:30 PM EST"
The Temporal API (coming soon) will handle date manipulation too.
Animations
CSS animations and the Web Animations API cover most use cases:
// No GSAP needed for simple animations
element.animate([
{ opacity: 0, transform: 'translateY(20px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 300,
easing: 'ease-out',
fill: 'forwards'
});
State Management
For many apps, URL state + component state + Context is enough:
// URL as state
const params = new URLSearchParams(window.location.search);
const filter = params.get('filter');
// Native state with URL sync
function updateFilter(newFilter) {
const url = new URL(window.location);
url.searchParams.set('filter', newFilter);
window.history.pushState({}, '', url);
}
Not every app needs Redux.
Intersection Observer
Lazy loading, infinite scroll, animation triggers—all native:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadMoreContent();
}
});
});
observer.observe(document.querySelector('.load-trigger'));
Resize Observer
Responsive components without polling:
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width } = entry.contentRect;
adjustLayout(width);
}
});
observer.observe(element);
When Libraries Make Sense
I'm not anti-library. The web platform doesn't do everything. Reach for libraries when:
Complex State Management
If your app has complex async flows, derived state, or time-travel debugging needs, something like Zustand or Redux makes sense.
Heavy Animation
For timeline-based animations, physics simulations, or complex orchestration, GSAP or Framer Motion are worth it.
Form Complexity
Simple forms? Native validation. Complex multi-step forms with async validation, arrays of fields, and conditional logic? React Hook Form or Formik save time.
Cross-Browser Edge Cases
Sometimes the platform solution works differently across browsers. A library that normalizes behavior can be worth the cost.
Developer Experience
If a library genuinely makes code more readable and maintainable, that's valuable—even if the platform can technically do it.
The Decision Framework
Before adding a dependency, I ask:
- Can the platform do this? Check MDN. The answer is "yes" more often than you'd think.
- How much of this library will I use? If you need one function from a utility library, write that function.
- What's the maintenance trajectory? Is this library actively maintained? What happens if it's abandoned?
- What's the bundle impact? Tools like bundlephobia.com show the cost clearly.
- Is this a core abstraction? Some libraries are worth it because they're fundamental to your application (React, Tailwind). Others are incidental and replaceable.
Practical Steps
Audit Your Dependencies
Run npm ls and ask for each package: do we still need this?
I recently removed 15 dependencies from a project. Build size dropped 40%. Nothing broke.
Learn Platform APIs
Spend time on MDN. The web platform has features you don't know about. Structured Clone, Navigation API, View Transitions—these exist and work today.
Set Standards
Define when libraries are appropriate for your team. Code review should include "can we do this natively?" as a check.
The Bigger Picture
The web platform keeps getting better. Features land monthly. Browser support converges. Native APIs become more powerful.
Our instincts should evolve with it.
The developers who know the platform deeply—who can reach for native solutions before npm solutions—write faster, smaller, more maintainable code.
That's a competitive advantage worth developing.

