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));
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;
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)