|
| 1 | +--- |
| 2 | +title: 'Qwik 1.14: Module Preloader' |
| 3 | +authorName: 'Wout Mertens' |
| 4 | +tags: ['Web development'] |
| 5 | +date: 'April 23, 2025' |
| 6 | +canonical: 'https://qwik.dev/blog/qwik-1-14-preloader' |
| 7 | +--- |
| 8 | + |
| 9 | +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; |
| 10 | +import { DiscordLink } from '~/routes/(blog)/blog/components/mdx/discord-link'; |
| 11 | +import CodeSandbox from '~/components/code-sandbox/index.tsx'; |
| 12 | + |
| 13 | +<ArticleBlock> |
| 14 | + |
| 15 | +# Qwik 1.14 Introduces a Smarter, Simpler Preloader |
| 16 | + |
| 17 | +With the release of Qwik 1.14, we are introducing a major enhancement to our JavaScript loading strategy: **a new, smarter preloader**. This preloader ensures that JavaScript QRL segments needed by your users are downloaded before they're required, significantly improving application responsiveness. |
| 18 | + |
| 19 | +```json |
| 20 | +"@builder.io/qwik": "~1.14.0", |
| 21 | +"@builder.io/qwik-city": "~1.14.0", |
| 22 | +"eslint-plugin-qwik": "~1.14.0", |
| 23 | +``` |
| 24 | + |
| 25 | +Let’s explore what’s changed, why it matters, and how you can prepare your apps for this upgrade. |
| 26 | + |
| 27 | +## What's New: Simplified Preloading in Qwik 1.14 |
| 28 | + |
| 29 | +Previously, Qwik relied on a service worker to cache and manage JavaScript segments. This gave us precise control over the cache, but also had some drawbacks: |
| 30 | + |
| 31 | +- **Complexity**: Additional layer of service worker configuration and maintenance overhead. |
| 32 | +- **Performance Penalty**: Some delays in startup, and every fetch triggered the service worker, both of which are problematic for older devices. |
| 33 | +- **Insensitive**: The browser has more information about the user's device and network speed and can make better decisions about when to honor preload requests. |
| 34 | + |
| 35 | +In Qwik 1.14, we've transitioned away from the service worker in favor of a solution leveraging [`<link rel="modulepreload">`](https://devdocs.io/html/attributes/rel/modulepreload). This change: |
| 36 | + |
| 37 | +- Reduces complexity in deployment and maintenance. |
| 38 | +- Improves startup performance across all devices. |
| 39 | +- Eliminates unnecessary overhead during network fetches. |
| 40 | +- Leverages the browser's native preload mechanism, which is more efficient and better suited for our use case. For example, the browser can pre-parse the module before it is run, speeding up execution. |
| 41 | + |
| 42 | +This change is possible because currently 93% of browsers support [`<link rel="modulepreload">`](https://devdocs.io/html/attributes/rel/modulepreload), which wasn't the case when we started using the service worker. |
| 43 | + |
| 44 | +Rest assured, the preloader has a fallback mechanism that will work even if the browser doesn't support `modulepreload`. |
| 45 | + |
| 46 | +## Quick Recap: Qwik Segments and Why They Matter |
| 47 | + |
| 48 | +Before diving deeper, a quick reminder of Qwik QRL segments: |
| 49 | + |
| 50 | +- **QRL segments** are pieces of code identified by the Qwik Optimizer, extracted from calls like `someFunction$(...)` and JSX attributes like `someAttr$={...}`, moved into a separate file, and turned into dynamic imports. |
| 51 | +- Qwik’s resumability architecture means there isn’t one entry point for your app; instead, the tiny embedded `qwikloader` orchestrates execution based on browser events (clicks, inputs, loads, custom events, etc.). |
| 52 | + |
| 53 | +## Why Preloading Matters |
| 54 | + |
| 55 | +This approach makes Qwik extremely efficient, but only if the segments are available exactly when needed. If a segment is not loaded yet, the browser has to wait for the network request, and possible import waterfalls after that. The preloader tries to make sure this doesn't happen. |
| 56 | + |
| 57 | +When clicking, user will notice delays of 200ms or more, and it is quite easy for initial script loading to take way longer than that. |
| 58 | + |
| 59 | +Therefore, we must make sure that the segments are available before the user needs them. This is where the preloader comes in. |
| 60 | + |
| 61 | +## How the New Qwik Preloader Works (Technical Deep Dive – Optional) |
| 62 | + |
| 63 | +*(Feel free to skip ahead if you just want the practical details!)* |
| 64 | + |
| 65 | +Here's what's going on behind the scenes in Qwik 1.14: |
| 66 | + |
| 67 | +- Qwik uses a bundler (Vite) to pack segments into `build/q-*.js` files called **bundles**. These bundles are ES modules,group multiple segments, and can import both static and dynamic dependencies. |
| 68 | +- Each Qwik segment has a dynamically adjusted probability of usage. For instance, running a `component$` segment usually indicates a high probability that a related `useVisibleTask$` segment will be needed soon, while something like a `window:beforePrint$` event might rarely be preloaded. |
| 69 | +- At build time, bundles are scored based on their interactivity impact and static import dependencies (which have a 100% probability). |
| 70 | +- This information is used to create a **bundlegraph**, a compact representation of all known bundles and their interaction probabilities. |
| 71 | +- This bundlegraph also stores information about which bundles are needed to render each route, for preloading `<Link />` tags. |
| 72 | + |
| 73 | +During server-side rendering (SSR), Qwik collects the event handler segments. These are combined to find the most likely needed bundles. |
| 74 | + |
| 75 | +A small inline script, injected below the SSR HTML output, performs the following sequence: |
| 76 | + 1. Waits for the `window:load` event. |
| 77 | + 2. Requests a browser idle callback. |
| 78 | + 3. Inserts `<link rel="modulepreload">` tags for highly probable bundles. |
| 79 | + 4. Concurrently dynamically imports the Qwik preloader module itself, setting up further probability-driven preloading. |
| 80 | + |
| 81 | +Steps 1 and 2 combined ensure that the browser is focused on rendering the page. This allows the best LCP (Largest Contentful Paint) scores. |
| 82 | +Step 3 then asks the browser to preload the bundles that are most likely to be needed, while step 4 is loading the preloader module itself, which will be used later to preload other bundles. Step 3 reduces the latency for the most important bundles. |
| 83 | + |
| 84 | +Another elegant detail: the preloader module itself is dynamically imported and later reused by Qwik core, preserving state seamlessly across the loading lifecycle. |
| 85 | + |
| 86 | +Once the Qwik Core is active, it informs the preloader about newly important segments, which will update the probabilities, and trigger preloading of most likely needed bundles. |
| 87 | + |
| 88 | +The browser will therefore only download code that is actually needed, before it is needed. |
| 89 | + |
| 90 | +## How to Prepare Your App for Qwik 1.14 |
| 91 | + |
| 92 | +Here are some practical considerations for upgrading: |
| 93 | + |
| 94 | +### Service Worker |
| 95 | + |
| 96 | +The service worker is no longer used, but it needs to be unregistered for existing users. Both the qwik-city service worker and the experimental qwik prefetch service worker have been updated to do this. |
| 97 | + |
| 98 | +So do not remove the service worker registration (in `root.tsx`) from your app just yet, wait until your users have loaded your site at least once. |
| 99 | + |
| 100 | +### Cache Headers |
| 101 | + |
| 102 | +With the service worker no longer forcibly caching segments, it’s important you configure appropriate HTTP caching headers. |
| 103 | + |
| 104 | +The bundles and assets are normally served at `/build` and `/assets`, respectively. Their filenames contain a content-based hash, making them immutable. If they change, their name changes as well. You can therefore set long-lived caching headers for these. |
| 105 | + |
| 106 | +The recommended header is: |
| 107 | +``` |
| 108 | +Cache-Control: public, max-age=31536000, immutable |
| 109 | +``` |
| 110 | + |
| 111 | +You can re-add the starter for your deployment target with `npx qwik add`, which should update the deployment configuration to use the correct headers. |
| 112 | + |
| 113 | +### Caveat for Translations |
| 114 | + |
| 115 | +If your app uses [`compiled-i18n`](https://github.com/wmertens/compiled-i18n) or [`qwik-speak`](https://github.com/robisim74/qwik-speak), translated bundles (`build/[locale]/*.js`) might retain identical filenames across builds, even when translations change. Consider how long you want to cache these files for so users get the latest translations. |
| 116 | + |
| 117 | +Note that this was also a problem with the service worker, and now you have the ability to configure the cache headers for these files, so it's a positive change. |
| 118 | + |
| 119 | +### Qwik Insights |
| 120 | + |
| 121 | +If you're using Qwik Insights, you don't have to do anything. The preloader is fully compatible with it and takes its recommendations into account. |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +*We'd love your feedback—let us know how this update improves your apps!* |
| 126 | + |
| 127 | +</ArticleBlock> |
0 commit comments