# Heavy dependencies

> Heavy libraries add weight and known pitfalls. Lazy-load them, import a lighter subset, or swap for a native API to cut bundle size and jank.

_Category: Performance · Detector `heavy-library` · Severity: warning_

Some libraries are heavy enough that pulling one in for a small feature dominates your bundle and brings its own performance footguns — 3D engines that leak GPU memory, animation libraries that read layout every frame, date libraries many times larger than the native alternative. AI agents reach for whatever library they’ve seen most, so a marketing page ends up shipping a full charting or animation runtime for one small effect.

## Symptoms

- One dependency accounts for a large share of the bundle
- A heavy runtime (Three.js, Framer Motion, Moment) loads on a page that barely uses it
- Known pitfalls: undisposed resources, per-frame layout reads, un-tree-shaken imports
- The bundle analyzer shows a single package dwarfing the rest

## How VibeCheck detects it

The `heavy-library` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `Framer Motion detected (58KB)`
- **Threshold:** Matches one of 16 known heavy-library signatures (globals, DOM patterns, or script URLs)

## Root causes

- A large library imported for one small feature
- Importing the whole library instead of a tree-shakeable subset
- The library loaded eagerly on a page that only uses it conditionally
- A dated heavyweight (e.g. Moment.js) where a modern/native option exists

## The fix

For each heavy library, decide: replace it with a native API or a lighter alternative, import only the slice you need so tree-shaking works, or lazy-load it so it’s fetched only when the feature is actually used. Confirm resources are disposed on unmount.

### Steps

1. Identify the heavy library and how much of it you actually use
2. Replace with a native/lighter option, or import a tree-shakeable subset
3. If it must stay, lazy-load it and dispose its resources on teardown

_Often the library isn’t needed at all_

```js
// ✗ Moment.js (~70KB) for one formatted date
import moment from 'moment'
moment(d).format('LL')
// ✓ native Intl — zero bytes shipped
new Intl.DateTimeFormat('en', { dateStyle: 'long' }).format(d)
```

## Framework-specific fixes

### React

Load heavy widgets on demand with `React.lazy`, and use a library’s lightweight entry when it has one — e.g. Framer Motion’s `LazyMotion` ships ~5KB instead of the full bundle.

```tsx
import { LazyMotion, domAnimation, m } from 'framer-motion'

<LazyMotion features={domAnimation}>
  <m.div animate={{ opacity: 1 }} /> {/* ~5KB, not ~58KB */}
</LazyMotion>
```

[React docs](https://motion.dev/docs/react-reduce-bundle-size)

### Next.js

Dynamic-import client-only heavy libraries with `ssr:false` so they never touch the server bundle or block first paint, and load them behind interaction where possible.

```tsx
import dynamic from 'next/dynamic'
const Globe = dynamic(() => import('./Globe'), { ssr: false }) // Three.js stays out of initial JS
```

### Vanilla JS

Dynamically import the library the first time the feature runs, and always dispose its resources (geometries, listeners, timelines) when you’re done to avoid leaks.

```js
let mod
async function animate() {
  mod ??= await import('gsap')
  const tween = mod.gsap.to(el, { x: 100 })
  return () => tween.kill() // dispose to avoid a leak
}
```

## FAQ

### Which libraries does VibeCheck flag?

It fingerprints 16 known-heavy libraries — 3D engines, animation runtimes, charting, CSS-in-JS, date and utility libraries — by their globals, DOM markers, or script URLs, and reports the weight plus that library’s common pitfalls.

### Do I have to remove the library?

Not necessarily. Often lazy-loading it (so only users who need the feature download it) or importing a lighter subset is enough. Removal only makes sense when a native API or a much smaller package fully covers your use.

### What’s the single most common heavy dependency?

Date libraries and animation runtimes. Moment.js is a classic — the native `Intl.DateTimeFormat` replaces most of it for zero bytes — and animation libraries are frequently shipped in full when a tree-shaken or lazy subset would do.

## Related problems

- [Large JavaScript bundles](https://vibecheck.wcgw.fun/fix/large-javascript-bundles.md) — Performance
- [Long tasks blocking the main thread](https://vibecheck.wcgw.fun/fix/long-tasks.md) — Performance
- [JavaScript memory leak](https://vibecheck.wcgw.fun/fix/memory-leak.md) — Performance
- [Cumulative layout shift (CLS)](https://vibecheck.wcgw.fun/fix/cumulative-layout-shift.md) — Performance

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/heavy-dependencies. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
