Skip to main content

Next.js out of memory error in production

TL;DR

How to diagnose and fix out of memory errors in self-hosted Next.js production applications.

Key facts

Topic
Production error triage
Stack
Next.js / Linux

TL;DR

A self-hosted Next.js application running out of memory in production typically crashes with FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory or is killed silently by the Linux OOM killer. Unlike Vercel, where each serverless invocation has isolated memory, a self-hosted Next.js process accumulates state across all requests.

Common causes

  • ISR cache growing unboundedly — every Incremental Static Regeneration page is cached in memory by default. High-cardinality routes (product pages, user profiles) can generate thousands of cached entries that never evict
  • getServerSideProps memory leaks — allocating large objects during server-side rendering without releasing them, especially when fetching and transforming large API responses on every request
  • Image optimization — Next.js's built-in image optimizer (next/image with the default loader) processes images in memory. Large images or high concurrent optimization requests spike memory rapidly
  • Module-level caches — singletons or module-scoped Maps and Sets that grow with each unique request but never shrink

Diagnosis

Check for OOM kills in kernel logs:

dmesg | grep -i "killed process"
journalctl -k | grep -i "out of memory"

Monitor the Next.js process memory over time:

pm2 monit
# Or snapshot current usage:
pm2 describe nextapp | grep memory

Take a V8 heap snapshot to find the largest retained objects:

NODE_OPTIONS='--inspect' pm2 start ecosystem.config.js
# Connect Chrome DevTools to the inspect port and take heap snapshots

Immediate fix: increase heap and set safety nets

Set --max-old-space-size to give the V8 heap more room, and configure PM2 to restart before an actual OOM crash:

module.exports = {
  apps: [{
    name: 'nextapp',
    script: '.next/standalone/server.js',
    node_args: '--max-old-space-size=4096',
    max_memory_restart: '3500M',
    instances: 2,
    exec_mode: 'cluster',
  }],
};

The max_memory_restart value should be lower than max-old-space-size so PM2 recycles the process gracefully before V8 hits the hard limit and crashes.

Long-term fixes

Control ISR cache size by configuring isrMemoryCacheSize in next.config.js:

module.exports = {
  experimental: {
    isrMemoryCacheSize: 50,
  },
};

This limits the number of ISR pages held in memory. For high-cardinality routes, consider using an external cache (Redis) via a custom cache handler.

Optimise getServerSideProps by streaming large responses, paginating database queries, and avoiding storing full result sets in variables:

export async function getServerSideProps() {
  const data = await fetch('/api/items?limit=50');
  return { props: { items: await data.json() } };
}

Reduce image optimization memory by using an external image CDN (Cloudinary, imgix) instead of the built-in optimizer, or limit concurrent optimizations with a queue.

Add swap space as a final safety net to absorb brief memory spikes without triggering the OOM killer:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Where Reflex helps

Reflex tracks V8 heap usage and process RSS on your Next.js servers. When memory climbs steadily toward the configured threshold, Reflex can execute a rolling restart across cluster workers — draining connections from one worker at a time — preserving availability while resetting memory. It alerts your team with a memory trend timeline and the request patterns that correlate with growth. See How it works.