npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-memory-leak-detector

v1.3.0

Published

Stop hunting memory leaks with heap snapshots. This babel plugin tags every React component and hook with a uniquely-named marker, then a runtime tracker uses WeakRef + FinalizationRegistry to warn you, live in the console, the moment something leaks.

Readme

react-memory-leak-detector

Catch React memory leaks the moment they happen — no heap snapshots required.

When a component unmounts but something still holds onto it (a stray timer, listener, subscription, or closure), you get a console warning right away and a leak event you can forward to your own logger or Sentry. Dev-only — it compiles out of production builds.

Install

npm install --save-dev react-memory-leak-detector

Setup

Two steps: add the build plugin, then import the runtime once in dev.

1. Add the plugin

Vite

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import heapMarkers from "react-memory-leak-detector/vite";

export default defineConfig({
  plugins: [
    heapMarkers(), // list it BEFORE react()
    react(),
  ],
});

Dev-only by default, so nothing ships to production. Works with any @vitejs/plugin-react version (or none). Keep heapMarkers() before react() — it warns you at startup if the order is wrong.

Webpack / Next.js / Babel — add the plugin to your Babel config, in development:

// .babelrc
{ "env": { "development": { "plugins": ["react-memory-leak-detector/babel-plugin"] } } }

2. Import the runtime

Load it once at the top of your app entry, in dev only:

// Vite
if (import.meta.env.DEV) import("react-memory-leak-detector/runtime");

// Webpack / Next.js
if (process.env.NODE_ENV === "development") import("react-memory-leak-detector/runtime");

That's it. Type declarations are bundled, so TypeScript needs no extra setup.

Getting leaks

On page load the tracker installs, and leaks show up as console warnings as they happen:

[heap-leak] Suspected leak: UserCard — 1 instance(s) unmounted >10s ago still retained (live 1 total)

Any time, run window.__heapTracker.report() for a live table of tracked components. Two numbers matter:

  • live — instances still in memory (mounted, or unmounted and not yet collected).
  • stale — unmounted more than leakAgeMs ago and still in memory. Stale is the leak.

Prefer to handle leaks yourself — a debug overlay, your logger, Sentry? Subscribe:

const unsubscribe = window.__heapTracker.subscribe((event) => {
  // { component, stale, live, leakAgeMs, at }
  Sentry.captureMessage(`heap-leak:${event.component}`, { extra: event });
});

Events fire only for stale leaks and are debounced per component. configure({ logging: false }) keeps the events but silences the console.

Finding the cause

A stale component tells you what leaked; a heap snapshot tells you why. In Chrome DevTools → Memory, take a snapshot and filter for ComponentName$Heap:

Heap snapshot: LeakyTimeout$Heap instances retained, with the Retainers panel showing a DOMTimer holding them

Each row is a leaked instance. Select one and read the Retainers panel bottom-up to see what's holding it — here a DOMTimer, i.e. an uncleared setTimeout/setInterval. Clean it up on unmount and the instances disappear.

Configuration

All options are optional. The common ones, passed to the plugin:

heapMarkers({
  leakAgeMs: 10000,   // ms unmounted-but-retained before it counts as a leak
  logging: true,      // console warnings on/off
  trackHooks: true,   // also track custom hooks, not just components
  excludeNames: [/^useTranslation$/], // component/hook names to skip entirely
});

Runtime options can also be changed live: window.__heapTracker.configure({ leakAgeMs: 5000 }).

Compatibility

| React feature | Status | | ----------------------------------------- | ------------------------------------------ | | React 17 / 18 function components & hooks | ✅ | | React 18 concurrent, Suspense, StrictMode | ✅ | | SSR (effects no-op server-side) | ✅ | | React Compiler | ✅ likely; warrants a CI snapshot test | | <Activity mode="hidden"> | ⚠️ use excludeUnmountTracking to opt out | | React Server Components | ⚠️ use skipServerComponents: true | | Class components | ❌ not instrumented (PRs welcome) |

All plugin options

heapMarkers({
  // ── build-time: what gets instrumented ──
  include: /\.[tj]sx?$/,              // files to process
  excludeNames: [/^useTranslation$/], // names that skip ALL instrumentation
  excludeUnmountTracking: [],         // names that keep the heap marker but skip
                                      //   the unmount effect — use for components
                                      //   under <Activity mode="hidden">, whose
                                      //   cleanup fires while still alive
  trackHooks: true,                   // false = components only
  skipServerComponents: false,        // true = skip files without "use client" (RSC)

  // ── runtime: how leaks are judged/reported (injected into the app) ──
  logging: true,                      // false = track silently, no console warnings
  leakAgeMs: 10000,                   // age before an unmounted instance is "stale"
  suspectThreshold: 1,                // min stale instances before warning/event
  sweepIntervalMs: 2000,              // how often the sweep runs
  warnCooldownMs: 30000,              // min gap between warnings for the same component
});

Runtime options can also be set before load (window.__heapTrackerOptions = { … }, e.g. at the top of main.tsx) or live (window.__heapTracker.configure({ … })).

window.__heapTracker API

| Call | Purpose | | ---- | ------- | | report() | console.table of every component with live instances; also returns the array. | | subscribe(fn) | Listen for stale-leak events; returns an unsubscribe function. | | configure(opts) | Update runtime options (e.g. { logging: false }). | | sweep() | Force an immediate sweep (otherwise every sweepIntervalMs). | | forceGc() | window.gc?.() + re-sweep. Needs Chrome started with --js-flags="--expose-gc"; use it to rule out plain GC lag. |

Leak event shape: { component, stale, live, leakAgeMs, at }. component matches the ComponentName$Heap marker in heap snapshots.

Engines (Vite plugin)

The Vite plugin injects markers with Oxc by default and falls back to Babel automatically if oxc-parser can't load — you normally don't touch this. Force one with heapMarkers({ engine: "oxc" }) or "babel". The Babel engine parses with Babel and reads no project config, so non-default syntax (decorators, using, import attributes) needs its parser opted in, e.g. heapMarkers({ engine: "babel", parserPlugins: ["decorators-legacy"] }); the Oxc engine parses these natively. oxc-parser needs Node ^20.19 or >=22.12.

TypeScript option types are exported as HeapMarkersOptions (/babel-plugin) and HeapMarkersViteOptions (/vite).

How it works

  1. The plugin tags every component/hook with a uniquely-named marker — searchable as ComponentName$Heap in heap snapshots — and injects a synthetic useEffect that reports mount/unmount.
  2. The runtime holds each marker in a WeakRef + FinalizationRegistry and sweeps periodically.
  3. If a marker is still reachable well after its component unmounted, something is leaking it → warning + event.

License

MIT