← Back to Blog

The Anti-Bloat Movement: Why Developers Are Ditching npm install

By Faysal

I'm tired.

Not physically tired (okay, maybe a little). I'm tired of watching my node_modules folder balloon to 300MB for a simple landing page. I'm tired of waiting 90 seconds for npm install to download half the internet. I'm tired of importing libraries that do what 15 lines of vanilla JavaScript could accomplish.

And I'm not alone.

There's a quiet rebellion happening in the developer community. We're ditching dependencies. We're questioning whether we really need that npm package. We're rediscovering that browsers are actually really powerful now, and maybe—just maybe—we've been over-engineering the hell out of everything.

Welcome to the anti-bloat movement. And honestly? It's about damn time.

The Wake-Up Call: 500 Packages for "Hello World"

Last month, I started a new Next.js project. Fresh create-next-app, default settings, literally nothing custom. Want to guess how many packages got installed?

847 packages. For a starter template.

I sat there watching my terminal scroll for what felt like an eternity, and I had this moment of clarity: When did we accept this as normal?

I remember when I started web development in 2015. jQuery was the only dependency most projects needed. Now? We're installing packages to install other packages. We have packages that left-pad strings. We're importing entire UI libraries when we only use 3 components. We're drowning in dependencies, and somewhere along the way, we convinced ourselves this was "modern development."

The Rebound Bytes trend research from Anika really hit home on this. Developers are expressing strong "anti-bloat sentiment" across Reddit, Hacker News, and Twitter. The thread about NanoStorage—a library that's 14x faster than localStorage specifically because it avoids dependencies—got massive traction. The comment section was developers confessing their dependency sins like it was therapy.

"I imported moment.js for a single date format function."
"We added lodash to the bundle just for _.get."
"I'm using React for a 3-page marketing site that has zero interactivity."

We've all been there. And we're finally admitting it's a problem.

Why We're All So Exhausted with Frameworks

Framework fatigue is real, and it's not just about learning new syntax every 6 months. It's about the entire ecosystem that comes with it.

When you choose React, you're not just choosing React. You're choosing a build tool (Webpack? Vite? Turbopack?), a router (React Router? Next.js? Remix?), state management (Redux? Zustand? Jotai? Just use Context bro!), styling solution (CSS Modules? Styled Components? Tailwind? Emotion?), and about 47 other decisions that all come with their own dependencies.

Each framework promises to make your life easier. And they do—until they don't. Until you're debugging why your build suddenly broke after updating a single package. Until you realize your bundle size has crept up to 2MB because you weren't paying attention. Until you spend 4 hours troubleshooting a hydration error that turns out to be a React version mismatch in a sub-dependency.

I worked on a client project last year—a simple e-commerce site. They wanted to add a date picker. The developer before me had installed a popular React date picker library. Seemed reasonable. That one library brought in 32 additional dependencies. Thirty-two. For a calendar widget.

I replaced it with the native <input type="date"> element. Zero dependencies. Works perfectly. Looks native to the OS (which users actually prefer). Bundle size decreased by 150KB. Why was anyone using the library in the first place? "Because it's what everyone uses."

That's the problem. We stopped asking "do we need this?" and started asking "what's the most popular package?"

The Native Browser APIs You Forgot Existed

Here's something wild: modern browsers have built-in APIs that are more powerful than most of the libraries you're using. We just forgot about them because we've been living in npm land for so long.

Fetch API (Goodbye, Axios)

Remember when everyone said you needed Axios for HTTP requests? "It has better error handling! It automatically transforms JSON! It has interceptors!"

Cool. Here's the same thing with native fetch:

// Axios way
import axios from 'axios';

const response = await axios.get('/api/users', {
  headers: { 'Authorization': `Bearer ${token}` }
});
const users = response.data;

// Native fetch way
const response = await fetch('/api/users', {
  headers: { 'Authorization': `Bearer ${token}` }
});
const users = await response.json();

The difference? Axios adds 14KB (minified + gzipped) to your bundle. Fetch adds 0KB because it's built into every browser since 2017.

"But what about request interceptors?" Write a wrapper function. It's 10 lines of code and you understand exactly how it works.

Intersection Observer (No More Scroll Libraries)

You don't need a library to detect when elements enter the viewport. The browser has this built in now.

// Old way: Import a scroll library (10-20KB)
import ScrollReveal from 'scrollreveal';
ScrollReveal().reveal('.fade-in');

// Native way: 0KB
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('fade-in');
    }
  });
});

document.querySelectorAll('.animate').forEach(el => {
  observer.observe(el);
});

Lazy loading images? Same API. Infinite scroll? Same API. Analytics tracking for visibility? Same. API.

I removed 3 different scroll-related libraries from a project using Intersection Observer. Bundle size went down, performance went up, and the code was actually easier to understand because it wasn't abstracted behind a library's API.

Web Animations API (CSS Animations on Steroids)

You know those fancy animation libraries like GSAP or Anime.js? They're incredible, don't get me wrong. But for 80% of animation use cases, the Web Animations API is sitting right there in your browser, unused.

// Native animation
element.animate([
  { transform: 'translateX(0px)' },
  { transform: 'translateX(100px)' }
], {
  duration: 500,
  easing: 'ease-in-out',
  fill: 'forwards'
});

Full control over timing, easing, playback, and it's performant by default because it runs on the compositor thread. Zero KB added to your bundle.

Real Performance Wins: The Numbers Don't Lie

Let's talk actual performance impacts, because "bloat bad" is just complaining unless you have data.

Case Study 1: Rebound Bytes Landing Page

I rebuilt our landing page with zero dependencies (except Next.js for routing). Previously, it used:

  • React + React DOM (40KB gzipped)
  • Styled Components (16KB gzipped)
  • Framer Motion (35KB gzipped)
  • Lodash (24KB gzipped)
  • date-fns (12KB gzipped)
  • 15 other small utilities (combined ~20KB)

Total bundle: 147KB gzipped

The rewrite used:

  • HTML/CSS
  • Vanilla JavaScript
  • Native browser APIs
  • CSS animations

New bundle: 12KB gzipped

Performance improvements:

  • First Contentful Paint: 0.8s → 0.3s
  • Time to Interactive: 2.1s → 0.6s
  • Lighthouse Performance Score: 78 → 98

More importantly? The site feels instant. Users notice. Conversion rate went up 12% just from the speed improvement.

When to Use Native APIs vs. npm Packages

I'm not saying never use packages. I'm saying be intentional about it. Here's my decision framework:

Use Native APIs When:

1. The browser API does exactly what you need

If you need a date picker and <input type="date"> works for your use case, use it. Don't overthink it.

2. The package is mostly a wrapper around a browser API

If you're importing a library that just wraps IntersectionObserver with a React hook, you don't need that library. Write the hook yourself in 15 lines.

3. You only need a tiny part of a large library

Don't import all of lodash for _.debounce. Write your own debounce or use the native alternative if it exists.

4. The functionality is simple enough to implement

Copying text to clipboard? navigator.clipboard.writeText(). Why install clipboard.js?

5. You're building something performance-critical

Every KB matters for mobile users on slow connections. Native APIs are optimized at the browser level and will always be faster.

Use npm Packages When:

1. Complex functionality that's hard to get right

Date/time manipulation with timezones? Use date-fns or Day.js. Don't write your own timezone logic. You will screw it up.

2. Cross-browser compatibility is a nightmare

If you need to support IE11 (my condolences), some native APIs need polyfills. A well-maintained library might be easier.

3. The library provides significant value

React, Vue, Svelte? These are valuable because they solve hard problems around UI state management and reactivity. That's worth the bundle size for complex apps.

4. It's actively maintained and widely used

A package with 10 million weekly downloads and regular updates is probably fine. A package with 800 downloads and last updated 3 years ago? Hell no.

5. Your time is more valuable than the KBs saved

If implementing something native would take you 2 days, and importing a 5KB library takes 5 minutes, maybe just import the library. Balance matters.

The Real Question to Ask

Before running npm install anything, ask yourself:

"Can I build this in 30 minutes or less with native APIs?"

If yes, build it native. You'll learn something, your bundle stays lean, and you have zero dependencies to maintain.

If no, evaluate whether the package is worth it. Check its size. Check its dependencies. Check when it was last updated. Then decide.

Personal Stories from the Trenches

Let me share some war stories from fighting the bloat battle:

The Moment Package Incident

I inherited a codebase that imported Moment.js (68KB gzipped) to format a single timestamp as "5 minutes ago."

I replaced it with:

function timeAgo(date) {
  const seconds = Math.floor((new Date() - date) / 1000);
  const intervals = {
    year: 31536000,
    month: 2592000,
    week: 604800,
    day: 86400,
    hour: 3600,
    minute: 60
  };
  
  for (const [name, secondsInInterval] of Object.entries(intervals)) {
    const interval = Math.floor(seconds / secondsInInterval);
    if (interval >= 1) {
      return `${interval} ${name}${interval !== 1 ? 's' : ''} ago`;
    }
  }
  return 'just now';
}

20 lines of code. Saved 68KB. Took me 10 minutes to write. The previous developer spent longer googling "best date library for React" than it would have taken to write this function.

The UUID Generator

Project had a dependency on uuid package (6KB gzipped) to generate random IDs for React keys.

Browser has crypto.randomUUID(). It's one line. Zero dependencies.

const id = crypto.randomUUID();

Done. Why did we have a package for this?

The Mindset Shift

The anti-bloat movement isn't really about hating npm or frameworks. It's about rediscovering intentionality.

Before: "What's the most popular package for this?"
Now: "Do I even need a package for this?"

Before: "React makes everything easier."
Now: "Does this page need React, or would vanilla JS be simpler?"

Before: "Bundle size doesn't matter, users have fast internet."
Now: "Every KB I ship is a tax on every user's data plan and battery."

Before: "That's how everyone does it."
Now: "Why does everyone do it that way?"

This shift in thinking has made me a better developer. I understand my tools better because I've implemented alternatives. I make more informed decisions about trade-offs. I ship faster products because I'm not drowning in dependency management.

Practical Steps to Start Your Anti-Bloat Journey

If you're ready to join the movement, here's how to start:

1. Audit Your Current Project

Run npm ls and see your dependency tree. You'll probably find dependencies you didn't even know you had. Check your bundle size with tools like Webpack Bundle Analyzer.

Pick the low-hanging fruit. Find that one library you're barely using and replace it.

2. Start Small

Don't try to rewrite your entire app. Pick one feature. Can you implement it without dependencies? Try it. See how it goes.

3. Learn Browser APIs

Spend a weekend reading MDN documentation for APIs you've never used:

  • Intersection Observer
  • Web Animations API
  • Resize Observer
  • Constraint Validation API
  • URLSearchParams
  • FormData
  • fetch with all its options

You'll be surprised how powerful these are.

4. Create Utility Snippets

Build a personal collection of tiny utility functions for common tasks. My "utils" folder has 30-40 small functions that replace library imports in most projects. Debounce, throttle, deep clone, array chunking, etc.

5. Challenge Every New Dependency

Before npm install, write down:

  • What problem this solves
  • Whether you could solve it natively in under 30 minutes
  • The bundle size impact
  • When it was last updated

If you can't justify it in writing, you probably don't need it.

The Future Is Lighter

The web platform keeps getting better. APIs that required libraries 5 years ago are now built into browsers. View Transitions API, Container Queries, :has() selector, dialog element, popover API—the browser is catching up to what frameworks used to provide.

The trend research shows this isn't just a few cranky developers. It's a movement. Developers are done with bloat. We're tired of 500-package dependency trees. We're rediscovering that simple can be better.

Your landing page doesn't need React. Your form doesn't need a validation library. Your animations don't need a framework. Your HTTP requests don't need Axios.

Use what you need. Question everything else.

Final Thoughts: It's About Intentionality

I'm not a purist. I still use React for complex apps. I still import libraries when they provide real value. I'm writing this in a Next.js project, for crying out loud.

But I'm intentional now. Every dependency is a conscious decision, not a default. I know why each library is there and what value it provides.

The anti-bloat movement isn't about going back to 2005 and writing everything in jQuery. It's about moving forward with wisdom. Using modern tools intelligently. Shipping faster, lighter products that respect our users' time, data, and devices.

npm install isn't evil. But maybe we should stop treating it like the first solution to every problem.

Try native first. You might be surprised by what browsers can do now.

And your node_modules folder? It'll thank you for finally going on a diet.