Build1 distinct publisher3 min readPublished
A dev.to walkthrough clocks a telemetry tab gaining 20MB a minute at 500 events per second. The shallowRef refactor that stops the proxy walk hands you a fresh 2,000-element array copy on every ingest instead.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Take the two rates together. Five hundred events a second is 30,000 a minute, and 20MB of heap growth across that minute [1] works out to roughly 0.7KB retained per event [1]. That is the right order of magnitude for a small record plus its proxy handlers plus a queued render job, so the account is at least internally consistent, though it is still arithmetic about someone else's payload.
Run the same rate out to the stated freeze at the two-hour mark and it implies about 2.4GB of net retention [2]. For that to be literal, the collector has to be reclaiming close to nothing, which means a live reference still reaches every batch ever ingested. The dev.to walkthrough attributes that to internal proxy references lingering across component re-renders and closure boundaries [13]. An unreachable proxy is collectable like any other object, so the retaining edge has to come from somewhere, and the article's own cause list supplies two candidates: index keys that confuse the patch algorithm during high-frequency updates, and child components registering global listeners they never remove in onUnmounted [7].
The proxy count reads as a real cost. The retention does not read as a leak. Five thousand records producing tens of thousands of wrappers [5] implies at least four proxies per record [5], because the multiplier is per nested object rather than per row: in the sample interface the metrics object and the tags array each get their own traps [15]. Every update then walks that structure, schedules queue work, and pushes the virtual DOM to diff the whole tree [6]. That is a tax proportional to live data, which is a different failure from unbounded retention of dead data.
The replacement ingest path is cheaper per item and dearer per call. It assigns a fresh array by spread on every call, and once length passes 2,000 it reassigns again with slice(-1500) [10]. Under a 500-per-second feed the buffer burns its 500 records of headroom in one second [3], so the trim fires roughly once a second on top of one whole-array copy per call. Call ingestBatch per event rather than per window and you are copying up to a million array slots a second on the main thread [4]. Windowing the socket is what makes that copy affordable, and nothing in the pattern does it for you.
Note also that capping the buffer at 1,500 records would cut memory under deep reactivity as well [10]. It is a capacity decision that happens to travel alongside the shallowRef refactor [8], independent of it.
For 20MB a minute to be your number, the account needs nesting comparable to the sample record [15], an append path that treats the stream as an immutable collection and never drops old rows [12], and rows that survive in the v-for as real DOM nodes, which is the retention the article pairs with virtualisation in its own title [14]. Change the third and the proxy fix stops being the dominant term.
Heap snapshots are tedious, and here they are the only witness: the detached-node count filtered by constructor is the thing that separates the two failures [11].
Ranked by verification strength, evidence, and original report placement.
Vue 3 replaced Vue 2's getter/setter tree traversal with native ES6 Proxy objects; passing a large array of objects to standard ref() or reactive() makes Vue recursively walk every nested property of every object and wrap them in nested getter/setter traps.
Each time a backend payload updates an item, the reactivity system traverses those proxies, schedules queue updates, and forces the virtual DOM to evaluate diffs across the entire structural tree.
The article names three compounding causes: deep proxy overhead from storing large read-heavy or append-only datasets in standard ref(); array indices used as v-for keys instead of unique primary keys, which confuses the patch algorithm during high-frequency updates; and child components inside list items registering global window or event-bus listeners without cleaning them up in onUnmounted.
shallowRef() opts out of recursive proxy conversion and tracks reactivity only on its .value property, so replacing the entire array triggers updates while modifying an inner property does not.
The refactored code calls triggerRef(telemetryStream) explicitly after mutating an inner metric, because deep reactivity is disabled.
The recommended ingest function assigns a new array via spread on every call, and when the array exceeds 2,000 entries it replaces it with slice(-1500) to keep the memory footprint bounded.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Allow-list the closed set, block-list the open one: 193 thin geo pages, one gate1 distinct publisher
build
A GAN beauty filter is a device budget allocation, not a feature toggle1 distinct publisher
build
Agent memory rots by accumulation, and the missing primitive is a supersession key1 distinct publisher
build
A reducer seeded at zero erased a 6,300-cent downside from the frontier summary1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Mechanics you can check, numbers you cannot
This story splits cleanly in two. The framework half stands on its own: recursive proxy conversion, shallowRef tracking only .value, the manual triggerRef that follows from it, and the printed code that demonstrates each. The incident half — 20MB a minute, 500 events a second, tens of thousands of wrappers, a crash at the two-hour mark — is recalled prose. The piece tells you to take a heap snapshot and then never shows one, not before the refactor and not after it.
One unnamed app, described by its own author
There is no adoption signal to measure. A single production deployment is asserted by the person who built it, with no name, scale, version, or third party attached, and nothing in this reporting indicates anyone else has applied the pattern or reproduced the failure.
A fix declared, then never weighed
'Memory leak' is carrying weight the evidence does not. Most of what is described is a live, unbounded buffer plus retained list DOM — real problems, but growth by design rather than leakage, and the slice() cap that fixes the buffer is presented as part of the reactivity fix. The bigger stretch is silence about price: the prescribed ingest rebuilds the entire array on every call, up to a million slots a second at the story's own feed rate, and that appears nowhere in the accounting. Take the 20MB-a-minute figure literally and it implies roughly 2.4GB retained before the crash, a number the piece asserts without pausing over.
Written for the search box, not for a vendor
No product is being sold here and no license or price is at stake, which keeps the pressure modest. What is visible is attention engineering: the phrase 'vue 3 memory leak large lists' sits mid-sentence in the prose exactly as a search query would be typed, and the structure — dramatic crash, named villain, four pasteable blocks, victory declared — is shaped by what earns reads on a developer platform rather than by what a postmortem would require.
Firm on the text, blind on the instrumentation
We hold the full post, so what it advises and how its code behaves are not in doubt, and the arithmetic on its own figures is straightforward. What we cannot do is check a single measurement, distinguish proxy retention from an unbounded buffer in the reported incident, or weigh this against any second account — there isn't one.