Debugging memory leaks in web apps is hard. The tooling exists, but it’s complicated, cumbersome, and often doesn’t answer the simple question: Why is my app leaking memory?
Because of this, I’d wager that most web developers are not actively monitoring for memory leaks. And of course, if you’re not testing something, it’s easy for bugs to slip through.
When I first started looking into memory leaks, I assumed it was a rare thing. How could JavaScript – a language with an automatic garbage collector – be a big source of memory leaks? But the more I learned, the more I suspected that memory leaks were actually quite common in Single Page Apps (SPAs) – it’s just that nobody is testing for it!
Since most web developers aren’t fiddling with the Chrome memory tools for the fun of it, they probably won’t notice a leak until the browser tab crashes with an Out Of Memory error, or the page slows down, or someone happens to open up the Task Manager and notice that a website is using many megabytes (or even gigabytes!) of memory. But at that point, it’s gotten bad enough that there may be multiple leaks on the same page.
I’ve written about memory leaks in the past, but my advice basically boils down to: “Use the Chrome DevTools, follow these dozen tedious steps, and then maybe you can figure out why your page is leaking.” This is not a great developer experience, and I’m sure many readers just shook their heads in despair and moved on. It would be much better if a tool could find memory leaks automatically.
That’s why I wrote fuite
(French for “leak”). fuite
is a CLI tool that you can point at any URL, and it will analyze the page for memory leaks:
npx fuite https://example.com
That’s it! By default, it assumes that the site is a client-rendered SPA, and it will crawl the page for internal links (such as /about
or /contact
). Then, for each link, it runs the following steps:
- Click the link
- Press the browser back button
- Repeat to see if memory grows
If fuite
finds any leaks, it will show which objects are suspected of causing the leak:
Test : Go to /foo and back Memory change: +10 MB Leak detected: Yes Leaking objects: | Object | # added | Retained size increase | | ----------------- | ------- | ---------------------- | | HTMLIFrameElement | 1 | +10 MB | Leaking event listeners: | Event | # added | Nodes | | ------------ | ------- | ------ | | beforeunload | 2 | Window | Leaking DOM nodes: DOM size grew by 6 node(s)
To do this, fuite
uses the basic strategy outlined in my blog post. It will launch Chrome, run some scenario n number of times (7 by default) and see if any objects are leaking a multiple of n times (7, 14, 21, etc.).
fuite
will also analyze any Arrays, Objects, Maps, Sets, event listeners, and the overall DOM to see if any of those are leaking. For instance, if an Array grows by exactly 7 after 7 iterations, then it’s probably leaking.
Testing real-world websites
Somewhat surprisingly, the “basic” scenario of clicking internal links and pressing the back button is enough to find memory leaks in many SPAs. I tested fuite
against the home pages for 10 popular frontend frameworks, and found leaks in all of them:
Site | Leak detected | Internal links | Average growth | Max growth |
---|---|---|---|---|
Site 1 | yes | 8 | 27.2 kB | 43 kB |
Site 2 | yes | 10 | 50.4 kB | 78.9 kB |
Site 3 | yes | 27 | 98.8 kB | 135 kB |
Site 4 | yes | 8 | 180 kB | 212 kB |
Site 5 | yes | 13 | 266 kB | 1.07 MB |
Site 6 | yes | 8 | 638 kB | 1.15 MB |
Site 7 | yes | 7 | 1.37 MB | 2.25 MB |
Site 8 | yes | 15 | 3.49 MB | 4.28 MB |
Site 9 | yes | 43 | 5.57 MB | 7.37 MB |
Site 10 | yes | 16 | 14.9 MB | 186 MB |
In this case, “internal links” refers to the number of internal links tested, “average growth” refers to the average memory growth for every link (i.e. clicking it and then pressing the back button), and “max growth” refers to whichever internal link was leaking the most. Note that these numbers don’t include one-time setup costs, as fuite
does one preflight iteration before the normal 7 iterations.
To confirm these results yourself, you can use the Chrome DevTools Memory tab. Here is a screenshot of the worst-performing site from my set, where I click a link, press the back button, take a heap snapshot, and repeat:

On this particular site, memory grows by about 6 MB every time you click a link and go back.
To avoid naming and shaming, I haven’t listed the actual websites. The point is just to show a representative sample of some popular SPAs – the authors of those websites are free to run fuite
themselves and track down these leaks. (Please do!)
Caveats
Note, though, that not every leak in an SPA is an egregious problem that needs to be addressed. SPAs need to, for example, maintain the focus and scroll state to properly support accessibility, which means that there may be some small metadata that is stored for every page navigation. fuite
will dutifully report such leaks (because they are leaks), but it’s up to the developer to decide if a tiny leak is worth chasing or not.
Some memory growth may also be due to browser-internal changes (such as JITing), which the web page can’t really control. So the memory growth numbers are an imperfect measure of what you stand to gain by fixing leaks – it could very well be that a few kBs of growth are unavoidable. (Although fuite
tries to ignore browser-internal growth, and will only say “leaks detected” if there is actionable advice for the web developer.)
In rare cases, some memory growth may also be due to outright browser bugs. While analyzing the sites above, I actually found one (Site #4) that seems to be suffering from this Chrome bug due to <img loading="lazy">
not being unloaded. Unfortunately it’d be hard for fuite
to detect browser bugs, so if you’re mystified by a leak, it’s good to cross-check against other browsers!
Also note that it’s almost impossible for a Multi-Page App (MPA) to leak, because the browser clears memory on every page navigation. (Assuming no browser bugs, of course.) During my testing, I found two frontend frameworks whose home pages were MPAs, and unsurprisingly, fuite
couldn’t find any leaks in them. These were excluded from the results above.
Memory leaks are more of a concern for SPAs, where memory isn’t cleared automatically on each navigation. fuite
is primarily designed for SPAs, although you can run it on MPAs too.
fuite
currently only measures the JavaScript heap memory in the main frame of the page, so cross-origin iframes, Web Workers, and Service Workers are not measured. Something like performance.measureUserAgentSpecificMemory()
would be more accurate, but it’s only available in cross-origin isolated contexts, so it’s not practical for a general-purpose tool right now.
Other memory leak scenarios
The “crawl for internal links” scenario is just the default one – you can also build your own. fuite
is built on top of Puppeteer, so for whatever scenario you want to test, you essentially just need to write a Puppeteer script to tell the browser what to do. Some common scenarios you might test are:
- Open a modal dialog and then close it
- Hover over an element to show a tooltip, then mouse away to dismiss it
- Scroll through an infinite-loading list, then navigate away and back
- Etc.
In each of these scenarios, you would expect memory to be the same before and after. But of course, it’s not always so simple with web apps! You may be surprised how many of your dialogs and tooltips are harboring memory leaks.
To analyze leaks, fuite
captures heap snapshot files, which you can load in the Chrome DevTools to inspect. It also has a --debug
mode that you can use for more fine-grained analysis: stepping through the test as it’s running, debugging the browser in real-time, analyzing the leaking objects, etc.
Under the hood, fuite
is a fairly basic tool, and I won’t claim that it can do 100% of the work of fixing memory leaks. There is still the human component of figuring out why your objects were allocated and retained, and then finding a reasonable fix. But my goal is to automate ~95% of the work, so that it actually becomes achievable to fix memory leaks in web apps.
You can find fuite
on GitHub. Happy leak hunting!
Update: I made a video tutorial showing how to debug memory leaks with fuite
.