12 novembre 2026

Trovare memory leak in frontend (davvero): individuare i “detached DOM nodes” con Chrome DevTools e agent

Quando l’app parte fluida e dopo qualche minuto diventa lenta, spesso non è “solo performance”: è memoria che non viene mai liberata.

I memory leak nelle web app moderne sono spesso causati da nodi DOM rimossi dalla pagina ma ancora referenziati da listener, closure o strutture dati: i classici detached DOM nodes. In questo articolo vediamo come riconoscere il sintomo, perché succede e come usare un flusso di analisi con snapshot e confronti automatici tramite un agent integrato con Chrome DevTools per arrivare rapidamente al responsabile del leak.

Capita spesso: l’app sembra reattiva, poi dopo qualche minuto di click, aperture/chiusure di modali e navigazione interna inizia a “sedersi”. Lo scrolling scatta, le animazioni perdono frame, e anche azioni banali diventano meno immediate. Se il peggioramento è progressivo e correlato all’uso, il sospetto più comune è un memory leak.

Tra le cause più frequenti in un frontend moderno ci sono i detached DOM nodes: elementi rimossi dal DOM, ma ancora vivi in memoria perché qualche riferimento li trattiene. Sono come “fantasmi”: non li vedi più nell’interfaccia, ma continuano a occupare risorse.

Il colpevole tipico: un nodo DOM “staccato” ma ancora referenziato

Un detached DOM node nasce quando:

  • un componente viene smontato o un modal viene chiuso;
  • i nodi vengono effettivamente rimossi dalla pagina;
  • qualcosa (listener, closure, cache, store, array) mantiene un riferimento a quei nodi o a oggetti che li referenziano.

Risultato: il garbage collector non può liberare quella memoria, perché dal punto di vista del runtime gli oggetti sono ancora raggiungibili.

Esempi ricorrenti:

  • event listener aggiunti manualmente e mai rimossi;
  • subscription a stream/event bus non “dispose” al teardown;
  • setInterval / setTimeout che catturano riferimenti in closure;
  • strutture dati globali (Map/Set/array) che accumulano elementi “chiusi” o non più utilizzati;
  • riferimenti al DOM memorizzati in singleton, store o servizi.

Il problema pratico: trovarlo a mano è lento

La parte frustrante non è tanto sapere che c’è un leak, ma trovare dov’è.

L’analisi manuale richiede tipicamente:

  1. riprodurre un flusso (es. apri/chiudi un modal 20 volte);
  2. fare uno snapshot dell’heap;
  3. ripetere il flusso;
  4. fare un altro snapshot;
  5. confrontare i risultati e inseguire catene di riferimenti (retain paths).

È un lavoro meticoloso: basta perdersi un dettaglio e si ricomincia.

Un approccio più rapido: snapshot + confronto guidato da agent

Un modo molto più efficiente è delegare la parte “meccanica” a un agent integrato con DevTools: invece di setacciare a mano l’heap, l’agent può:

  • creare automaticamente più heap snapshot in momenti chiave;
  • confrontarli per evidenziare cosa cresce e non torna giù;
  • puntare subito l’attenzione su oggetti sospetti (spesso proprio i detached DOM nodes);
  • indicare l’elemento e le relazioni che lo trattengono in memoria.

In pratica, trasformi una caccia al tesoro in un flusso ripetibile: “fai A → snapshot → fai A → snapshot → dimmi cosa è rimasto”.

Strategia consigliata: riproduci, misura, isola

Anche con strumenti automatici, la qualità del risultato dipende da come imposti il test. Una procedura solida:

  1. Scegli un’azione ripetibile che rappresenti il problema (aprire/chiudere un dialog, cambiare route, montare/smontare una lista virtualizzata).
  2. Esegui un ciclo (10–30 ripetizioni) per amplificare il leak.
  3. Acquisisci snapshot prima e dopo.
  4. Confronta cercando:
    • crescita costante di oggetti;
    • presenza di detached nodes;
    • listener o strutture dati che mantengono riferimenti.

Se dopo il ciclo la memoria non rientra (o rientra poco) e nello snapshot compaiono nodi “detached” che aumentano, hai un segnale forte.

Come chiudere davvero il leak: rimuovere la causa, non il sintomo

Quando individui il nodo o il componente che “resta appeso”, la fix tipica è una tra queste:

  • rimuovere event listener nel teardown/unmount;
  • disiscriversi da observer/subscription;
  • cancellare timer e callback pendenti;
  • eliminare riferimenti da cache/Map/Set quando l’elemento non serve più;
  • evitare di memorizzare direttamente riferimenti DOM in strutture long-lived (o quantomeno gestirli esplicitamente).

Il punto chiave: non basta “nascondere” un pezzo di UI. Se l’albero DOM o gli oggetti correlati restano raggiungibili, resteranno anche in memoria.

Sintesi: meno caccia ai fantasmi, più metodo

I memory leak in frontend raramente sono misteriosi: spesso sono nodi DOM staccati trattenuti da riferimenti dimenticati. La differenza la fa il flusso di indagine.

Usare un approccio basato su heap snapshot e confronto, supportato da un agent che automatizza raccolta e analisi, riduce drasticamente il tempo necessario per arrivare al responsabile. E una volta identificata la catena di riferimenti, la correzione diventa quasi sempre un’operazione di “pulizia” nel lifecycle: unsubscribe, removeEventListener, clearInterval e gestione rigorosa delle strutture dati long-lived.

Se un’app diventa più lenta “col tempo”, non è un destino inevitabile: spesso è solo memoria che non viene mai lasciata andare.