node_modules is the folder where JavaScript package managers install everything a project depends on. The official npm documentation describes a local install as putting packages “in ./node_modules of the current package root.” Crucially, it holds not just your direct dependencies but their dependencies and theirs, the full transitive tree, which is why it grows so large. The folder became an internet running joke, often called one of the heaviest objects in the universe.
Early npm nested dependencies deeply: each package got its own node_modules inside it, duplicating shared packages many times over and producing absurdly deep paths. The documentation notes that “since version 3, npm hoists dependencies by default,” installing them at the highest level possible. This flattening (and the related deduplication, where a package already present in an ancestor node_modules is not installed again) was a direct response to the size and depth problem.
Hoisting has a downside: a flat node_modules lets code reach packages it never declared, because everything sits at the top level. pnpm tackles both the size and the looseness. Its documentation explains that it stores each package once in a content-addressable store and hard-links files into node_modules, so identical packages across many projects cost almost no extra disk, and it uses symlinks to expose only a project’s direct dependencies at the root, keeping transitive ones tucked away.
So node_modules is more than a folder; it is the pressure point that shaped a generation of tooling. Flat installs, hoisting, deduplication, and pnpm’s linked store all exist to make this one directory smaller, faster, and safer.