In today's fast-paced digital world, we often take connectivity for granted. We assume that our devices will always be online, allowing us to access the web and use apps without interruption. However, there are many situations where a reliable internet connection is not available, such as remote areas or during network outages. This is where building offline-capable apps becomes crucial.
If you are a big fan of Qwik, you may see many people discuss about PWA offline mode with Qwik.
In this article, I'll share my experience of building an offline app with Qwik, a JavaScript framework designed for instant-loading web apps. My app is named CueX.
CueX Teleprompter
Welcome back to Dev Success 101, and I'm Kim. CueX is a free online teleprompter that runs on smartphones, tablets, and desktops, making it your ideal companion for smooth and professional script delivery. It is built with Qwik and TailwindCSS.
I aim to make CueX installable, with an app icon on the home screen, and usable as a native app on mobile/tablet, even offline. That's why it is a PWA.
I attempted to use the Vite PWA plugin to generate a service worker for PWA. This plugin offers various features for PWAs, such as:
- Generating manifest.json
- Generating a service worker
- Auto-update
- Workbox, etc.
However, it didn't work as expected. I had to set up PWA offline mode by integrating Workbox manually.
Why not Vite PWA plugin?
Vite PWA plugin was my initial choice. Following the plugin's instructions, I installed it using:
yarn add -D vite-plugin-pwa
Subsequently, I updated vite.config.js
as follows:
import { defineConfig } from "vite";
import { qwikVite } from "@builder.io/qwik/optimizer";
import { qwikCity } from "@builder.io/qwik-city/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { partytownVite } from "@builder.io/partytown/utils";
import { join } from "path";
import { VitePWA } from 'vite-plugin-pwa' export default defineConfig(() => { return { plugins: [ qwikCity(), qwikVite(), tsconfigPaths(), partytownVite({ dest: join(__dirname, "dist", "~partytown") }), // Add VitePWA plugin: VitePWA({ manifest: false, registerType: 'autoUpdate', injectRegister: 'script', // injectRegister need to register service worker by adding the script into `root.tsx` file: // <script src="/registerSW.js"></script> workbox: { globPatterns: ['**/*.{js,css,ico,png,jpg,svg}'] } }), ], preview: { headers: { "Cache-Control": "public, max-age=600", }, }, };
});
Any files matching the globPatterns
regex will be cached into the cache storage (note that globPatterns
is only for files, not routes).
Unfortunately, I encountered the following error:
Uncaught (in promise) non-precached-url: non-precached-url :: [{"url":"index.html"}]
I attempted to fix this error using some workarounds mentioned in this issue, such as:
// ... workbox: { globPatterns: ['**/*.{js,css,ico,png,jpg,svg}'], // Don't fallback on document based (e.g. `/some-page`) requests // Even though this says `null` by default, I had to set this specifically to `null` to make it work navigateFallback: null, }
After applying this fix, the error was resolved.
Checking the generated file sw.js
(at the end of the file):
- Before fix:
// ...
e.cleanupOutdatedCaches()
e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))}));
- After fix:
// ...
e.cleanupOutdatedCaches()}));
The error was eliminated because the PWA plugin didn't register the Workbox Navigation Route to /index.html
. This means the app didn't precache any route into the cache storage, leading to no offline mode.
I modified the Workbox config from navigateFallback: null
to navigateFallback: '/'
with hope. I thought Workbox would create a navigation route /, and then the offline mode would work. However...
There is no way to register a navigation route manually when using the Vite PWA plugin. Therefore, I believe I need to create a service worker manually, without the Vite PWA plugin.
Building an Offline App with Qwik (Configuring Workbox Manually)
Getting started with this approach is quite straightforward. Qwik has already defined a service worker in the file /src/routes/service-worker.ts
, so we can write our code in that file.
As a first step, I will need to install additional dependencies to use Workbox:
yarn add workbox-precaching workbox-routing workbox-strategies
Next, I precache the necessary routes for offline mode. For example, the home page /
, logo images, and other assets for which I already know the exact URL.
const revision = import.meta.env.VITE_GIT_COMMIT_HASH; precacheAndRoute([ { url: "/", revision }, { url: "/icon/icon-192x192.png", revision }, { url: "/icon/icon-256x256.png", revision }, { url: "/icon/icon-384x384.png", revision }, { url: "/icon/icon-512x512.png", revision }, { url: "https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js", revision: "1.6.0", },
]); cleanupOutdatedCaches(); // ...
The application also requires CSS, images, and other assets for which I don't know the exact URL. I use workbox-routing to intercept network requests for those files and cache them.
registerRoute(new NavigationRoute(createHandlerBoundToURL("/")));
registerRoute(new NavigationRoute(createHandlerBoundToURL("/?pwa=true"))); // intercept network requests of CSS, Image
registerRoute( ({ request }) => request.destination === "style" || request.destination === "image", new CacheFirst(),
);
There's no need to intercept network requests for JS files because Qwik's service worker already handles that.
Now, the app is ready for offline use!
- Live demo: https://cuex.devsuccess101.com
- Source code: https://github.com/devsuccess101/cuex-teleprompter
Conclusion
In conclusion, while I faced challenges with the Vite PWA plugin, the manual configuration of Workbox enabled the seamless implementation of offline mode for the Qwik app. Don't forget to subscribe to the channel for more updates on Qwik and web development tips. Happy coding!