next-oom
v0.1.0
Published
Auto-restart Next.js dev server when memory exceeds threshold. Fights Turbopack memory leaks so you don't have to.
Maintainers
Readme
next-oom
Auto-restart Next.js dev server when memory exceeds threshold. Fights Turbopack memory leaks so you don't have to.
Why
Next.js dev server (especially with Turbopack) leaks memory over time — HMR module graph accumulation, transpilePackages caching, and V8 heap fragmentation cause RSS to grow from ~500 MB to 1 GB+ after browsing a few pages. This is a known Next.js issue with no root fix.
next-oom watches the next-server process memory and automatically restarts your dev server when it gets too fat. It also injects V8 GC optimization flags to slow the bleed.
Quick Start
# npx — zero install
npx next-oom -- next dev --turbopack
# or install as devDependency
npm install -D next-oomThen in your package.json:
{
"scripts": {
"dev": "next-oom -- next dev --turbopack"
}
}Usage
next-oom [options] -- <command...>Everything after -- is the command to run and monitor.
Examples
# Basic — wraps your Next.js dev command
next-oom -- next dev --turbopack
# Custom threshold (2 GB) and check interval (15s)
next-oom --threshold 2048 --interval 15 -- next dev
# With Turbo monorepo
next-oom -- npx turbo dev --ui stream
# Monitor-only mode (no auto-restart, just observe)
next-oom --monitor-only -- next dev --turbopack
# Disable GC optimization flags
next-oom --no-gc-optimize -- next dev --turbopackOptions
| Flag | Default | Description |
|------|---------|-------------|
| --threshold <mb> | 1536 | Memory threshold in MB. Restart when total RSS exceeds this. |
| --interval <sec> | 10 | How often to check memory (seconds). |
| --grace <sec> | 15 | Startup grace period — skip checks for this many seconds after (re)start. |
| --max-old-space-size <mb> | same as threshold | V8 old generation heap limit passed to child process. |
| --no-gc-optimize | — | Don't inject NODE_OPTIONS GC flags into child process. |
| --monitor-only | — | Only monitor and display memory usage, don't auto-restart. |
| -h, --help | — | Show help. |
| -v, --version | — | Show version. |
How It Works
┌─────────────────────────────────────────────────────────────┐
│ next-oom (parent) │
│ │
│ ┌─ spawn (stdio: inherit) ──────────────────────────┐ │
│ │ your command (e.g. next dev --turbopack) │ │
│ │ └── next-server ← tracked via ppid chain │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ every <interval>s: │
│ ps -eo pid,ppid,rss,comm │
│ → build child process tree from ppid chain │
│ → find next-server within descendants only │
│ → sum RSS across all next-server workers │
│ → if total > threshold → SIGTERM → restart │
│ │
│ rapid restart protection: │
│ > 5 restarts in 60s → exit (avoid infinite loop) │
│ │
│ stderr status bar (doesn't interfere with stdout): │
│ [next-oom] PID 87076 ████████░░░░ 1,234/1,536 MB (80%) │
└─────────────────────────────────────────────────────────────┘Key Design Decisions
stdio: inherit— Full stdin/stdout/stderr passthrough. Interactive commands work, no buffering issues, no output corruption.- ppid-based process discovery — Doesn't grep all processes blindly. Walks the child process tree from the spawned PID to find
next-serverdescendants. Safe when multiple dev servers run simultaneously. - Status bar on stderr — Monitoring output goes to stderr so it never mixes with your app's stdout. Hide it with:
next-oom -- next dev 2>/dev/null. - Total RSS across workers — If Next.js spawns multiple
next-serverworkers, their RSS is summed for threshold comparison. - Rapid restart protection — If the process restarts more than 5 times within 60 seconds, next-oom exits to avoid infinite restart loops. This usually means your
--thresholdis set too low. - Graceful shutdown — On restart, sends SIGTERM first and waits 5s. Falls back to SIGKILL if the process doesn't exit, including orphaned
next-serverchildren.
What Gets Injected
When --no-gc-optimize is not set, the following NODE_OPTIONS are injected:
--max-old-space-size=<threshold> --max-semi-space-size=64--max-old-space-size: Caps V8 old generation heap, triggers more frequent GC before reaching the restart threshold--max-semi-space-size=64: Optimizes young generation for HMR's many short-lived objects
These flags are inherited by all Node.js processes in the child tree (safe — only active when a process approaches the limit).
Requirements
- Node.js >= 20
- macOS or Linux (uses
psfor process discovery) - Minimal dependencies (only execa)
FAQ
Q: Does it work with Turbo monorepos?
Yes. Wrap your turbo command: next-oom -- npx turbo dev --ui stream. The --ui stream flag is recommended to avoid TUI output conflicts.
Q: What if I run multiple dev servers? Each next-oom instance only monitors its own child process tree (via ppid chain). Multiple instances won't interfere with each other.
Q: What about multiple next-server workers? Their RSS is summed. If you have 4 workers at 400 MB each (1.6 GB total), a restart is triggered at the default 1.5 GB threshold.
Q: Does it work on Windows?
Not yet — ps is not available on Windows. PRs welcome for wmic / tasklist support.
Q: What happens after restart? The browser does a full page reload (HMR state is lost). This is the trade-off: fresh memory vs. preserved state. In practice, if memory hit 1.5 GB, HMR was likely slow anyway.
Q: How is this different from PM2's --max-memory-restart?
| Feature | next-oom | PM2 |
|---|---|---|
| Check interval | Configurable (default 10s) | Fixed 30s |
| Process discovery | ppid tree (multi-instance safe) | PID file |
| V8 GC optimization | Auto-injected | Manual |
| Real-time status bar | Yes (stderr) | No (need pm2 monit) |
| Multi-worker RSS sum | Yes | Per-process only |
| Rapid restart protection | Yes (5 restarts/60s limit) | No |
| Minimal dependencies | Yes (execa only) | ~30 MB install |
| Monitor-only mode | Yes | No |
License
MIT
