How TREK Implements Offline PWA Mode with Service Worker Caching

TREK enables offline functionality by registering a Service Worker through vite-plugin-pwa that caches map tiles, CDN libraries, and user uploads using Workbox strategies, while storing structured trip data in IndexedDB for access without network connectivity.

TREK is an open-source trip planning application that transforms into a fully functional Progressive Web App (PWA) through sophisticated Service Worker caching. By combining Workbox runtime caching strategies with proactive tile prefetching and IndexedDB storage, the application ensures users can access their trip data and maps even without an internet connection. This article examines how the offline PWA mode with Service Worker caching is implemented in the mauriceboe/TREK repository.

Service Worker Registration and Lifecycle Management

In client/src/App.tsx, TREK handles Service Worker registration by checking for navigator.serviceWorker support and registering the auto-generated worker on initial load. When the application version changes, the code actively unregisters existing Service Workers and clears their caches to prevent stale assets from interfering with updates.

// In client/src/App.tsx
useEffect(() => {
  if ('serviceWorker' in navigator) {
    const regs = await navigator.serviceWorker.getRegistrations()
    await Promise.all(regs.map(r => r.unregister()))
  }
}, [])

This approach ensures that users always receive the latest version of the application without stale cache interference, while maintaining the offline capabilities of the PWA.

Workbox Runtime Caching Strategies

The caching behavior is defined in client/vite.config.js using the runtimeCaching array (lines 16-99) provided by vite-plugin-pwa. The configuration implements distinct strategies for different asset types to optimize storage and performance:

  • Map tiles (map-tiles cache) — Uses CacheFirst strategy with a 30-day expiration and 12,288 entry limit, matching the tile prefetch budget
  • CDN libraries (cdn-libs cache) — CacheFirst strategy for Leaflet and other external assets
  • User uploads (user-uploads cache) — CacheFirst for cover images and avatars
  • API data — NetworkOnly strategy to prevent cross-user data leakage through the Service Worker; offline reads are served from IndexedDB instead
// From client/vite.config.js Workbox configuration
{
  // Carto map tiles (default provider)
  // maxEntries MUST stay >= MAX_TILES in src/sync/tilePrefetcher.ts
  urlPattern: /^https:\/\/[a-d]\.basemaps\.cartocdn\.com\/.*/i,
  handler: 'CacheFirst',
  options: {
    cacheName: 'map-tiles',
    expiration: { maxEntries: 12288, maxAgeSeconds: 30 * 24 * 60 * 60 },
    cacheableResponse: { statuses: [0, 200] },
  },
},

Proactive Map Tile Prefetching

When a trip syncs, TREK executes the tile prefetcher located in client/src/sync/tilePrefetcher.ts. The prefetchTiles function (lines 41-71) calculates a bounding box from the trip's places using computeBbox, enumerates XYZ tile coordinates for zoom levels 10-16, and fires fetch() requests until reaching the MAX_TILES budget of 12,288 tiles. These requests are intercepted by the Service Worker and stored in the map-tiles cache via the CacheFirst handler.

The prefetchTilesForTrip function (lines 78-111) orchestrates this process and is invoked by the sync manager after trip data is successfully synchronized.

import { prefetchTilesForTrip } from './sync/tilePrefetcher'

export async function syncTrip(tripId: number, places: Place[]) {
  // …other sync steps…
  await prefetchTilesForTrip(tripId, places) // fires fetches that the SW caches
}

This prefetching strategy ensures that raster map tiles are available locally before the device goes offline, enabling full map visualization without network connectivity.

IndexedDB for Structured Trip Data

While the Service Worker handles static assets, structured trip data persists in IndexedDB via offlineDb (organized through client/src/db/offlineDb.ts). The sync manager writes complete trip bundles to IndexedDB after successful online synchronization, ensuring text content, PDFs, and non-photo attachments remain available offline.

This separation of concerns—using the Service Worker for static assets and IndexedDB for dynamic structured data—prevents cache pollution and ensures user-specific content remains secure while maintaining offline functionality.

Summary

  • Service Worker registration occurs through vite-plugin-pwa in client/src/App.tsx, with automatic unregistration of old workers on version updates to prevent stale cache issues
  • Workbox runtime caching in client/vite.config.js applies CacheFirst strategies to map tiles, CDN libraries, and user uploads, while API data uses NetworkOnly to prevent cross-user leakage
  • Tile prefetching via client/src/sync/tilePrefetcher.ts proactively populates the map-tiles cache with up to 12,288 tiles covering zoom levels 10-16 for offline map viewing
  • IndexedDB storage maintains structured trip data separately from the Service Worker cache, ensuring secure offline access to user-specific content
  • Combined architecture enables the app to launch from the home screen and function fully without network connectivity after the initial online sync

Frequently Asked Questions

How does TREK handle Service Worker updates when deploying new versions?

In client/src/App.tsx, the application checks for existing Service Worker registrations on load and unregisters them before registering the new version. This ensures old caches are cleared and users receive the latest assets immediately, preventing the "stale cache" problem common in PWAs.

Why does TREK use IndexedDB instead of the Service Worker cache for API data?

API responses are configured with a NetworkOnly strategy in the Workbox configuration to prevent cross-user data leakage through the Service Worker cache. Instead, structured trip data is written to IndexedDB after sync, providing secure offline access to user-specific content without exposing data through shared cache mechanisms.

What is the maximum number of map tiles TREK can cache for offline use?

The tile prefetcher enforces a MAX_TILES limit of 12,288 tiles, which matches the maxEntries configuration in the Service Worker cache. This budget covers zoom levels 10-16 based on the trip's geographic bounding box, as calculated by the computeBbox function in client/src/sync/tilePrefetcher.ts.

Which files should developers examine to understand or modify the offline caching behavior?

Key files include client/vite.config.js for Workbox configuration, client/src/App.tsx for Service Worker registration logic, client/src/sync/tilePrefetcher.ts for map tile prefetching algorithms, and client/src/db/offlineDb.ts for IndexedDB schema and operations. Additionally, the wiki/Offline-Mode-and-PWA.md documentation provides detailed information about cache contents and eviction policies.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →