A 1.8GB Memory Leak in Next.js: One Config Line, 4.4GB of RAM
By Arash Latifi
A real bug from the Logos team: an Edge runtime directive on an OG image route made `@resvg/resvg-wasm` leak memory on every render until the container OOM-crashed. Root cause, measurements, and the fix.
TL;DR — A single
export const config = { runtime: 'edge' }on an OG image route pushed a Next.js app to 4.4GB of memory in three minutes; the culprit was the known leak in@resvg/resvg-wasm, and deleting that one line cut usage by roughly 1.8GB.
This is the kind of bug that scares you because it's silent. The team at publishing platform Logos (in a write-up by Siddarth Kumar, March 2026) watched their container hit 4.4GB of memory within three minutes of load testing, OOM, restart, and repeat. Logs showed RuntimeError: unreachable coming from a WASM module inside og.js — the OG image generation route.
Root cause: Edge runtime on plain Node.js
The top of the OG image route had this line:
export const config = { runtime: 'edge' }
On Vercel Edge or Cloudflare Workers, fine. But when you deploy to a standard Node.js environment, Next.js still tries to honor the directive. Since the Edge runtime's built-in renderer isn't available, it falls back to @resvg/resvg-wasm — the same library @vercel/og uses under the hood to render SVG-based preview images. And resvg-wasm has a known memory leak: every render allocates WASM memory that never gets freed. On a route generating OG images for every page, it compounds fast.
The fix? Delete that one line. The route falls back to a standard Node.js handler using next/og, avoiding the leaky WASM path entirely.
The numbers: before and after
The author re-ran the same load test against master and the fix branch, sampling RSS and heap every 30 seconds:
| Time | RSS (master) | Heap (master) | RSS (fix) | Heap (fix) | | --- | --- | --- | --- | --- | | 18:08:21 | 1194 MB | 57 MB | 1424 MB | 46 MB | | 18:09:22 | 2690 MB | 311 MB | 2514 MB | 152 MB | | 18:10:23 | 3720 MB | 994 MB | 2553 MB | 109 MB | | 18:11:24 | 4404 MB | 1580 MB | 2571 MB | 148 MB |
After three minutes, master sat at 4404MB RSS with heap still climbing. The fix branch stabilized around 2571MB — roughly 1833MB saved, and more importantly, a flat heap instead of a linear climb.
Two more leaks found along the way
- Placeholder image cache: the PlaceholderService stored base64 placeholder strings in a
Mapwith no eviction; every image processed during ISR revalidation added an entry that was never removed. Fixed with a FIFO cap at 500 entries. - Search index cache: the PostSearchService accumulated Lunr inverted indexes keyed by query parameters — unbounded growth again. Capped at 50 entries with FIFO eviction.
Neither was the primary leak, but on a long-running server they'd bite eventually. One more fix: fonts were fetched over HTTP on every OG image request, replaced with fs.readFileSync at module level with cached buffers.
Checklist: is your Next.js app exposed?
- [ ] Search your repo for
runtime: 'edge'— if you deploy to plain Node.js, you don't need it - [ ] Find your OG image route (
opengraph-image.tsxor a dedicatedogroute) and check which renderer it uses - [ ] If you see
@vercel/ogorresvg-wasm, monitor container memory under load test - [ ] Audit server-side
Maps and caches for size caps / eviction - [ ] Load heavy assets like fonts once at module scope, not per request
- [ ] Add an alert on RSS above a threshold so you see a leak before the OOM
The bigger lesson
Don't copy-paste directives you don't understand. runtime: 'edge' isn't a hint; it's a contract that activates a completely different execution path in the wrong environment. If your app is quietly eating CPU or memory, start with image rendering routes and WASM, then move to uncapped caches. For general image optimization see our Next.js image guide, and for staying current check the Next.js 16.4 agent upgrade tooling.
Q: I deploy on Vercel — does this bug affect me?
Not this way. The problem appears when the Edge directive runs on a standard Node.js environment (your own server, Docker, a VPS). On Vercel, Edge routes actually run on the edge, so the fallback never happens — but if you later self-host, the same line becomes a time bomb.
Q: How do I know if my app is leaking memory?
Three signs: container RSS only climbs and never drops, heap grows linearly in a profiler, and the container restarts periodically with OOMKilled. Simplest approach: log RSS every 30 seconds and chart it — exactly what this team did.
Q: What's the difference between next/og and @vercel/og?
Per the original write-up, when the route runs as a standard Node.js handler it uses next/og and bypasses the leaky @vercel/og/resvg-wasm WASM path. Same OG image capability, no leak — provided you remove the Edge directive.