
next-leak: ten a túa app de Next.js unha fuga de memoria?
npx next-leak .Descobre se a túa app de Next.js ten de verdade unha fuga de memoria: canta, en que ruta e de quen é a culpa.
Seccións
Que responde
- Non tes unha fuga: o pico é transitorio e baléirase en repouso. É o caso máis común.
- Algo estase a encher, non a fugar: unha caché limitada camiño do seu teito.
- A fuga está no teu código ou nunha dependencia, co ficheiro fonte cando é posible.
- Parece do propio framework, cun borrador de issue listo para abrir.
Comezar
Compila a túa app con output: "standalone" e executa isto desde o seu directorio:
next build npx next-leak .
Como mide
Cada ruta mídese nun proceso novo:
warm-up → forced GC → baseline snapshot → [load → idle → GC → sample] ×4 → snapshot
O veredicto sae da forma da curva tras o GC forzado, non de onde está o heap: 40 MB e 400 MB non din nada por si sós.
Veredictos
- stable
- Ningún crecemento que esta execución puidese detectar. Non proba que non haxa fuga: o veredicto prefire perder unha fuga a inventala.
- leak
- O heap retido segue a medrar en cada ciclo. Nomea o culpable cando os source maps o resolven.
- saturating
- Cada ciclo medrou, pero menos que o anterior: un almacén limitado que se queda sen claves novas.
- inconclusive
- A evidencia non decide. A ruta vólvese medir co dobre de ciclos.
- failed
- A ruta deu erros baixo carga: máis dun 1% de respostas que non son 2xx detén a medición.
En arredor de 25 rutas sas de apps en produción (PPR, MDX, Auth.js, Sentry, i18n) non deu ningún falso positivo.
Exemplo real
/api/heap — reprodución de #95094, corrixido en Next 16.3.0
| 1–3 | 28.7 · 40.3 · 59.0 |
|---|---|
| 4–6 | 75.8 · 75.9 · 101.2 |
| 7–9 | 101.2 · 139.0 · 139.0 |
| Pendente | +4.70 MB/1000 req |
Retedor
grown [object] Array 112.5 MB — TimeoutsManager#object[.resources] <- system / Context#object[.timeoutsManager] <- destroy#closure[.context] <- ResourceManager#object[.properties] <- IntervalsManager#object[.map]
Unha ruta sa devolve entre o 20 e o 30% do que medra. Esta non devolve nada: esa é a forma de escaleira.
Medir o build, non o servidor
npx next-leak build .
Un sitio grande pode quedar sen heap mentres prerenderiza, antes de que exista un servidor que medir. Este comando executa o teu build sen modificalo e mostrea a memoria residente de cada worker de xeración estática. Non precisa un build previo nin saída standalone.
| Next 16.3.3 | leak | Sen heap tras 1.617 e 1.525 de 2.504 páxinas, en dúas execucións. |
|---|---|---|
| Next 16.2.12 | Remata con 0.05 MB por páxina. |
Corrixido en 16.3.5 co mesmo arranxo que #97938. Aínda sen volver medir aquí.
O proceso principal do build infórmase, non se xulga. Nesa reprodución baixou de 1.43 GB a 0.10 GB mentres os workers subían, así que sumalos anularía o achado.
--attribute ademais nomea que retén o worker. É opcional e lento: o worker escribe todo o seu heap en disco.
Verificado con issues reais de Next.js
Estado das issues revisado o 19 de set. de 2026.
| Issue | Que é | Medido | Estado |
|---|---|---|---|
| #97938 | use cache / cacheComponents: AbortSignal.any composites never released | +705 KB per request on 16.3.3, flat on 16.2.6 | corrixida en 16.3.5 |
| #84884 | axios + AbortSignal in middleware: a reference cycle through undici's Request finalizer | +17.02 MB/1000 req on 16.3.5 (Node 24.18), +4.62 (Node 24.21); flat with scope hoisting off | aberta · arranxo proposto en nodejs/undici#5822 |
| #98707 | next dev: a route handler compiled after N pages costs ~16 MB × N | 851 MB on 16.3.0-canary.100, 2,354 MB on canary.101, 1,704 MB on 16.3.5 | aberta |
| #96533 | ISR revalidation holds RSC buffers between collections | 4–5 MB of arrayBuffers held vs 0.32 MB retained | aberta |
| #92287 | Cache Components: unbounded arrayBuffers under load | 37.5 MB of arrayBuffers held between collections, 37x what it retains (16.3.1) | aberta |
| #89091 | zlib retention on mid-stream aborts | +42.5 MB/1000 aborted req on 16.1.5; +0.03 on 16.3.1 | pechada |
| #95094 | Middleware setTimeout ids retained by the sandbox | 112 MB retained; flat after the fix | corrixida en 16.3.0 |
| #94890 | Router LRU cache doesn't count its keys | 26.7 → 71.9 MB | corrixida en 16.3.0 |
| #94919 | Retention on client aborts | 39 → 139 MB | corrixida en 16.3.0 |
En #95094 atopou a fuga, 28.7 → 138.9 MB en 8 ciclos. Co workaround do fío aplicado (clearTimeout(id)), mesma app e mesmos parámetros: 27.8 → 25.6 MB, plano.
As corrixidas seguen na lista a propósito: amosan que as medicións coincidiron co que resultaron ser os arranxos.
Alcance e límites
- O comando por defecto precisa App Router,
output: "standalone", Node 22 ou posterior, e Linux ou macOS. Pages Router, os builds sen standalone e Windows rexéitanse cunha mensaxe clara. stablenon proba que non haxa fuga. Executa antes--self-check: planta unha fuga de 8 KB por petición e demostra que o arnés a ve onde o estás a executar.- Cada proceso medido corre cun límite de heap de 512 MB, para que unha fuga chegue ao teito en minutos. As apps con máis memoria de traballo precisan
--max-old-space. - Unha app de 60 rutas cos valores por defecto tarda horas. Acota con
--routesmentres iteras. - Nomear o ficheiro require un build de Turbopack con source maps de servidor, o habitual desde Next 15. En builds de webpack os achados quedan sen atribuír; a medición non depende diso.
- As rutas no límite poden alternar entre
stableeleakdunha execución a outra. Máis ciclos resólveno. - A app execútase co seu contorno real: as rutas que chaman a servizos externos chamaranos baixo carga.
Ligazóns
- github.com/xabierlameiro/next-leakCódigo fonte, README e issues
- npmjs.com/package/next-leakO paquete en npm
- npmx.dev/package/next-leakTamaño de instalación, dependencias e descargas
- Como atopar unha fuga de memoria de Next.js en produciónO artigo que explica o método
- next-coverageA outra ferramenta: que APIs de Next.js usa a túa app?
- Un veredicto incorrecto? Abre unha issue