Why node_modules gets so big
When you run npm install, yarn or pnpm install, the package
manager downloads every dependency of your project, and every
dependency of those dependencies, into a node_modules folder next to
package.json. A small app easily ends up with hundreds of packages.
A project with a bundler, a test runner, TypeScript and a framework
often passes 500 MB, and monorepos can reach several gigabytes.
The folder is tied to the project, not shared. Ten projects mean ten copies of React, ten copies of TypeScript, ten copies of esbuild’s binaries. When you finish a project or clone something to try it out, the folder stays behind.
The good news: node_modules is fully reproducible. Your lockfile
(package-lock.json, yarn.lock or pnpm-lock.yaml) records exactly
what was installed, so deleting the folder loses nothing you cannot
get back.
Find every node_modules folder
Start in the folder where you keep your code. This finds each
node_modules folder, without descending into nested ones, and lists
them by size:
~ % find ~/Projects -type d -name node_modules -prune -exec du -sh {} + 2>/dev/null | sort -h
96M /Users/you/Projects/landing-page/node_modules
412M /Users/you/Projects/api/node_modules
688M /Users/you/Projects/old-dashboard/node_modules
1.9G /Users/you/Projects/monorepo/node_modulesReplace ~/Projects with your own folder, or use ~ to search your
whole home folder (slower, and it also finds tools you installed
globally). The -prune option matters: without it, find also lists
every nested node_modules inside the outer one, and the totals count
the same files many times.
For a single total, add the folders up in one run:
~ % find ~/Projects -type d -name node_modules -prune -print0 | xargs -0 du -shc | tail -1
3.1G totalPick the ones you no longer use
You probably want to keep node_modules for projects you work on this
week, and clear the rest. A simple signal is how long ago the folder
last changed. This lists folders that have not been modified in 90
days:
~ % find ~/Projects -type d -name node_modules -prune -mtime +90
/Users/you/Projects/old-dashboard/node_modules
/Users/you/Projects/conference-demo/node_modulesThe modification date changes when you install or update packages, so it is a rough measure of activity. Have a look at the list before you act on it.
Delete them
For a single project, move the folder to the Trash in Finder, or run one of these in Terminal:
# move to the Trash (macOS 15 and later) ~ % trash ~/Projects/old-dashboard/node_modules # or remove it right away ~ % rm -rf ~/Projects/old-dashboard/node_modules
To clear every folder from the 90 day list in one go, hand the same
find to trash:
~ % find ~/Projects -type d -name node_modules -prune -mtime +90 -exec trash {} +Items in the Trash still take up space until you empty it. That is a useful safety net: if you removed the wrong folder, use Put Back in Finder.
Interactive tools
If you prefer a list you can browse, npkill
scans a folder, shows every node_modules with its size and last
change, and deletes the ones you select. Run it without installing it:
~/Projects % npx npkill ~/Projects % yarn dlx npkill ~/Projects % pnpm dlx npkill
Note that npkill deletes permanently, without the Trash.
Getting a project back
When you return to a project, install again with the package manager it uses. With a lockfile in place, you get the same versions you had:
~/Projects/old-dashboard % npm ci ~/Projects/old-dashboard % yarn install --immutable ~/Projects/old-dashboard % pnpm install --frozen-lockfile
Use the one that matches the lockfile in the project. (yarn install --immutable is Yarn 2 and later; on Yarn 1 the flag is
--frozen-lockfile.) This needs a network connection, unless the
packages are still in your package manager’s cache, which is another
reason not to clear every cache at the same time.
Use less space in the first place
A few setups keep the problem smaller:
- pnpm stores each package version once, in a global store, and
links it into each project’s
node_modules. Ten projects using the same React version share one copy on disk. - Yarn Plug’n’Play (the default linker in Yarn 2 and later, unless
a project sets
nodeLinker: node-modules) skipsnode_modulesentirely and loads packages from compressed archives in a cache. - Build caches such as
.next,.turbo,.parcel-cacheand.svelte-kitsit next tonode_modulesand can grow just as large. They rebuild on the next build, so the same cleanup applies.
What about global packages and caches?
Two other places grow alongside node_modules:
- Globally installed tools (
npm install -g) live in your Node.js installation, for example under~/.nvmif you use nvm. Old Node.js versions in~/.nvm/versions/nodeeach carry their own global packages;nvm uninstall <version>removes one. - The download caches of npm, Yarn and pnpm. Those are covered in Cleaning up Homebrew, pip and other package caches.
