Last updated: August 2026
A node_modules folder is reproducible from
package.json plus a lockfile. If both are present and
committed, deleting it costs you one install.
The condition is doing real work: reinstalling needs the network, the registry, and a toolchain that still builds the same native modules. Four cases break that — they are listed below, and every one of them shows up on old projects, which is exactly what you are about to delete.
The trap in every version of this command you will find elsewhere is
nested node_modules. Without -prune,
find descends into dependencies' own dependency folders and
reports each one again inside its parent's total.
find ~ -name node_modules -type d -prune -print0 2>/dev/null \
| xargs -0 du -sh | sort -h
-prune tells find to stop descending once it has
matched, so each top-level folder is counted exactly once.
Size alone is the wrong signal — the biggest folder is usually your current project. What you want is size next to last activity, so abandoned projects sort themselves to the top of the delete list:
find ~ -name node_modules -type d -prune 2>/dev/null | while read -r d; do
p=$(dirname "$d")
printf '%s\t%s\t%s\n' \
"$(du -sh "$d" | cut -f1)" \
"$(git -C "$p" log -1 --format=%as 2>/dev/null || echo no-git)" \
"$p"
done | sort -h
1.2G 2026-07-30 /Users/you/work/current-app1.9G 2024-02-11 /Users/you/side/old-dashboard2.4G no-git /Users/you/Downloads/tutorial-clone
That last column is the decision. A 2.4 GB folder from a tutorial you followed once is free space. A 1.2 GB folder from last week is your job.
Each of these is more likely the older the project, which makes them correlate precisely with the folders you are most tempted to delete.
package-lock.json, yarn.lock, or
pnpm-lock.yaml, a reinstall resolves to whatever satisfies the
ranges in package.json today. You get different code than you
deleted, and a project that built fine may not.node_modules was the last one.node-gyp rebuilds against your current Node version
and system toolchain. Old projects routinely fail here — the module needs a
Python or a compiler you upgraded past years ago.The cheap insurance: before deleting an old project's folder, confirm the lockfile is committed.
git -C ~/side/old-dashboard status --short -- '*lock*'
Silence means it is tracked and clean. Anything printed means the lockfile
is untracked or modified, and deleting node_modules throws away
the only record of what actually worked.
Send them to the Trash rather than rm -rf, so a mistake is
recoverable for as long as you leave the Trash alone:
find ~/side ~/Downloads -name node_modules -type d -prune 2>/dev/null \
| xargs -I{} mv {} ~/.Trash/
Note the explicit directories. Running this rooted at ~ will
also take the project you have open right now.
npm and Yarn Classic copy every dependency into every project, so twelve projects sharing one 400 MB dependency tree cost you 4.8 GB. pnpm keeps one content-addressed copy in a global store and hard-links it into each project. The twelve projects then cost roughly 400 MB in total.
pnpm store pathpnpm store prune
store prune drops packages no project references any more. It
is safe — anything still linked is retained by definition.
In a workspace-based repo you have a root node_modules plus
one inside each package. -prune only stops descending
inside a folder it has matched, so
packages/api/node_modules is still found and reported — it is
not nested within the root node_modules, just alongside it.
That is the behaviour you want. It also means a single monorepo can produce a dozen rows in the listing above, and the total for the repo is the sum of them rather than any one line. If you are deciding whether a monorepo is worth deleting, total it explicitly:
find ~/work/monorepo -name node_modules -type d -prune -print0 2>/dev/null \
| du -ch --files0-from=- 2>/dev/null | tail -1
On a Mac without GNU coreutils, du has no
--files0-from; brew install coreutils and use
gdu, or simply add up the rows from the earlier command.
Deleting node_modules does not touch your package manager's
download cache, which is its own multi-gigabyte item:
| Manager | Cache location | Clear with |
|---|---|---|
| npm | ~/.npm/_cacache | npm cache clean --force |
| pnpm | pnpm store path | pnpm store prune |
| Yarn | yarn cache dir | yarn cache clean |
Clearing these costs only re-download time. Full sizes and commands for every other toolchain are on the cache sizes page.
Every toolchain's cache in one table, with what clearing each one costs.
Why deleting images inside Docker does not shrink the file on your disk.
If you deleted gigabytes and free space did not move, start here.
Shodhana does this check for you: every
node_modules matched back to its project's manifest and lockfile,
sized, dated by last use, and never confused with a folder that merely shares
the name. See the developer view.