惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
量子位
Recent Announcements
Recent Announcements
T
The Blog of Author Tim Ferriss
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Blog — PlanetScale
Blog — PlanetScale
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - Franky
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
V
Visual Studio Blog
博客园 - 聂微东
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
U
Unit 42
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
L
LangChain Blog

TanStack Blog

TanStack + Vercel Partnership | TanStack Blog TanStack AI Enters the RC Phase | TanStack Blog Inside a TanStack Router Navigation | TanStack Blog Form v2 is here: All you need to know about the alpha | TanStack Blog Announcing TanStack Table V9 | TanStack Blog TanStack Has a New Look | TanStack Blog Introducing TanStack Markdown and TanStack Highlight | TanStack Blog We Removed React Server Components from TanStack.com | TanStack Blog We Stopped Using RSC on TanStack.com | TanStack Blog Inside TanStack Table V9 Reactivity | TanStack Blog Run Any Coding Agent in a Sandbox, With One chat() Call | TanStack Blog TanStack Start and TanStack AI Win 2026 Open Source Awards | TanStack Blog How an Underrated Refactor Saved 90% Memory Usage | TanStack Blog TypeScript Performance in TanStack Table V9 | TanStack Blog TanStack AI Beta: The Switzerland of AI Tooling Grows Up | TanStack Blog TanStack Table V9: Taking Form | TanStack Blog TanStack AI: Your MCP, your way | TanStack Blog TanStack Start Adds First-Class Rsbuild Support | TanStack Blog Introducing Experimental Workflows and Orchestrators in TanStack AI | TanStack Blog Chat UIs Are Lists Until They Aren't | TanStack Blog Structured Output That Remembers Across Turns | TanStack Blog TanStack AI now fully speaks AG-UI | TanStack Blog Stop Waiting on JSON: Stream Structured Output with One Schema | TanStack Blog Hardening TanStack After the npm Compromise | TanStack Blog Postmortem: TanStack npm supply-chain compromise | TanStack Blog Who Owns the Tree? RSC as a Protocol, Not an Architecture | TanStack Blog TanStack AI Just Learned to Compose Music | TanStack Blog Your AI Tool Calls Should Fail at Compile Time, Not in Production | TanStack Blog One Flag, Every Chunk: Debug Logging Lands in TanStack AI | TanStack Blog How We Test TanStack AI Across 7 Providers on Every PR | TanStack Blog
TanStack Virtual just got a lot faster, and finally handl...
Tanner Linsley · 2026-05-19 · via TanStack Blog

by Tanner Linsley on May 19, 2026.

I spent three days last week auditing TanStack Virtual end-to-end, and what came out of it is the biggest single perf release the library has shipped in years. Cold mount on a 100k-item list dropped from 6.1 ms to 4.5 ms in real React. A worst-case resizeItem storm on 10k items went from nearly two seconds to 1.3 milliseconds. iOS Safari momentum scroll, which had been broken for years on dynamic-height lists, now actually works. Scroll-up jank with dynamic items, the single largest complaint cluster in our tracker, is gone by default.

The work was a mix of bug fixes, a substantial internal rewrite for the hot path, and a new iOS-specific code path. Most of it landed in virtual-core so every framework adapter benefits. Here's what changed and why.

Before measuring anything I read the entire virtual-core source looking for things that were quantifiably bad, and the worst one was a Map clone hiding in plain sight. Every time resizeItem ran, we'd do this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size)), which copies the whole size cache into a fresh Map just to invalidate a memo dep. For a 10k-item list where every item resizes once on mount, that's about 50 million wasted operations and a 1.9-second cold mount that nobody had pinned down. The fix was four lines (use a version counter, same dep pattern, integer comparison) and dropped that to 1.3 milliseconds. 1382× faster.

Below it were the usual smaller suspects: an Object.entries+delete pattern in setOptions that was triggering V8's dictionary-mode deopt on every render, a Math.min(...arr) spread that could blow the argument-list limit at 125k items, an elementsCache leak when React replaced a measured node, a useReducer(() => ({}), {}) rerender pattern allocating per scroll event. None catastrophic alone, but together they explain why our issue tracker had recurring complaints about scroll stutter and slow initial renders on large lists.

After the audit fixes we still mounted a 100k-item list slower than we should have, and the cause was that we were allocating a VirtualItem object per index even though only ~50 are ever visible. The fix is the biggest single change in the release.

For single-lane lists (the default and the common case) we now store start and size as a flat Float64Array and only construct VirtualItem objects when something actually reads measurements[i]. The public API still hands out an Array<VirtualItem> shape, but it's a Proxy that materializes lazily and caches. Internal hot paths read straight from the typed array, skipping the Proxy.

Cold mount at 100k went from 6.1 ms to 4.5 ms in real React, and 2.5 ms to 0.54 ms in the synthetic bench. At 500k items it's now 2.7 ms instead of 14. The work is fully backward compatible: measurementsCache still satisfies its Array<VirtualItem> contract, internal consumers continue to read [i].start and [i].end the same way they used to, and only the lanes>1 path keeps the old eager allocation because lane assignment is order-dependent and harder to defer cleanly.

If you've ever called el.scrollTop = x during a momentum scroll on iOS Safari, you know what happens: momentum dies, page snaps, user sees a jolt. iOS WebKit treats any programmatic scrollTop write during a touch-driven scroll as a cancel instruction, which is the opposite of what virtualization libraries want to do, because virtualization libraries write scrollTop in response to size measurements arriving.

We had no iOS-specific handling at all. The "scroll stops abruptly when content above me resizes" complaints in our tracker have been some flavor of this for years.

The fix defers the scrollTop write while a finger's on the screen, during the 150 ms post-touchend momentum window, and during the elastic-overscroll bounce. The accumulated adjustment flushes in a single write once everything actually settles, and the user keeps their momentum. About 370 bytes of iOS-specific code that doesn't tree-shake away on non-iOS bundles since the detection is runtime, but the per-event cost on non-iOS is one cached boolean check. That's an acceptable trade given how much of mobile traffic is iOS.

The biggest single complaint cluster in our issue tracker is "items jump while I scroll up" with dynamic heights, and the cause is that we were writing scrollTop on every above-viewport resize to keep the visible window stable. That makes sense during forward scroll, but during backward scroll the same write actively pushes the user past where they're trying to go. The community had independently rediscovered the same workaround five separate times across the years.

We just gate it on direction now. Forward scroll and mount-time adjustments still fire, backward scroll skips them. Anyone who wants the old behavior can supply shouldAdjustScrollPositionOnItemSizeChange (it was already there) and ignore the direction.

virtualizer.takeSnapshot() returns the currently-measured items as plain VirtualItem objects, suitable for persisting through state storage and feeding back as initialMeasurementsCache on remount. Pair with the current scrollOffset and you get exact scroll restoration after route navigation:

Only items the consumer actually rendered show up in the snapshot, since unmeasured items can fall back to estimateSize on restore.

Compared to the current published version:

MetricBeforeAfter
Cold mount @ 100k items (real React)6.1 ms4.5 ms
Cold mount @ 100k items (synthetic)2.5 ms0.54 ms
Cold mount @ 500k items (synthetic)14 ms2.7 ms
resizeItem storm on 10k items1.9 s1.3 ms
setOptions × 10,000 (per render)14.4 ms1.3 ms
scrollToIndex landing accuracy on dynamic 10k listswithin 1 px0.0 px
iOS Safari momentum scrollbrokenworks
Backward-scroll jank with dynamic itemsrecurringgone by default

Bundle delta is about +900 bytes gzip, mostly the lazy fast-path machinery and the iOS code. Production minified comes out around 6.1 kB total. 91 unit tests, all green.

Reverse infinite scroll for chat use cases is the one big thing missing, and given how much of the modern web is now a streaming UI on top of a list, it deserves its own release with its own design pass rather than getting wedged into this one. A Fenwick-tree memory rewrite for 1M+ item lists is the other piece; it'll come if a real-world case actually asks for it.

I also built a cross-library benchmark suite at benchmarks/ while I was at it, since I wanted to verify my own changes didn't regress anything and the existing comparison content online is either stale or contradictory. It runs the same scenarios across every major virtualization library via Playwright, reports medians across runs, and is fully reproducible: cd benchmarks && pnpm bench. The bench is in the repo if you want to see it.