Summary
The SPA router's prefetch cache (packages/router/src/client.ts ~line 85) stores full HTML strings keyed by URL. Hovering over many distinct [data-stx-link] elements (or programmatic prefetches via the prefetch: API at line 803) populates the cache without bound. There's no LRU policy, no TTL, no max-size — the only cleanup is the explicit clearCache() API which nothing in the router calls automatically.
Reproducer
<!-- Page with a sidebar of 200 internal links -->
<nav>
<StxLink to="/page-1" prefetch>Page 1</StxLink>
<!-- ... 199 more ... -->
</nav>
Hover slowly down the list (each hover triggers prefetch). Open DevTools memory profiler:
- Observed:
cache, layoutCache, layoutGroupCache retain ~200 entries indefinitely. For a typical page (~50 KB HTML), that's ~10 MB held forever.
- Expected: Some bounded retention policy.
Realistic real-world trigger: a long-running SPA session where the user hovers over many table rows / sidebar items / search results, each of which is an SPA link with prefetch.
Root cause
packages/router/src/client.ts ~line 85, ~694-712:
var cache={}, layoutCache={}, layoutGroupCache={};
var prefetching={};
// ... later, on hover:
if (cache[key] || prefetching[key]) return;
prefetching[key] = true;
fetch(url, /* ... */)
.then(/* parse */)
.then(function(result) {
if (o.cache) {
cache[key] = result.html; // never removed
layoutCache[key] = result.layout; // never removed
layoutGroupCache[key] = result.layoutGroup;
}
})
.catch(function(){})
.finally(function() { delete prefetching[key] });
There's a clearCache() exposed on window.__stxRouter (line 810) but nothing inside the router invokes it.
Impact
Long-running tabs (dashboards, admin tools, anywhere users dwell for hours) bloat memory. Especially bad with view transitions enabled because the cached HTML is held alongside in-flight transition state.
Suggested fix
Pick one:
- LRU with configurable max (recommended):
prefetchCacheMaxSize: 50 (default). When cache size exceeds the limit, evict the LRU entry. Default size 50 is generous; users with many links can tune up.
- TTL on prefetched entries: prefetched (but never navigated-to) entries expire after N minutes. Navigated entries can stay until the user leaves the page they came from.
- At minimum, clear on full document swap: cache entries can be purged on every layout-group change, since the new page's links will re-prefetch as needed.
Workaround
Periodically call window.__stxRouter.clearCache() from a custom hook.
Environment
Summary
The SPA router's prefetch cache (
packages/router/src/client.ts~line 85) stores full HTML strings keyed by URL. Hovering over many distinct[data-stx-link]elements (or programmatic prefetches via theprefetch:API at line 803) populates the cache without bound. There's no LRU policy, no TTL, no max-size — the only cleanup is the explicitclearCache()API which nothing in the router calls automatically.Reproducer
Hover slowly down the list (each hover triggers prefetch). Open DevTools memory profiler:
cache,layoutCache,layoutGroupCacheretain ~200 entries indefinitely. For a typical page (~50 KB HTML), that's ~10 MB held forever.Realistic real-world trigger: a long-running SPA session where the user hovers over many table rows / sidebar items / search results, each of which is an SPA link with
prefetch.Root cause
packages/router/src/client.ts~line 85, ~694-712:There's a
clearCache()exposed onwindow.__stxRouter(line 810) but nothing inside the router invokes it.Impact
Long-running tabs (dashboards, admin tools, anywhere users dwell for hours) bloat memory. Especially bad with view transitions enabled because the cached HTML is held alongside in-flight transition state.
Suggested fix
Pick one:
prefetchCacheMaxSize: 50(default). When cache size exceeds the limit, evict the LRU entry. Default size 50 is generous; users with many links can tune up.Workaround
Periodically call
window.__stxRouter.clearCache()from a custom hook.Environment