← All articles

You Have 40 GB of node_modules You Forgot About

Not the project you are working on — the sixty others. How to find every node_modules folder on your Mac, rank them by how long since you touched them, and delete the dead ones safely.

node_modulesnpmJavaScriptStorageDeveloper

The node_modules in the project you have open right now is fine. Leave it alone.

The problem is the other sixty. The tutorial you followed in 2024. The take-home assignment. The three abandoned side projects. The repo you cloned to read one file. Each one carries 200 MB to 1.5 GB of dependencies, and every one of them is perfectly reproducible from a lockfile.

Find every one of them

find ~ -type d -name node_modules -prune -print0 2>/dev/null \
  | xargs -0 -n1 du -sh 2>/dev/null | sort -hr | head -40

Two details in there matter:

  • -prune stops find descending into a node_modules once it has matched it. Without it you get every nested one too, sizes double-count, and the totals are meaningless.
  • -print0 / -xargs -0 survives paths with spaces, which you will have.

Give it a minute on a large home directory. The output is one line per project, biggest first, and it is usually a longer list than people expect.

Rank by last touched, not by size

Size tells you what is expensive. Age tells you what is dead. The second question is the one you actually want answered:

find ~ -type d -name node_modules -prune 2>/dev/null \
  | while read -r d; do
      printf '%s\t%s\n' "$(stat -f '%Sm' -t '%Y-%m-%d' "$d")" "$d"
    done | sort | head -40

That prints oldest first, YYYY-MM-DD then path. Anything last modified more than a year ago is a free deletion — you are not going back to that project this week, and if you do, npm install restores it exactly.

Delete them

Once you have read the list and recognised the projects:

rm -rf ~/projects/old-thing/node_modules

To clear everything untouched in the last year in one pass, look first:

find ~ -type d -name node_modules -prune -mtime +365 -print 2>/dev/null

Read that output. Actually read it. Then, and only then:

find ~ -type d -name node_modules -prune -mtime +365 -print0 2>/dev/null \
  | xargs -0 rm -rf

If you would rather do it interactively, npkill is a well-known npx tool that lists every node_modules in a terminal UI and deletes on keypress:

npx npkill

What you can and cannot restore

You can always restore node_modules as long as the project has a lockfile — package-lock.json, pnpm-lock.yaml or yarn.lock — and the packages are still published. npm ci reproduces the exact tree in seconds.

The three cases where you should stop and think:

  1. No lockfile committed. Restoring gives you today’s versions matching your semver ranges, not the versions that were there. For a project that still needs to build reproducibly, commit the lockfile before you delete.
  2. You patched something inside node_modules by hand. It is gone. If you use patch-package the patches live in patches/ and survive; ad-hoc edits do not.
  3. A dependency has been unpublished or the registry is private and now unreachable. Rare, but npm ci will simply fail. If the project matters and it depends on something fragile, keep it.

The global caches are a separate 5–15 GB

Deleting node_modules folders does not touch your package manager’s global store, which is its own pile:

# npm
du -sh ~/.npm/_cacache
npm cache clean --force

# pnpm — ask it where the store is, the default moves
du -sh "$(pnpm store path)"
pnpm store prune

# Yarn
yarn cache clean

pnpm store prune is the polite one: it removes only packages no longer referenced by any project on your machine. npm cache clean --force empties everything; npm re-downloads on demand, so the only cost is bandwidth.

Why pnpm makes this problem much smaller

If you routinely have a dozen active JavaScript projects, this is the structural fix. pnpm keeps one content-addressed store and hard-links into each project instead of copying. Ten projects sharing React store one copy of React, not ten. Real-world disk use for node_modules typically drops by well over half, and installs get faster as a side effect.

It will not retroactively clean up the npm-installed folders you already have — delete those with the commands above first, then reinstall with pnpm.

This is a recurring tax, not a one-off

You will clone something next week. It will pull 400 MB. In six months you will have forgotten it exists, and the only trace will be a disk-full warning with no explanation attached.

DevCruft finds every node_modules on every volume, shows each one sized to scale against the rest of your disk with the date you last opened the project, and clears the dead ones in a batch. The commands above do the same job for free — this is a real problem either way, and it is worth twenty minutes of your attention today.

Next: the full developer storage guide, or what Docker is hiding from you.

Or let the map do it.

DevCruft finds every cache in this article, shows you what each one costs you, and clears them in one click.

Apple silicon & Intel · No account · Nothing leaves your Mac