# GeoLibre PWA Caching Strategies for Offline Functionality

> Discover GeoLibre's PWA caching strategies. Learn how its three-layer architecture with vite-plugin-pwa and Workbox enables full offline geospatial functionality efficiently.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: deep-dive
- Published: 2026-07-31

---

**GeoLibre implements a three-layer caching architecture using vite-plugin-pwa and Workbox, combining app-shell precaching with runtime CacheFirst rules to enable fully offline geospatial operations without directly writing to the Cache Storage API.**

GeoLibre, developed by the opengeos organization, delivers desktop-grade geospatial capabilities through a Progressive Web App (PWA) architecture that functions entirely offline. The application's caching system is orchestrated through a service worker generated by vite-plugin-pwa, which manages three distinct storage layers: the application shell, computational assets, and basemap tiles.

## App-Shell Precaching Strategy

The first layer ensures the user interface renders instantly upon loading. During the service worker installation phase, GeoLibre precaches the essential bootstrap resources required to display the application shell.

In [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts), the `VitePWA` plugin configuration defines glob patterns that capture the HTML entry point, static JavaScript and CSS bundles, fonts, and UI images. These assets are revision-hashed and stored using Workbox's precaching mechanism, ensuring users always receive the latest version of the application shell while maintaining offline availability.

```typescript
// vite.config.ts - Precache configuration excerpt
VitePWA({
  workbox: {
    globPatterns: ['**/*.{html,js,css,woff2,png,svg}'],
    // Additional precache options...
  }
})

```

## Runtime Caching for Assets and Engines

The second layer handles large, lazily-loaded computational resources using a **CacheFirst** strategy. This approach stores heavy assets after their first network fetch, serving subsequent requests directly from the cache.

The configuration in [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts) (lines 96-135) defines two specific runtime caching rules:

- **geolibre-assets**: Caches bundled chunks from `/assets/` and WebAssembly blobs
- **geolibre-cdn-engines**: Caches external binaries including Pyodide, PGlite, CereusDB, and GDAL distributions

Both rules implement expiration policies limiting storage to 30 days or 300–400 entries, preventing cache bloat while maintaining offline functionality for essential computational engines.

```typescript
// vite.config.ts - Runtime caching for assets
{
  urlPattern: /^https:\/\/.*\/assets\/.*/i,
  handler: 'CacheFirst',
  options: {
    cacheName: 'geolibre-assets',
    expiration: {
      maxEntries: 400,
      maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days
    }
  }
}

```

## Runtime Caching for Basemap Tiles

The third layer manages dynamic geospatial visualization data through **CacheFirst** rules targeting CORS-friendly basemap providers. This strategy enables offline map viewing by storing vector tiles, raster tiles, style JSON, sprites, and glyphs.

Configured in [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts) (lines 140-158), the `geolibre-basemaps` cache captures all requests to `openfreemap.org` and `cartocdn.com` with generous limits of 8,000 entries and 30-day expiration. The service worker matches these URLs using hostname regex patterns, allowing any region downloaded by the user to persist for offline use.

```typescript
// vite.config.ts - Basemap tiles caching rule
{
  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] }
  }
}

```

## Offline Region Download Workflow

GeoLibre implements an indirect caching mechanism that deliberately avoids direct Cache Storage API manipulation. Instead, the application uses standard `fetch()` requests that pass through the service worker, allowing Workbox to handle storage according to its predefined rules.

This architecture is implemented in [`apps/geolibre-desktop/src/lib/offline-tiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/offline-tiles.ts). When users select a geographic region for offline use, the system enumerates every required tile URL—including style assets and glyphs—and issues ordinary fetch requests. The service worker intercepts these requests and stores responses via the `geolibre-basemaps` CacheFirst route.

The core functions driving this process include:

- **`planOfflineZoom`** - Resolves the zoom range for download, respecting the map's maximum zoom constraints
- **`collectOfflineUrls`** - Gathers tile-template URLs and style assets, deduplicating entries
- **`warmUrls`** - Performs bounded-concurrency fetches (default 8 concurrent requests) with progress reporting and failure collection
- **`hasActiveServiceWorker`** - Verifies service worker control before initiating downloads

```typescript
// offline-tiles.ts - Cache warming implementation
export async function warmUrls(
  urls: string[],
  options: {
    concurrency?: number;
    timeoutMs?: number;
    onProgress?: (progress: WarmProgress) => void;
  }
) {
  const { concurrency = 8, timeoutMs = 15000, onProgress } = options;
  
  // Fetch implementation that passes through service worker
  const fetchWithTimeout = (url: string) => 
    Promise.race([
      fetch(url),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), timeoutMs)
      )
    ]);

  // Process with bounded concurrency and progress tracking...
}

```

## Service Worker Activation and Testing

GeoLibre configures the service worker with `injectRegister: false` to enable manual registration control, ensuring the application determines when caching behavior initiates. The worker is configured with `clientsClaim: true` and skip-waiting behavior to immediately assume control of pages without requiring multiple reloads.

The end-to-end test suite in [`e2e/pwa.spec.ts`](https://github.com/opengeos/GeoLibre/blob/main/e2e/pwa.spec.ts) validates the complete offline architecture. Tests verify that after initial page load, the service worker activates correctly, caches map assets, and successfully boots the application during subsequent offline reloads.

## Key Source Files

- **[`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts)** - Service worker generation, precache configuration, and Workbox runtime caching strategies
- **[`apps/geolibre-desktop/src/lib/offline-tiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/offline-tiles.ts)** - Tile enumeration, URL collection, and cache warming logic
- **[`e2e/pwa.spec.ts`](https://github.com/opengeos/GeoLibre/blob/main/e2e/pwa.spec.ts)** - End-to-end tests validating PWA offline functionality
- **[`apps/geolibre-desktop/src/lib/offline-regions.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/offline-regions.ts)** - Metadata management for cached regions

## Summary

GeoLibre's PWA offline functionality relies on a sophisticated three-tier caching system:

- **Precached App Shell** - HTML, JavaScript, CSS, and fonts stored at install time for instant loading
- **Asset Caching** - CacheFirst strategy for WASM binaries and engine files with 30-day expiration
- **Basemap Caching** - CacheFirst strategy for tiles with 8,000 entry capacity, populated indirectly through fetch requests
- **Indirect Storage** - Region downloads use standard fetch calls rather than direct Cache API writes, allowing Workbox to manage all cache entries
- **Manual SW Control** - Custom registration and immediate client claiming ensure offline availability after first visit

## Frequently Asked Questions

### How does GeoLibre store map tiles for offline use without using the Cache Storage API directly?

GeoLibre employs an indirect caching mechanism where the [`offline-tiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/offline-tiles.ts) module enumerates all required tile URLs for a selected region and issues standard `fetch()` requests. These requests pass through the Workbox-generated service worker, which applies the `geolibre-basemaps` CacheFirst strategy to store responses. This design ensures the generated service worker remains the sole cache writer, maintaining consistency with the runtime caching rules defined in [`vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/vite.config.ts).

### What is the maximum storage duration for offline basemap tiles?

The basemap tile cache uses a 30-day expiration policy with a maximum of 8,000 entries, as configured in the `geolibre-basemaps` runtime caching rule. This applies to tiles from `openfreemap.org` and `cartocdn.com`, ensuring downloaded regions remain available for offline viewing for one month before automatic eviction.

### How does GeoLibre handle large computational assets like Pyodide and GDAL for offline use?

Large engine binaries and WASM files are cached using separate `CacheFirst` runtime routes named `geolibre-assets` and `geolibre-cdn-engines`. These routes store files after the first network request with expiration limits of 30 days or 300–400 entries, allowing complex geospatial processing to function offline after initial load.

### Why does GeoLibre require an active service worker before allowing offline downloads?

The `hasActiveServiceWorker()` check ensures the service worker controls the current page before initiating download operations. This verification is necessary because the cache warming process relies on fetch requests being intercepted by the service worker; without active control, requests would bypass the Workbox routes and fail to populate the offline caches.