How GeoLibre Implements PWA Offline Support with Workbox
GeoLibre uses vite-plugin-pwa to generate a Workbox-based service worker that precaches the lightweight app shell while runtime-caching heavy assets and basemap tiles, enabling full offline functionality after the first online visit.
GeoLibre is an open-source geospatial application built by opengeos that requires robust offline capabilities for field use. The project implements PWA offline support through a carefully architected Workbox configuration that balances initial load performance with comprehensive asset caching. This approach ensures the app remains functional without connectivity while minimizing the bandwidth required for first-time installation.
Architecture Overview
The implementation follows a strict separation between precaching (the essential app shell) and runtime caching (large, lazily-loaded assets). This split prevents the initial download from becoming prohibitively large while guaranteeing that all necessary resources are available offline after first use. A custom stale-chunk reload mechanism coordinates with the service worker lifecycle to ensure updates do not unexpectedly discard user state.
Service Worker Generation with vite-plugin-pwa
In apps/geolibre-desktop/vite.config.ts, the pwaPlugin() function returns a VitePWA configuration that integrates Workbox into the Vite build pipeline. The plugin is spread into the Vite plugins list using ...pwaPlugin().
The configuration explicitly disables automatic registration by setting injectRegister: false. This allows the application to coordinate service worker registration with custom stale-chunk reload logic in the entry point, preventing premature updates that could interrupt active user sessions.
Precaching Configuration
The precaching strategy uses globPatterns to select HTML, JavaScript, CSS, and font files for immediate caching. However, the globIgnores array—defined as HEAVY_PRECACHE_IGNORES—explicitly excludes heavy binaries like MapLibre, DuckDB-WASM, and Cesium from the initial precache. This keeps the first-visit download small while allowing these chunks to be cached on first use via runtime strategies.
/* vite.config.ts – Workbox configuration */
function pwaPlugin(): Plugin[] {
return VitePWA({
disable: PWA_DISABLED,
registerType: "autoUpdate",
injectRegister: false,
workbox: {
globPatterns: ["**/*.{js,css,html,woff,woff2}"],
globIgnores: HEAVY_PRECACHE_IGNORES,
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
navigateFallback: "index.html",
// ... runtimeCaching array
},
}) as Plugin[];
}
Runtime Caching Strategies
Workbox's runtimeCaching array defines three distinct caching strategies tailored to different asset types. Each uses the CacheFirst strategy to guarantee offline availability after the initial fetch.
Same-Origin Assets
Assets served from /assets/ (including lazy-loaded WebAssembly modules and fonts) are cached under the geolibre-assets cache name. The configuration limits this cache to 300 entries with a 30-day expiration, ensuring storage remains bounded while preserving recent assets.
{
// Same-origin assets (MapLibre, DuckDB-WASM, etc.)
urlPattern: ({ url, sameOrigin }) =>
sameOrigin && url.pathname.includes("/assets/") && /\.(?:js|css|wasm|data|woff2?)$/.test(url.pathname),
handler: "CacheFirst",
options: {
cacheName: "geolibre-assets",
expiration: { maxEntries: 300, maxAgeSeconds: 60 * 60 * 24 * 30 },
cacheableResponse: { statuses: [0, 200] },
},
}
CDN-Loaded Engines
External engines loaded from cdn.jsdelivr.net—including Pyodide, PGlite, and CereusDB—are cached under geolibre-cdn-engines. Because these URLs contain exact version hashes, a redeploy automatically generates new URLs, eliminating stale content concerns while allowing 400 cached entries.
{
// CDN-loaded engines (Pyodide, PGlite, CereusDB)
urlPattern: ({ url }) =>
url.hostname === "cdn.jsdelivr.net" &&
(url.pathname.startsWith("/pyodide/") ||
url.pathname.startsWith("/npm/@electric-sql/") ||
url.pathname.startsWith("/npm/@cereusdb/") ||
url.pathname.startsWith("/npm/gdal3.js")),
handler: "CacheFirst",
options: {
cacheName: "geolibre-cdn-engines",
expiration: { maxEntries: 400, maxAgeSeconds: 60 * 60 * 24 * 30 },
cacheableResponse: { statuses: [0, 200] },
},
}
Basemap Tiles
Basemap tiles from CORS-friendly hosts like OpenFreeMap and CARTO are cached under geolibre-basemaps with an 8000-entry limit. This enables offline map browsing after the user has visited an area while online.
{
// Basemap tiles from OpenFreeMap or CARTO
urlPattern: ({ url }) => /(?:^|\.)(?:openfreemap\.org|cartocdn\.com)$/.test(url.hostname),
handler: "CacheFirst",
options: {
cacheName: "geolibre-basemaps",
expiration: { maxEntries: 8000, maxAgeSeconds: 60 * 60 * 24 * 30 },
cacheableResponse: { statuses: [0, 200] },
},
}
Service Worker Registration and Lifecycle
The service worker registration is handled in apps/geolibre-desktop/src/main.tsx using the registerSW function imported from virtual:pwa-register.
Registration in the Entry Point
Registration is performed conditionally for web builds only; in Tauri or embedded Jupyter environments, the function executes as a no-op. The configuration opts into immediate: true registration and provides a custom onNeedReload callback that intentionally performs no action, delegating update decisions to the stale-chunk reload logic.
/* src/main.tsx – Service-worker registration */
import { registerSW } from "virtual:pwa-register";
registerSW({
immediate: true,
onNeedReload() {
// No forced reload; stale-chunk logic handles it when needed
},
onRegisterError(error) {
console.error("[GeoLibre] Service worker registration failed", error);
},
});
Stale-Chunk Reload Coordination
A separate module at apps/geolibre-desktop/src/lib/stale-chunk-reload.ts monitors for 404 errors on lazily-loaded chunks. When a chunk is removed by a redeploy, this module triggers a controlled page reload rather than allowing Workbox's default immediate activation behavior. This coordination preserves user state by ensuring reloads occur only when absolutely necessary and at non-disruptive moments.
Summary
- vite-plugin-pwa generates the Workbox service worker in
vite.config.ts, withinjectRegister: falseenabling custom registration logic. - Precaching captures the app shell (HTML, JS, CSS) while excluding heavy binaries via
HEAVY_PRECACHE_IGNORESto minimize initial download size. - Runtime caching uses CacheFirst strategies for three asset categories: same-origin assets (
geolibre-assets), CDN engines (geolibre-cdn-engines), and basemap tiles (geolibre-basemaps). - Controlled updates are managed by stale-chunk reload logic in
src/lib/stale-chunk-reload.ts, which overrides Workbox's default activation reload to prevent data loss. - The implementation enables full offline functionality after the first online visit while maintaining lightweight initial loads.
Frequently Asked Questions
Why does GeoLibre disable automatic service worker registration?
The configuration sets injectRegister: false to coordinate service worker activation with the stale-chunk reload system. This prevents Workbox from automatically reloading the page when a new service worker activates, allowing the application to preserve user state and trigger reloads only when lazy-loaded chunks are actually missing.
How does GeoLibre handle application updates without losing user data?
Instead of accepting Workbox's default immediate reload on update, GeoLibre implements a custom stale-chunk detection module at apps/geolibre-desktop/src/lib/stale-chunk-reload.ts. This module watches for 404 errors on dynamic imports and triggers a reload only when a requested chunk no longer exists, ensuring updates occur at non-disruptive moments.
What heavy assets are excluded from precaching?
The HEAVY_PRECACHE_IGNORES constant excludes large binaries such as MapLibre, DuckDB-WASM, and Cesium from the initial precache. These assets are instead cached on first use via the runtime caching strategy, keeping the initial installation under the 4MB limit defined by maximumFileSizeToCacheInBytes.
How does offline basemap tile caching work?
Basemap tiles from OpenFreeMap and CARTO domains are cached using a CacheFirst strategy with the cache name geolibre-basemaps. This stores up to 8,000 tiles for 30 days, enabling users to browse previously visited map areas without an internet connection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →