Netlify’s build cache is a powerful tool to dramatically speed up your build times, but most people only think of it as a black box that magically makes things faster.

Let’s see it in action. Imagine a simple package.json for a Node.js project:

{
  "name": "my-fast-site",
  "version": "1.0.0",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "scripts": {
    "build": "react-scripts build"
  }
}

When Netlify builds this for the first time, it needs to download react and react-dom. This happens in a build image that’s essentially a fresh Linux environment. npm install (or yarn install) will fetch these dependencies from the npm registry and place them in the node_modules directory. The entire node_modules directory can be quite large, especially for complex projects.

Now, here’s where the cache comes in. Netlify identifies certain directories and files as cacheable. For Node.js projects, the primary one is node_modules. On subsequent builds, Netlify checks if the node_modules directory from the previous build is still valid. If it is, instead of re-downloading everything, it restores the cached node_modules directory. This means your npm install step can effectively become an npm ci (clean install), which is orders of magnitude faster because it skips the dependency resolution and download phases and just installs the exact versions specified in package-lock.json or yarn.lock.

The magic happens because Netlify’s build system uses a content-addressable cache. This means it generates a unique hash based on the contents of your dependency lock file (package-lock.json or yarn.lock). If that hash matches the hash from a previous build, Netlify knows the dependencies haven’t changed and can restore the cached node_modules.

This isn’t just about node_modules. Netlify caches other things too, like Go modules (pkg/mod), Ruby gems (vendor/bundle), and Python packages (venv). The key is that Netlify intelligently identifies these based on your project’s build command and detected language.

The exact mechanism involves Netlify storing the cached node_modules (or equivalent) in its object storage, keyed by the hash of your lock file. When a build starts, Netlify calculates the hash of your current lock file. If a cache entry with that hash exists, it downloads that cached directory to the build container before your build command even runs. If not, it proceeds with a fresh download.

To see this in action, look at your Netlify build logs. The first build will show a significant amount of time spent on npm install (or your package manager’s equivalent). Subsequent builds, provided your lock file hasn’t changed, will show a much shorter duration for this step, often with messages indicating cache restoration.

The primary lever you control is your dependency lock file. Any change to package.json or package-lock.json (or yarn.lock) will generate a new hash, invalidating the cache and forcing a fresh download. This is by design; it ensures you always get the correct, intended dependencies.

Netlify’s caching is particularly effective for front-end projects built with tools like Create React App, Vue CLI, or Next.js, where node_modules can easily exceed hundreds of megabytes. For these projects, a cached build can be minutes faster than a cold build.

One critical aspect often overlooked is that Netlify doesn’t just cache the node_modules folder itself. It caches the entire directory structure that your package manager creates based on the lock file. This means if you have custom scripts that modify or add files within node_modules (which is generally discouraged but sometimes done), those changes won’t be part of the restored cache unless they are also dictated by the lock file. The cache is a snapshot of what your package manager should have installed.

The next thing you’ll likely encounter is optimizing the cache for monorepos, where multiple packages might have their own dependencies.

Want structured learning?

Take the full Netlify course →