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
getServerSidePropsmemory 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/imagewith 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.