Quick Answer
AI tools like Bolt, Lovable, and Cursor install dozens of npm packages to solve simple problems - a basic CRUD app can arrive with 80+ dependencies. Each package adds attack surface (more code that can contain vulnerabilities), bloats your JavaScript bundle, and slows your users down. The fix is to audit your dependencies regularly, remove unused packages, and use lightweight alternatives.
How Many Packages Is Too Many?
When you ask Bolt to "build a task management app," it doesn't just install React. It installs React, a UI component library, a form library, a validation library, a date formatting library, an icon library, a state management library, and several others - often before you've even asked for those features. The resulting package.json can list 60-90 direct dependencies for an app that's essentially a to-do list.
According to Snyk's 2024 State of Open Source Security report, the average npm project has 49 direct dependencies and 79 transitive dependencies. For AI-generated apps, that number is routinely higher because AI tools default to well-known packages rather than asking whether they're needed.
GitHub's 2024 Octoverse report found that developers spend an average of 25% of their time managing dependencies - updating, patching, resolving conflicts. Every dependency you don't need is time and risk you don't need.
This isn't just a performance concern. In the npm ecosystem, each package is a potential attack vector. The 2021 ua-parser-js supply chain attack, the node-ipc sabotage, and the event-stream compromise all targeted widely-used npm packages. A smaller dependency tree is a smaller attack surface.
What AI-Generated package.json Files Look Like
Here's a realistic example of what a Bolt or Lovable app's package.json looks like after scaffolding a basic SaaS dashboard:
// ❌ BAD - 80+ dependencies for a simple dashboard
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"next": "^14.0.0",
"@mui/material": "^5.15.0",
"@mui/icons-material": "^5.15.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"react-hook-form": "^7.49.0",
"zod": "^3.22.0",
"@hookform/resolvers": "^3.3.0",
"date-fns": "^3.0.0",
"moment": "^2.30.0",
"lodash": "^4.17.21",
"axios": "^1.6.0",
"react-query": "^3.39.0",
"@tanstack/react-query": "^5.0.0",
"zustand": "^4.4.0",
"jotai": "^2.6.0",
"framer-motion": "^10.16.0",
"react-spring": "^9.7.0",
"recharts": "^2.10.0",
"chart.js": "^4.4.0",
"react-chartjs-2": "^5.2.0"
}
}
Notice the redundancy: both moment and date-fns for date handling; both zustand and jotai for state; both react-query v3 and @tanstack/react-query v5 (the same library, two versions); multiple charting libraries. AI tools don't audit what they've already installed.
The Bundle Size Consequence
Every production dependency ships code to your users. Heavy packages have a real cost:
| Package | Minified + Gzipped | Lightweight Alternative | Alternative Size |
|---|---|---|---|
| moment.js | 72 KB | date-fns (tree-shakeable) | ~2 KB per function |
| lodash | 25 KB (full build) | lodash-es (tree-shakeable) | ~1 KB per function |
| axios | 13 KB | native fetch | 0 KB |
| @mui/material | ~300 KB (full) | shadcn/ui (copy-paste) | Only what you use |
| framer-motion + react-spring | ~90 KB combined | CSS transitions | 0 KB |
According to Google's web performance research, every 100 KB of uncompressed JavaScript adds roughly 350ms of parse and execute time on a mid-range Android device. An AI-generated app carrying 500 KB of unnecessary JavaScript starts with a 1.75-second handicap before a single line of your own code runs.
How to Audit Your Dependencies
A proper dependency audit covers three things: what's installed, what's actually used, and what's vulnerable.
# Step 1: See your full dependency tree
npm list --depth=0
# Step 2: Find unused dependencies (depcheck)
npx depcheck
# Step 3: Analyze bundle size impact
npx bundle-phobia-cli lodash moment axios
# Step 4: Check for known vulnerabilities
npm audit
# Step 5: Find duplicate packages
npx find-duplicate-packages
The depcheck tool scans your code and flags packages listed in package.json that aren't imported anywhere. This is the fastest way to find dead weight. It's common to find 10-20 unused packages in an AI-generated project on the first run.
The Right Way to Manage Dependencies
// ✅ GOOD - Lean, intentional dependencies
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"next": "^14.0.0",
"react-hook-form": "^7.49.0",
"zod": "^3.22.0",
"@hookform/resolvers": "^3.3.0",
"@tanstack/react-query": "^5.0.0",
"date-fns": "^3.0.0"
},
"devDependencies": {
"typescript": "^5.3.0",
"@types/react": "^18.2.0",
"eslint": "^8.55.0",
"prettier": "^3.1.0"
}
}
This setup covers form handling, validation, server state, and dates - everything a typical SaaS dashboard needs. No animation libraries until you specifically need them. No icon libraries until you pick one. No state management library until React's built-in state proves insufficient.
How to Fix Dependency Bloat in Your AI-Generated App
- Run depcheck first: Uninstall everything that isn't imported. Start there - it's the easiest win.
- Deduplicate: If you have both
momentanddate-fns, pick one and migrate. If you have two state management libraries, choose one. - Check native alternatives: The browser's native
fetch,Intl.DateTimeFormat, and CSS animations cover a lot of ground without any npm package. - Use bundle analysis: Add
@next/bundle-analyzerto your Next.js config or use Vite's built-inrollup-plugin-visualizerto see exactly what's in your bundle. - Automate scanning: Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for excessive and unused dependencies (DEP-001, DEP-004, DEP-006, DEP-007), flagging specific packages and their impact. Free to sign up.
FAQ
How many npm packages is normal for a Next.js app?
A well-maintained Next.js SaaS app typically has 15-30 direct dependencies. More than 50 is a red flag. Bolt and Lovable scaffolded apps often start at 60-80 before you've written any custom logic.
Does tree shaking fix bundle bloat from large packages?
Partially. Tree shaking removes unused exports at build time, but only if the package uses ES module syntax. CommonJS packages (like lodash) don't tree-shake by default. Even with tree shaking, having a dependency means it's in your supply chain and vulnerability surface.
What's the difference between DEP-001, DEP-004, DEP-006, and DEP-007?
DEP-001 flags an excessive total package count. DEP-004 flags packages listed as production dependencies that should be devDependencies. DEP-006 flags packages that are installed but never imported. DEP-007 flags duplicate packages that solve the same problem (e.g., two date libraries, two state managers).
Will removing unused packages break my app?
If depcheck says a package is unused and you don't see any direct imports, removing it is safe. Always test after removing. Some packages are registered as plugins at runtime (e.g., via next.config.js) which static analysis can miss - depcheck will warn you about these.
Why do AI tools install so many packages?
AI tools are trained on code from the internet, which tends to use popular, well-documented packages. They optimize for working code, not minimal code. They also lack context about what you've already installed, so they add packages defensively rather than checking if something equivalent exists.