Caching Request and Response Pairs
In many traditional frameworks, the preferred strategy is to use
<link> with a
rel attribute of
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).
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.
Known Issues with link
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!
- 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.
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.