DEV Community

Peter Hallander
Peter Hallander

Posted on

I built a Shopify app that makes stores feel instant using link prefetching

I've been building small Shopify apps as side projects for a while, and one thing that's always bugged me about Shopify stores is the noticeable page load delay when navigating between products and pages.

You click a link, and there's a brief but annoying pause before anything happens. It's not catastrophic, but that friction adds up — especially on mobile connections. I decided to fix it.

The idea: prefetch on hover

The fix is conceptually simple. When a user hovers over a link, there's roughly a 200–400ms window before they actually click. That's enough time to start loading the destination page in the background, so by the time they click, the page is already partially (or fully) loaded.

This technique is called link prefetching, and libraries like instant.page and quicklink have been doing it on general websites for years. But for Shopify specifically, there was no dead-simple plug-and-play solution that works within the app ecosystem constraints. So I built one: Prefetch for Shopify.

How it works technically

Prefetch injects a small script (~2KB gzipped) into your storefront that listens for hover events and fires prefetch requests using <link rel="prefetch">.

A few nuances worth sharing:

1. Debouncing hover intent

We don't prefetch on every fleeting mouseover — that would waste bandwidth and fire constantly as users scan a page. Instead, we use a ~65ms delay to detect genuine hover intent. Fast accidental mouse movements don't count.

link.addEventListener('mouseover', () => {
  hoverTimer = setTimeout(() => prefetch(link.href), 65);
});
link.addEventListener('mouseout', () => clearTimeout(hoverTimer));
Enter fullscreen mode Exit fullscreen mode

2. Network-aware prefetching

On slow connections (2G/slow-3G), prefetching is a net negative — it would consume the limited bandwidth users need for the actual page they're on. The script checks navigator.connection.effectiveType and disables itself on slow connections automatically.

const conn = navigator.connection;
if (conn && (conn.saveData || /2g/.test(conn.effectiveType))) return;
Enter fullscreen mode Exit fullscreen mode

3. Only prefetch what matters

We limit prefetching to internal store links (same origin only), skip already-cached URLs, skip checkout/cart links, and skip static resource files. Only navigational page links get prefetched — which is what actually benefits the user.

4. Mobile: touchstart instead of mouseover

On mobile there's no hover, so we listen for touchstart instead. The moment a finger touches a link, we fire the prefetch. Given that touch-to-tap typically takes 80–150ms, this gives us a meaningful head start.

The result

In testing across several stores, this shaves 200–400ms off perceived navigation time. Google's research puts 100ms as the threshold humans perceive as "instant" — we're not magic, but closing the gap noticeably improves how snappy a store feels.

The Shopify constraint challenge

Building for Shopify is interesting because you can't modify the theme directly from an app — everything has to be injected via script tags through the App Bridge / ScriptTag API. That forced me to keep the implementation entirely self-contained with zero dependencies, no global namespace pollution, and graceful degradation if the browser doesn't support the Prefetch API.

The hardest design decision was figuring out when not to prefetch. Being too aggressive causes a spike in server requests and bandwidth consumption that can hurt slow-connection users. The threshold tuning (65ms delay, connection checks, same-origin filtering) took the most iteration.

Try it

If you run a Shopify store and care about performance, the app is free to install: Prefetch on the Shopify App Store

Happy to answer any technical questions in the comments — particularly around the Shopify scripting constraints or the prefetch threshold tuning.

Top comments (0)