# How TREK Implements Offline PWA Mode with Service Worker Caching

> Discover how TREK achieves offline PWA mode with Service Worker caching. Learn how map tiles, libraries, and trip data are stored for seamless offline access.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-07-04

---

**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`](https://github.com/mauriceboe/TREK/blob/main/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.

```tsx
// 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`](https://github.com/mauriceboe/TREK/blob/main/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

```js
// 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`](https://github.com/mauriceboe/TREK/blob/main/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.

```ts
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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/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`](https://github.com/mauriceboe/TREK/blob/main/client/vite.config.js) for Workbox configuration, [`client/src/App.tsx`](https://github.com/mauriceboe/TREK/blob/main/client/src/App.tsx) for Service Worker registration logic, [`client/src/sync/tilePrefetcher.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/sync/tilePrefetcher.ts) for map tile prefetching algorithms, and [`client/src/db/offlineDb.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/db/offlineDb.ts) for IndexedDB schema and operations. Additionally, the [`wiki/Offline-Mode-and-PWA.md`](https://github.com/mauriceboe/TREK/blob/main/wiki/Offline-Mode-and-PWA.md) documentation provides detailed information about cache contents and eviction policies.