Prefetching

Prefetching is a way for applications to begin downloading modules in a background task, before the user may actually require these modules. The ideal solution is to only prefetch the smallest amount of code that is highly likely to be executed from a user's interaction, but avoiding any JavaScript that's couldn't be used.

Only downloading and executing a minimal amount of JavaScript is an area where Qwik applications excel. And since Qwik is able to understand how individual components are used (and what's not used), it can also best decide which bundles should be prefetched.

Remember, the difference between resumability and hydration, is that resumability allows 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 also significantly smaller than from a traditional approach.

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 in order 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, a product page may have a click listener on the "Add to cart" button, and when this 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 would collect the symbols for only the click event listener, and the renderer for the add to cart widget. It would not, however, have to download, hydrate, and re-render any of the other parts of the application, since it's not even possible to re-render other sections of the page.

Since Qwik understand what's possible, it's able to prefetch the code only for the event listener rather than all the JavaScript for the entire application or route. This is in contrast to the traditional approach, where prefetching is used to load the entire application or route, and framework code, just in order for the application to add the click event listener.

Prefetching Strategy

The prefetching strategy is the logic which 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 an area Qwik will continue to research and experiment with.

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

Prefetching Implementation

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

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      implementation: {
        // custom prefetching implementation
      },
    },
    ...opts,
  });
}
OptionDescription
prefetchEventDispatch a qprefetch event with detailed data containing the urls that should be prefetched. The event dispatch script will be inlined into the document's HTML.
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 that it populate's the network cache.

Request/Response Cache and Service Workers

The preferred caching strategy used by Qwik City, is to use a Service Worker to populate the browser's Cache. Qwik itself should be configured to use the prefetchEvent implementation, which will dispatch a qprefetch event. Next, Qwik City 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 fine-grain caching control, along with preventing multiple-requests for the resource.

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 challenge with the link rel approach is 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.

For example, Safari (the browser powering iPhones and iPads) does not support modulepreload. This is significant because mobile devices maybe benefit the most from module preloading. Next is Firefox, which does not support link rel prefetch when on https.

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 actually make experiences slower!

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

Additionally, it may be possible to fire off a multiple 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), the user interacts with the app, which then decides to actually request and execute module-a.js. At the time of writing, browsers will often fire off a second request, which makes matters worse.

Web Worker Fetch

A prefetching approach that can work is using 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 logic lives on another thread. Next, the fetch response should have an immutable or long cache-control header, so the browser doesn't make a second network request.

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

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 3G 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 results 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 the worst case, Qwik prefetches the same amount of code as the existing frameworks' 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 configuring the renderToStream() with prefetching strategy.

Made with โค๏ธ by