I freed 15GB by deleting old node_modules directories (and you can, too)

npm installs node modules to a local node_modules directory for every project you have, and, if you’re like me, over time you’ll accumulate a lot of crusty old node_modules dirs. They can easily take up 10s to 100s of megabytes. By deleting these old directories you can free up several gigabytes of disk space. I did a sweep of my own recently and freed over 15GB.

It’s important to keep in mind that without a good lock file an npm install is not deterministic, so some module directories that you delete will not reinstall the same way. This is especially true if your package.json includes version ranges, because upon reinstall npm may find newer versions of your modules that satisfy the requested range. So, buyer beware. Please make sure you have backed up and that you know what you are doing before blindly copying and pasting the commands here, or you could delete much more than you intended.

Also note: This technique won’t work if you have filepaths with spaces in them, and could in fact cause some you to delete some unintended files. (There’s a section at the end that explains why.) Only the commands listed below with “rm” in them can cause harm, so you can still follow along safely until that point.

When I did this sweep I wasn’t too concerned about reinstalls of node_modules being different because many of the directories I was clearing out had been installed with older versions of node. They likely were out of date already. I had a need to free up some disk space quickly, and decided to let future me deal with reinstallation issues.

On macOS, use the find tool to find all directories named node_modules. The -prune flag instructions find not to descend after it finds a match. Without it you’d find hundreds of nested node_modules directories.

find ~/work -type d -name 'node_modules' -prune

Use du to get an idea for how much disk space these directories take up. Pass the “h” and “s” flags to displays size info in human-readable form (mb, gb, …) totals per directory instead of each file within that directory:

find ~/work -type d -name 'node_modules' -prune | xargs du -sh

Wow. Three directories and nearly a gigabyte (900MB) in potential savings already. (Note: If find found any filepaths with spaces in them, you will likely see errors here from du, and that’s an indication that you should not use the commands below because they may delete more than you intended.)

The GNU sort command can sort by the human-readable sizes that du outputs if you pass it the “h” flag. On macOS you should use brew to install coreutils, which will install gsort. It is the same as the built-in sort except that it recognizes the “h” flag. Use the “r” flag to reverse the output so largest sizes are listed first:

find ~/work -type d -name 'node_modules' -prune | xargs du -sh \
| gsort -rh

This gives a list of the worst offenders and you can selectively delete them at your leisure.

I opted for the shot-gun approach, though, piping each line to rm -r to do the deletion in a single unix command. If you are looking for a single command to run, that deletes all your node_modules directories (within some parent dir), this is it. Again, do not use this if you had any issues before, or if you have filepaths with spaces (or, in general, if you don’t understand what you are doing):

find ~/work -type d -name 'node_modules' -prune | xargs rm -r

Another option is to continue to utilize du and gsort and select only a few of the heaviest directories to pipe to our rm -r. To do so, thread through calls to the head command (to limit to the top X results) and the cut command, which is necessary to remove the size information that du outputs. du adds 6 characters to the beginning of each line, so cut those using cut -c 6- :

find ~/work -type d -name 'node_modules' -prune | xargs du -sh \
| gsort -rh | head -10 | cut -c 6- | xargs rm -r

And there you go. That one-liner will delete the 10 heaviest (by file size) ‘node_modules’ directories anywhere within the ~/work parent directory.

Alternative Approaches

You could use the mtime or size flags with find to limit by modified-time or file-size.

To learn more about find, I recommend this excellent guide. It’s a poorly understood but very powerful tool.

A note on spaces in filepaths

I warn above to be extra careful using these commands if you have spaces in your filepaths. This is because when a string with a space piped to xargs , it will operate as though you gave it two commands instead. For example if you have a filepath “/Users/me/abc def” that is piped to xargs rm -r, it is as though you did rm -r /Users/me/abc def, and rm will attempt to remove /Users/me/abc and def instead of the single argument “/Users/me/abc def”.

The find command accepts the print0 flag, which causes it to print results using a null terminator (instead of a newline), and xargs has a correlated -0 flag that causes it to assume incoming arguments are separated by null characters. These flags go well together to fix the issues described above. However, in the one-liner above you’ll notice that output is piped into xargs twice, once from print and then again later from cut. The output from cut will again use spaces and newlines, so the second xargs, xargs rm -r will still suffer from the issue described.

There is a workaround, for the intrepid, which is to use null-terminators all the way through. This is possible if you did install coreutils, as mentioned above; it provides g-prefixed versions of the commands used, and those will each accept the “-z” flag to output nulls instead of newlines. And you need to use tr to translate the newlines that du prints. Hat tip to @mixonic for his assistance on this monstrosity:

find ~/work -type d -name 'node_modules' -prune -print0 | \
xargs -0 du -sh | \
tr '\n' '\0' | \
gsort -zrh | ghead -z -n 10 | gcut -z -c 6- | xargs -0 rm -r
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.