Skip to Content
ExamplesService Worker for Map Tiles Caching

Service Worker for Map Tiles Caching

This guide explains how to implement Service Worker caching to improve map loading performance and enable offline functionality for the MapVX Web SDK.

Overview

Service Workers allow you to cache map tiles and other assets, significantly improving performance on repeated visits and providing offline capabilities. This is independent of the SDK but greatly enhances its performance by caching map resources.

Benefits

  • Faster Loading: Cached tiles load instantly on repeat visits
  • Offline Support: Maps work without internet connection
  • Reduced Bandwidth: Less data usage for returning users
  • Better User Experience: Smoother map interactions
  • Performance Optimization: Reduced server load

Implementation Approaches

There are different ways to implement Service Workers depending on your project needs:

1. Manual Service Worker (injectManifest)

Write your own Service Worker for maximum flexibility. Best for complex caching requirements or when you need additional Service Worker features like push notifications.

2. Generated Service Worker (generateSW)

Let Workbox generate the Service Worker for you. Best for simple caching needs and quick setup.

3. Build Tool Integration

Use Workbox plugins for bundlers like Webpack, Vite, or Rollup. Best when you want Service Worker generation as part of your build process.

Basic Implementation

1. Service Worker Setup

You need to manually register and serve a Service Worker file from your site’s root directory (HTTPS required). This Service Worker will intercept network requests and cache map resources to improve performance.

2. Basic Service Worker (Workbox)

Create a file named mapvx-tiles-sw.js in your site’s root directory:

/* MapVX Web SDK Service Worker (Workbox) - Cache-first strategy for map tiles (PBF files) - Stale-while-revalidate for other map assets - Network-first for API calls Requirements: 1) Save this file as "/mapvx-tiles-sw.js" in your site root 2) Ensure your site is served over HTTPS 3) Register it manually in your application code */ /* eslint-env serviceworker */ /* global workbox, importScripts */ importScripts("https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js") const { registerRoute } = workbox.routing const { CacheFirst, StaleWhileRevalidate, NetworkFirst } = workbox.strategies const { CacheableResponsePlugin } = workbox.cacheableResponse const { ExpirationPlugin } = workbox.expiration // Preload Workbox modules workbox.loadModule("workbox-routing") workbox.loadModule("workbox-strategies") workbox.loadModule("workbox-cacheable-response") workbox.loadModule("workbox-expiration") // Allowed hosts for caching (adjust based on your API endpoints) const ALLOWED_HOSTS = ["api.mapvx.com", "maps.mapvx.com", "tiles.mapvx.com"] const isAllowedHost = (hostname) => ALLOWED_HOSTS.some((host) => hostname.includes(host)) // API calls → Network-first strategy registerRoute( ({ url }) => isAllowedHost(url.hostname) && url.pathname.includes("/api/"), new NetworkFirst({ cacheName: "mapvx-api-cache", plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 60 * 60, // 1 hour }), ], }) ) // Map tiles (PBF files) → Cache-first strategy registerRoute( ({ url }) => isAllowedHost(url.hostname) && /\.pbf($|\?)/.test(url.pathname), new CacheFirst({ cacheName: "mapvx-tiles-pbf", plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxEntries: 10000, maxAgeSeconds: 60 * 60 * 24 * 7, // 1 week }), ], }) ) // Map styles and other assets → Stale-while-revalidate registerRoute( ({ url }) => isAllowedHost(url.hostname) && !/\.pbf($|\?)/.test(url.pathname), new StaleWhileRevalidate({ cacheName: "mapvx-map-assets", plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxEntries: 500, maxAgeSeconds: 60 * 60 * 24, // 1 day }), ], }) ) // Images and icons → Cache-first registerRoute( ({ request }) => request.destination === "image", new CacheFirst({ cacheName: "mapvx-images", plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days }), ], }) )

3. Manual Service Worker Registration

If you need manual control over Service Worker registration:

// Register Service Worker manually async function registerServiceWorker() { if ("serviceWorker" in navigator) { try { const registration = await navigator.serviceWorker.register("/mapvx-tiles-sw.js", { scope: "/", }) console.log("Service Worker registered successfully:", registration) // Listen for updates registration.addEventListener("updatefound", () => { const newWorker = registration.installing console.log("New Service Worker found") newWorker?.addEventListener("statechange", () => { if (newWorker.state === "installed") { if (navigator.serviceWorker.controller) { // New update available console.log("New content available, please refresh") } else { // Content cached for offline use console.log("Content cached for offline use") } } }) }) } catch (error) { console.error("Service Worker registration failed:", error) } } } // Call when your application starts registerServiceWorker()

Alternative Workbox Approaches

Using workbox-cli

For projects without complex build processes:

npm install workbox-cli --save-dev npx workbox wizard

This creates a workbox-config.js file:

// workbox-config.js export default { globDirectory: "dist/", globPatterns: ["**/*.{css,woff2,png,svg,jpg,js,pbf}"], swDest: "dist/sw.js", runtimeCaching: [ { urlPattern: /\.pbf$/, handler: "CacheFirst", options: { cacheName: "map-tiles", expiration: { maxEntries: 10000, maxAgeSeconds: 60 * 60 * 24 * 7, // 1 week }, }, }, ], }

Using workbox-build

For more programmatic control:

// build-sw.js import { generateSW } from "workbox-build" generateSW({ globDirectory: "dist/", globPatterns: ["**/*.{css,woff2,png,svg,jpg,js,pbf}"], swDest: "dist/sw.js", runtimeCaching: [ { urlPattern: /api\.mapvx\.com/, handler: "NetworkFirst", options: { cacheName: "api-cache", networkTimeoutSeconds: 3, }, }, ], })

Using workbox-webpack-plugin

For Webpack projects:

// webpack.config.js import { GenerateSW } from "workbox-webpack-plugin" export default { plugins: [ new GenerateSW({ swDest: "./dist/sw.js", runtimeCaching: [ { urlPattern: /\.pbf$/, handler: "CacheFirst", options: { cacheName: "map-tiles", }, }, ], }), ], }

Framework-Specific Integration

React/Next.js

// next.config.js module.exports = { async rewrites() { return [ { source: "/mapvx-tiles-sw.js", destination: "/api/service-worker", }, ] }, } // pages/api/service-worker.js import { readFileSync } from "fs" import { join } from "path" export default function handler(req, res) { const swPath = join(process.cwd(), "public", "mapvx-tiles-sw.js") const serviceWorker = readFileSync(swPath, "utf8") res.setHeader("Content-Type", "application/javascript") res.setHeader("Cache-Control", "no-cache") res.send(serviceWorker) }

Angular

// angular.json - add to assets "assets": [ "src/favicon.ico", "src/assets", "src/mapvx-tiles-sw.js" ] // app.component.ts import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit { ngOnInit() { this.registerServiceWorker(); } private async registerServiceWorker() { if ('serviceWorker' in navigator) { try { await navigator.serviceWorker.register('/mapvx-tiles-sw.js'); console.log('Service Worker registered'); } catch (error) { console.error('Service Worker registration failed:', error); } } } }

Vue.js

// vue.config.js module.exports = { pwa: { workboxPluginMode: "InjectManifest", workboxOptions: { swSrc: "src/mapvx-tiles-sw.js", swDest: "mapvx-tiles-sw.js", }, }, } // main.js import { createApp } from "vue" import App from "./App.vue" const app = createApp(App) // Register Service Worker if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/mapvx-tiles-sw.js") .then((registration) => { console.log("SW registered: ", registration) }) .catch((registrationError) => { console.log("SW registration failed: ", registrationError) }) }) } app.mount("#app")

Advanced Configuration

Custom Cache Strategies

// Custom cache strategy for specific map styles registerRoute( ({ url }) => url.pathname.includes("/styles/custom"), new CacheFirst({ cacheName: "custom-map-styles", plugins: [ new CacheableResponsePlugin({ statuses: [0, 200] }), new ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days }), ], }) ) // Skip caching for real-time data registerRoute( ({ url }) => url.pathname.includes("/realtime") || url.pathname.includes("/live"), new NetworkFirst({ cacheName: "realtime-data", networkTimeoutSeconds: 3, plugins: [ new CacheableResponsePlugin({ statuses: [0, 200] }), new ExpirationPlugin({ maxEntries: 20, maxAgeSeconds: 60, // 1 minute }), ], }) )

Cache Management

// Clear old caches self.addEventListener("activate", (event) => { const cacheWhitelist = ["mapvx-tiles-pbf", "mapvx-map-assets", "mapvx-api-cache"] event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (!cacheWhitelist.includes(cacheName)) { console.log("Deleting old cache:", cacheName) return caches.delete(cacheName) } }) ) }) ) })

Testing Service Worker

Chrome DevTools

  1. Open Chrome DevTools
  2. Go to Application tab
  3. Click Service Workers in the sidebar
  4. Verify registration and status
  5. Check Cache Storage for cached resources

Testing Script

// Test Service Worker functionality async function testServiceWorker() { if ("serviceWorker" in navigator) { const registration = await navigator.serviceWorker.getRegistration() if (registration) { console.log("✅ Service Worker is registered") console.log("Scope:", registration.scope) console.log("State:", registration.active?.state) // Test cache const cache = await caches.open("mapvx-tiles-pbf") const keys = await cache.keys() console.log(`📦 Cached tiles: ${keys.length}`) } else { console.log("❌ Service Worker not registered") } } else { console.log("❌ Service Worker not supported") } } // Run test testServiceWorker()

Performance Monitoring

// Monitor cache performance self.addEventListener("fetch", (event) => { const start = performance.now() event.respondWith( // Your caching strategy here caches.match(event.request).then((response) => { const duration = performance.now() - start if (response) { console.log(`Cache hit: ${event.request.url} (${duration.toFixed(2)}ms)`) } else { console.log(`Cache miss: ${event.request.url} (${duration.toFixed(2)}ms)`) } return response || fetch(event.request) }) ) })

Important Notes

  1. HTTPS Required: Service Workers only work over HTTPS (except localhost)
  2. Same Origin: Service Worker must be served from the same origin
  3. Cache Limits: Be mindful of storage quotas (usually 50-100MB)
  4. Update Strategy: Plan for Service Worker updates and cache invalidation
  5. Fallback: Always provide network fallbacks for critical functionality
  6. Testing: Test thoroughly in different network conditions

Troubleshooting

Service Worker Not Registering

  • Check browser console for errors
  • Verify HTTPS connection
  • Ensure file path is correct
  • Check browser compatibility

Cache Not Working

  • Verify cache names match
  • Check network tab in DevTools
  • Clear browser cache and re-test
  • Verify cache strategies are correct

Performance Issues

  • Monitor cache size limits
  • Implement proper cache expiration
  • Use appropriate cache strategies for different resource types

This implementation uses Workbox , Google’s library for adding offline support to web apps. Workbox provides flexible caching strategies and can be adapted to different frameworks and build processes. For more advanced configurations and integration options, refer to the official Workbox documentation .

Last updated on