Prefetching Modules

Qwik provides various strategies to prefetch modules ahead of time. This page describes the low-level features of Qwik's prefetching.

Prefetching modules allows applications to start downloading necessary code in the background before users actually need it. The ideal solution is to prefetch only the smallest amount of relevant code that is highly likely to be executed from a user's interaction, while also avoiding any JavaScript that will not be used.

Qwik applications excel at downloading and executing a minimal amount of JavaScript, optimizing resource use and performance. By understanding how individual components are used or not used, Qwik can effectively decide which bundles should be prefetched. This targeted approach ensures that only necessary code is loaded.

Remember, the key difference between resumability and hydration, is that resumability enables Qwik applications to avoid executing JavaScript just to restore the event listeners, component tree, and application state. By fundamentally breaking apart a component's event listeners, render function, and state, the amount of code to prefetch is significantly smaller compared to traditional approaches.

Collecting Used Symbols

When Qwik renders an app, it's able to collect which "symbols" were used during the render. A symbol includes various parts of a component, which are extracted by the optimizer to break apart the application. Individual event listeners, component state, and the component renderer itself are examples of different symbols that could be extracted.

For example, consider a product page that is mostly static except for one "Add to cart" button. When the button is clicked, the user should immediately get feedback to show the product added to the cart. In this example, the Qwik optimizer would be able to understand that the only symbol a user could interact with is the "Add to cart" button click event listener.

For our "Add to cart" example, the optimizer collects the symbols only for the click event listener and the renderer for the add to cart widget. There is no need to download, hydrate, and re-render any other parts of the application that aren't relevant. This demonstrates Qwik's capability to determine which interactions are possible and to prefetch only the necessary code for the event listener. In contrast, traditional approaches require the entire application or route, including framework code, to be prefetched just to add the click event listener.

Prefetch Strategy

The prefetching strategy is the logic that decides which JavaScript, if any, Qwik should prefetch in the background. By default, Qwik will prefetch any visible listeners on the page. To configure the prefetching strategy, use the options argument of the renderToStream() function, often found in the src/entry.ssr.tsx source file. Providing optimal prefetching strategies is a continual commitment of Qwik.

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      // custom prefetching config
    },
    ...opts,
  });
}

Implementation

Browsers offer numerous ways to implement a prefetching strategy. Qwik can be configured to prefer one implementation over another, each with pros and cons. Depending on this configuration, the generated HTML content will include the chosen prefetch implementation.

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      implementation: {
        // custom prefetching implementation
      },
    },
    ...opts,
  });
}
OptionDescription
prefetchEventDispatch a qprefetch event with detail data containing the urls that should be prefetched. The event dispatch script will be inlined into the document's HTML. By default, the prefetchEvent implementation will be set to always.
linkInsertInsert the <link> element into the document. When using html-append, it will render each <link> directly within the html, appended at the end of the body. Using the js-append option, it will instead insert some JavaScript, which creates the elements at runtime and appends them at the end of the body.
linkRelThis option is used to define the rel attribute of the <link> element. When the linkInsert option is used, the default is prefetch. Other options include preload and modulepreload.
workerFetchInsertPrefetch urls by calling a fetch() for each module, with the goal of populating the network cache.

Dispatched Prefetch Event

Speculative Module Fetching is the preferred caching strategy. This strategy listens for the qprefetch event, which is dispatched by the Qwik framework. The event contains a list of URLs that the background thread should use to pre-populate the browser's Cache.

Qwik should be configured to use the prefetchEvent implementation, which will dispatch a qprefetch event. By default, the prefetchEvent implementation will be set to always. Next, Speculative Module Fetching will listen for this event and communicate with its service worker to persist the Request / Response object pairs so they are cached in long-lived memory.

By using a service worker to intercept fetch requests from the browser, this approach allows granular control over caching, along with preventing duplicate requests for the same resource.

Below is an example of manually dispatching the event. These events are dispatched from Qwik itself and do not require developers to dispatch these events manually. Additionally, the service worker will automatically add listeners for these events.

dispatchEvent(new CustomEvent("qprefetch", { detail: {
  bundles: [...]
}}));

Using the <link> element with the rel attribute is a common approach by today's frameworks, and Qwik can use this method by configuring the linkInsert and linkRel options. The link rel approach, although effective, currently faces a lack of support on all devices, at least at the time of writing. Additionally, during development, it can be misleading to assume that it works everywhere, since prefetching on mobile devices is not easily visible.

For example, Safari does not support modulepreload. This is significant because mobile devices may benefit the most from module preloading. Similarly Firefox does not support link rel prefetch when on https.

Prefetch is a feature designed to enhance the speed of our visitors' experiences. However, the effectiveness can vary depending on the combination of browser and CDN/server used, highlighting the importance of an optimized setup to ensure the best performance.

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

Additionally, it's possible for multiple requests for the same resource. For example, let's say we want to prefetch module-a.js, and while it's downloading regardless of how long it takes, the user interacts with the app. The app then decides to actually request and execute module-a.js. At the time of this writing, browsers will often fire off a second request making matters worse.

  • 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 Firefox.

Web Worker Fetch

workerFetchInsert instructs Qwik to employ a web worker to fetch() a JavaScript file, with the goal of priming the browser cache with the module. By using a web worker, the fetch and caching logic lives on another thread. The fetch response will also have an immutable or long cache-control header, so the browser doesn't make a second network request.

The downside of this setting is that the fetched response is thrown away, and it's only at the browser level that hopefully the file is cached.

Frequently Asked Prefetching Questions

QUESTION: Is lazy loading on user events slow because the user must wait for the code to download?

Yes, that would create a noticeable delay, especially on slow networks. This is why code prefetching is an important part of Qwik applications.

Prefetching code ensures that all of the necessary code for running the application is fetched immediately on navigating to the page. This way, when the user performs an action, the code for that action comes from the prefetch cache rather than the network. The result is that the code execution is instant.

QUESTION: Doesn't code prefetch result in the same behavior as existing frameworks that download and execute all of the code eagerly?

No, for several reasons:

  • Existing frameworks must download and execute all of the code (hydration) before the application can be interactive. Typically the download of the code is a smaller time cost than the execution of the code.
  • Qwik code prefetch only downloads but does not execute the code. Therefore even if Qwik prefetches the same amount of code as the existing frameworks, the result is significant time cost savings.
  • Qwik only prefetches the code which is needed for the current page. Qwik avoids downloading code associated with components that are static. In scenarios where more code needs prefetching, Qwik still only reaches the volume that existing frameworks consider their best case. In most cases, Qwik prefetches a small fraction of code compared to the existing frameworks.
  • Prefetching of code can happen on other threads than the main thread. Many browsers can even pre-parse the AST of the code off the main thread.
  • If the user interaction happens before the prefetch is completed, the browser will automatically prioritize the interaction chunk before the remaining prefetch chunks.
  • Qwik can break up the application into many small chunks, and these chunks can be downloaded in the order of probability that the user will interact with them. Existing frameworks have trouble breaking up applications into small chunks, and there is no easy way to prioritize the chunk download order because hydration requires a single "main" entry point to the application.

QUESTION: Who is responsible for knowing what code to prefetch?

Qwik can automatically generate the prefetch instructions as part of the SSR rendering. By executing the application, Qwik has runtime knowledge of which components are visible, which events the users can trigger and what code will need to be downloaded. The result is that the prefetch is an ideal set of files for this page. No action on the developers' part is required other than adding the prefetching strategy to renderToStream().

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • adamdbradley
  • RATIU5
  • manucorporat
  • literalpie
  • saikatdas0790
  • the-r3aper7
  • mhevery
  • mrhoodz
  • thejackshelton
  • maiieul
  • jemsco