DEV Community

Dominic Rockson
Dominic Rockson

Posted on

How rockzy-link's prefetching actually works under the hood

My first post was the announcement. This one is for the curious: how does rockzy-link actually make navigation feel instant? Let's go under the hood.

There are two paths, not one

Most navigation code thinks in one path: click, then fetch, then render. rockzy-link splits it in two:

  1. Prefetch path — something signals intent (hover, focus, a link entering the viewport, browser idle time, or pointerdown). The scheduler starts loading what the route will need.
  2. Navigation path — the actual click. By then the data and route chunks are usually already warm, so the navigation just runs its checks, guards, and router delegation, then renders.

The insight is boring but true: the fastest request is the one that finished before you asked for it.

Intent detection: how it knows you might click

The prefetch prop on <Link> accepts modes:

  • hover (the default) — best for high-confidence intent: menus, sidebars, expensive pages
  • viewport — starts prefetching when the link scrolls into view
  • idle — uses browser idle time, so it never competes with real work
  • none / false — off

And there's one more trick: pointerdown. When you press the mouse button, the click hasn't happened yet. That gap between press and release is a few dozen to a few hundred milliseconds of free time — rockzy-link fires a high-priority prefetch there too.

<Link to="/dashboard" prefetch="hover">
  Dashboard
</Link>

<Link to="/reports" prefetch="viewport">
  Reports
</Link>
Enter fullscreen mode Exit fullscreen mode

The prefetch scheduler

All of those signals funnel into a smart prefetch scheduler (rockzy-link/prefetch). It doesn't just fire requests blindly:

  • It dedupes prefetch requests across links and tabs, so ten links to the same route don't trigger ten fetches.
  • It stays within the browser's connection budget — only a few requests can be in flight at once, and prefetching must never starve the real navigation or the user's current page.

This is the part people miss. Prefetching isn't hard. Polite prefetching is hard.

What actually gets prefetched and cached

Prefetching a route means warming its route data: the JSON/data the page needs, or the HTML/RSC payload depending on the app. rockzy-link stores this in a smart cache with:

  • TTL — fresh data for a window you control
  • stale-while-revalidate — serve instantly, refresh in the background
  • tags — group cache entries and invalidate a whole group at once
  • mutation invalidation — when you mutate data, invalidate the tags it affects so you never render stale UI
routeCache.set(key, response, {
  ttlMs: 60_000,                // fresh for 1 minute
  staleWhileRevalidateMs: 5 * 60_000, // serve stale for 5 more, revalidate behind
  tags: ['products', 'home'],
});

// later, after a mutation:
routeCache.invalidateTag('products');
Enter fullscreen mode Exit fullscreen mode

The cache also keeps itself bounded (max 500 entries) so a long-lived tab doesn't eat memory.

What happens on click

When the click finally lands, rockzy-link runs the navigation path:

  1. Safety checks and guards — beforeNavigate hooks can cancel
  2. Router delegation — works with React Router, TanStack Router, or the built-in one
  3. Scroll and focus handling — smooth scroll, hash support, focus management for accessibility
  4. Transitions — View Transitions API support, startTransition where it helps
  5. Offline — if the network is down, it serves the cached route instead of a blank page, and queues mutations for later

Then onSuccess fires. If everything was prefetched, steps 1-4 are the only real cost — and navigation feels instant because the data fetch, the slow part, already happened.

The fastest possible path

If you just want raw speed with no router ceremony:

import { runtime } from 'rockzy-link';

await runtime.navigate("/dashboard", {
  router,
  scroll: false,
  announce: false,
  focus: false,
});
Enter fullscreen mode Exit fullscreen mode

Why this matters more than bundle size

For the record: the prefetch scheduler is ~4.24 KB brotli, and core + link is ~2.70 KB. Small. But size isn't the point. Perceived performance is about when work happens, not how much work there is. Move the fetch earlier — to the moment of intent instead of the moment of click — and the same app feels like a different app.

Try it out — it's live on Product Hunt today:

npm install rockzy-link
Enter fullscreen mode Exit fullscreen mode

Top comments (0)