Skip to content

router: prefetch cache grows unbounded — no LRU/TTL on hover-prefetched HTML #1719

@glennmichael123

Description

@glennmichael123

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:

  1. 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.
  2. 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.
  3. 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

  • stx main
  • Bun 1.3.13

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions