node-prewarm: CLI for Node 25's Compile Cache

Back to Blog Listing

node-prewarm: CLI for Node 25's Compile Cache

New in Node 25 is the node compile cache, which can deliver a 20% to 30% boost in server start-up time. I built `node-prewarm` to make that practical on real projects with minimal changes.

Ben HoustonMay 15, 20264 min read

When Node.js made the module compile cache stable in Node 25.4.0, I immediately wanted to use it. A low-friction build-time optimization that can improve startup responsiveness by a noticeable amount is exactly the sort of thing I like.

In earlier experiments, I had seen results in the rough 20% to 30% range. On this website, the final measured improvement landed at about 20%, which is still very worthwhile for such a low-impact change.

This matters even more on scale-to-zero platforms such as Google Cloud Run, which is what I use for this blog. Faster startup times reduce the penalty of cold starts, which makes it more practical to let the service scale all the way down to zero when nobody is using it instead of paying to keep instances warm all the time.

The problem is that the raw feature is only a mechanism. You still need a practical way to start your app during a build, wait until it is actually listening, and then shut it down cleanly so the compile cache is populated before the real deployment starts.

I wanted something I could drop into different Node projects without invasive application changes, so I built node-prewarm.

Why This Is Useful#

Node's compile cache stores compiled module output on disk. On later starts, Node can reuse that work instead of recompiling everything again.

In practice, that means you can often improve startup responsiveness with only two ingredients:

  • Set NODE_COMPILE_CACHE to a writable directory.
  • Start the app once during your Docker build so Node has a chance to fill that cache.

The annoying part is orchestration. You need to know when the app is actually ready, and you need to stop it gracefully afterward. That is the gap node-prewarm fills.

What node-prewarm Does#

node-prewarm runs your normal Node startup command, waits until a TCP port accepts connections, and then sends SIGTERM so the process can exit cleanly.

That makes it work well for build-time cache generation:

node-prewarm "node server.js" --port 8080

If you just want to measure your server's start-up time and not require NODE_COMPILE_CACHE to be defined, you can use:

node-prewarm "node server.js" --port 8080 --dry-run

In --dry-run mode, it still times startup until the app is listening.

Docker Integration#

This was the main design goal: low-impact integration. I did not want to add custom application code just to make compile cache practical.

Here is the basic Docker pattern:

FROM node:26 AS build
WORKDIR /app

COPY package*.json ./
# assuming node-prewarm installed via package.json
RUN npm install

COPY . .
RUN npm run build

ENV NODE_COMPILE_CACHE=/app/.node_compile_cache
RUN npx node-prewarm "node server.js" --port 8080

CMD ["node", "server.js"]

If your image already sets PORT, node-prewarm will respect it. The important thing is that the prewarm step happens during the image build, so the first real container start can benefit from the populated cache.

Benchmark Results on This Website#

To test this on a real project, I used this personal website as the benchmark target.

I measured three things:

  1. A baseline with compile cache explicitly disabled.
  2. A prewarm pass that generated the cache.
  3. A second round of timed starts using the generated cache.

Across repeated runs on this website's production build, the median time until the server listened dropped from 290ms without compile cache to 238ms with a prewarmed compile cache. That is about a 20% improvement in startup responsiveness.

The prewarm pass itself took 316ms during the build and created 3.5MB of data.

For me, that is the sweet spot. A build-time step of about a third of a second bought a noticeable startup win.

Conclusion#

Node's stable compile cache is one of those features that is more valuable than it first appears. The hard part is not the cache itself. The hard part is making it easy to use consistently across projects and containers.

That is why I built node-prewarm.

If you are running Node 25+ services and care about startup responsiveness, node-prewarm gives you a low-impact way to adopt compile cache, benchmark it with --dry-run, and integrate it cleanly into Docker builds. That is especially appealing for scale-to-zero deployments like Cloud Run, where better cold-start performance makes it easier to avoid running the service when nobody is using it. On this website, it was an easy win.