Caching Request and Response Pairs

In many traditional frameworks, the preferred strategy is to use <link> with a rel attribute of prefetch, preload or modulepreload. However, there are enough known issues that Qwik has preferred to not make link the default prefetching strategy (though it still can be configured).

Instead, Qwik prefers to use a newer approach that takes full advantage of the browser's Cache API, which is also better supported compared to modulepreload.

Cache API

The Cache API is often associated with service workers, as a way to store request and response pairs in order for an application to work offline. In addition to enabling applications to work without connectivity, the same Cache API provides an extremely powerful prefetching and caching mechanism available to Qwik.

Using the installed and activated service worker to intercept requests, Qwik is able to handle specific requests for known bundles. In contrast to the common way service workers are used, the default does not attempt to handle all requests, but rather, only known bundles generated by Qwik.

An advantage of Qwik's optimizer is it also generates a q-manifest.json file. The manifest provides a detailed module graph of not only how bundles are associated, but also which symbols are within each bundle. This same module graph data is provided to the service worker which allows for every network request for known bundles to be handled by the cache.

Dynamic Imports and Caching

When Qwik requests a module it uses a dynamic import(). For example, let's say a user interaction happened, requiring Qwik to execute a dynamic import for /build/q-abc.js. The code to do so would look something like this:

const module = await import('/build/q-abc.js');

What's important is here that Qwik itself has no knowledge of a prefetching or caching strategy. It's simply making a request for a URL. However, because we've installed a service worker, and the service worker is intercepting requests, it's able to inspect the URL and say, "look, this is a request for /build/q-abc.js! This is one of our bundles! Let's first check to see if we already have this in the cache before we do an actual network request."

This is where the power of the service worker and cache API comes in! Qwik first pre-populates the cache for modules the user may soon request within another thread. And better yet, if it's already cached, then there's no need for the browser to do anything.

Other benefits include Parallelizing Network Requests.

The challenge with the link rel approach is the lack of support on all devices, at least at the time of writing. Additionally, during development, it can be misleading that it works everywhere, while on mobile devices it is not easily visible that link prefetching is working correctly.

Prefetch is a feature that's supposed to help make our visitor's experiences faster but with the wrong combination of browser and CDN / server it can make experiences slower!

- Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation

  • Even though it's in the HTML spec, that doesn't mean your end-users are preloading your app correctly. Can I Use: modulepreload
  • Not supported by Safari. This means that for iPhone and iPad users (those who may benefit the most from prefetching), modules would not get prefetched.
  • Not supported by Firefox.

Duplicate Requests

It may be possible to fire off duplicate requests for the same resource. For example, let's say we want to prefetch module-a.js, and while that's being downloaded (which may take a short time or a very long time, we don't know), the user interacts with the app, which then decides to request and execute module-a.js. At the time of writing, browsers will often fire off a second request, which makes matters worse.

The service worker approach can avoid this by identifying a request that is already in flight, waiting on the first request for module-a.js to finish, and then cloning it for the second request. Meaning only one network request will happen, even though numerous modules and prefetches may call for the same request/response.

Made with โค๏ธ by