Memory leak in NodeJS broke our NextJS app

Because of memory problems in NodeJS our NextJS app was constantly crashing because of out of memory exceptions.

After one random deployment our NextJS suddenly started crashing. From when the app restarted memory consumption quickly started rising until we got an out of memory exception and the container restarted. This can be seen on the image below, where we can also see exactly the time where we figured out what the issue is and fixed it and stopped getting memory spikes.

NextJS memory spikes
NextJS memory spikes

The reason this was hard to debug and figure out what is happening was that the memory leak was not in our codebase. We were looking for bugs inside our project and trying to optimize things, and although we did some optimizations that actually helped a little bit by prolonging the time between crashes, eventually in couple of hours the container would crash nonetheless. Memory leak was inside NodeJS Fetch API. We first discovered this by going over issues and discussion on NextJS github page. There we found the discussion titled Possible memory leak in Fetch API which lead us a discussion on NodeJS github Memory Leak in nodejs version 20.16.0. There we could see the related fix PR in undici project fix: increased memory in finalization first appearing in v6.16.0. Undici is the http client NodeJS uses and it caused a memory leak for NodeJS version 20.16.0 on NextJS apps.

The thing we did wrong was having not enough specified NodeJS version in our package.json. It used to look like:

{
  ...
  "engines": {
    "node": ">=20.0.0"
  },
  ...
}

And at some point our hosting provider updated NodeJS version that is used and it switched to a broken one. Our temporary fix was to change it to previous version without memory leak:

{
  ...
  "engines": {
    "node": "=20.15.1"
  },
  ...
}

It was fixed in NodeJS version 22.7. so after that was released we changed package.json to:

{
  ...
  "engines": {
    "node": "=22.7.0"
  },
  ...
}

We decided that we will have from now on a specific version and update it during dependency updates, where we also update other packages in package.json, so that it is easier to pin point where something went wrong and this kind of problem won't occur all of a sudden.