Skip to main content
World Monitor is a fully-featured Progressive Web App (PWA) that works offline, installs to the home screen, and provides a native app-like experience.

PWA Features

  • Offline Support - Core functionality available without internet
  • Installable - Add to home screen on mobile and desktop
  • Fast Loading - Service worker caches assets and data
  • Background Sync - Queue updates when offline
  • Push Notifications - Real-time alerts (optional)

Configuration

Vite PWA Plugin

World Monitor uses vite-plugin-pwa with Workbox:
vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      injectRegister: false,
      
      includeAssets: [
        'favico/favicon.ico',
        'favico/apple-touch-icon.png',
        'favico/favicon-32x32.png',
      ],
      
      manifest: {
        name: 'World Monitor - Real-Time Global Intelligence Dashboard',
        short_name: 'WorldMonitor',
        description: 'Real-time global intelligence dashboard with live news, markets, military tracking, and geopolitical data.',
        start_url: '/',
        scope: '/',
        display: 'standalone',
        orientation: 'any',
        theme_color: '#0a0f0a',
        background_color: '#0a0f0a',
        categories: ['news', 'productivity'],
        icons: [
          { src: '/favico/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
          { src: '/favico/android-chrome-512x512.png', sizes: '512x512', type: 'image/png' },
          { src: '/favico/android-chrome-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
        ],
      },
      
      workbox: {
        globPatterns: ['**/*.{js,css,ico,png,svg,woff2}'],
        globIgnores: ['**/ml*.js', '**/onnx*.wasm', '**/locale-*.js'],
        navigateFallback: null,
        skipWaiting: true,
        clientsClaim: true,
        cleanupOutdatedCaches: true,
      },
    }),
  ],
});

Web App Manifest

Manifest Fields

The web app manifest defines how World Monitor appears when installed:
manifest.webmanifest
{
  "name": "World Monitor - Real-Time Global Intelligence Dashboard",
  "short_name": "WorldMonitor",
  "description": "Real-time global intelligence dashboard with live news, markets, military tracking, and geopolitical data.",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "orientation": "any",
  "theme_color": "#0a0f0a",
  "background_color": "#0a0f0a",
  "categories": ["news", "productivity"],
  "icons": [
    {
      "src": "/favico/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/favico/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/favico/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

Display Modes

  • standalone - Full-screen without browser UI (recommended)
  • fullscreen - Full-screen with no browser or OS UI
  • minimal-ui - Minimal browser UI (back/forward buttons)
  • browser - Normal browser tab
World Monitor uses standalone for an app-like experience.

Maskable Icons

Maskable icons adapt to different device shapes (rounded corners, circles):
{
  "src": "/favico/android-chrome-512x512.png",
  "sizes": "512x512",
  "type": "image/png",
  "purpose": "maskable"
}
Design maskable icons with a safe zone (80% of canvas) for important content.

Variant-Specific Manifests

Each variant has custom metadata:
vite.config.ts
const VARIANT_META = {
  full: {
    siteName: 'World Monitor',
    shortName: 'WorldMonitor',
    categories: ['news', 'productivity'],
  },
  tech: {
    siteName: 'Tech Monitor',
    shortName: 'TechMonitor',
    categories: ['news', 'business'],
  },
  finance: {
    siteName: 'Finance Monitor',
    shortName: 'FinanceMonitor',
    categories: ['finance', 'news'],
  },
  happy: {
    siteName: 'Happy Monitor',
    shortName: 'HappyMonitor',
    categories: ['news', 'lifestyle'],
  },
};

const activeVariant = process.env.VITE_VARIANT || 'full';
const activeMeta = VARIANT_META[activeVariant];

VitePWA({
  manifest: {
    name: `${activeMeta.siteName} - ${activeMeta.subject}`,
    short_name: activeMeta.shortName,
    categories: activeMeta.categories,
  },
});

Service Worker

Workbox Configuration

World Monitor uses Workbox for advanced caching strategies:
vite.config.ts
workbox: {
  // Files to precache
  globPatterns: ['**/*.{js,css,ico,png,svg,woff2}'],
  
  // Exclude heavy files from precache
  globIgnores: ['**/ml*.js', '**/onnx*.wasm', '**/locale-*.js'],
  
  // Don't use navigation fallback (breaks API routes)
  navigateFallback: null,
  
  // Update immediately instead of waiting
  skipWaiting: true,
  
  // Take control of all clients immediately
  clientsClaim: true,
  
  // Remove old caches on activation
  cleanupOutdatedCaches: true,
}

Caching Strategies

World Monitor uses different strategies for different resource types:

HTML Navigation - Network First

vite.config.ts
runtimeCaching: [
  {
    urlPattern: ({ request }) => request.mode === 'navigate',
    handler: 'NetworkFirst',
    options: {
      cacheName: 'html-navigation',
      networkTimeoutSeconds: 3,
    },
  },
]
Tries network first, falls back to cache if network fails or times out after 3 seconds.

API Routes - Network Only

vite.config.ts
{
  urlPattern: ({ url, sameOrigin }) => sameOrigin && /^\/api\//.test(url.pathname),
  handler: 'NetworkOnly',
  method: 'GET',
}
Never caches API responses to ensure fresh data.

Map Tiles - Cache First

vite.config.ts
{
  urlPattern: /^https:\/\/api\.maptiler\.com\//,
  handler: 'CacheFirst',
  options: {
    cacheName: 'map-tiles',
    expiration: {
      maxEntries: 500,
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
    },
    cacheableResponse: {
      statuses: [0, 200],
    },
  },
}
Caches map tiles aggressively for offline use.

Fonts - Cache First

vite.config.ts
{
  urlPattern: /^https:\/\/fonts\.gstatic\.com\//,
  handler: 'CacheFirst',
  options: {
    cacheName: 'google-fonts-woff',
    expiration: {
      maxEntries: 30,
      maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year
    },
    cacheableResponse: {
      statuses: [0, 200],
    },
  },
}

Images - Stale While Revalidate

vite.config.ts
{
  urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/i,
  handler: 'StaleWhileRevalidate',
  options: {
    cacheName: 'images',
    expiration: {
      maxEntries: 100,
      maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
    },
  },
}
Serves from cache immediately, updates in background.

Service Worker Registration

World Monitor registers the service worker automatically:
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js');
  });
}

Service Worker Updates

With skipWaiting: true and autoUpdate, new versions activate immediately:
VitePWA({
  registerType: 'autoUpdate',
  workbox: {
    skipWaiting: true,
    clientsClaim: true,
  },
});
Users don’t need to close all tabs to get updates.

Offline Support

Offline Page

World Monitor serves a custom offline page when no cache is available:
public/offline.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Offline - World Monitor</title>
</head>
<body>
  <h1>You're offline</h1>
  <p>World Monitor requires an internet connection for real-time data.</p>
  <p>Cached data will load when you reconnect.</p>
</body>
</html>

Offline Detection

Detect network status in your app:
const [isOnline, setIsOnline] = useState(navigator.onLine);

useEffect(() => {
  const handleOnline = () => setIsOnline(true);
  const handleOffline = () => setIsOnline(false);
  
  window.addEventListener('online', handleOnline);
  window.addEventListener('offline', handleOffline);
  
  return () => {
    window.removeEventListener('online', handleOnline);
    window.removeEventListener('offline', handleOffline);
  };
}, []);

Background Sync

Queue actions when offline, sync when online:
if ('serviceWorker' in navigator && 'SyncManager' in window) {
  navigator.serviceWorker.ready.then((registration) => {
    registration.sync.register('sync-data');
  });
}

Installation

Install Prompt

World Monitor can prompt users to install:
let deferredPrompt: any;

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  // Show custom install button
});

function handleInstall() {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    deferredPrompt.userChoice.then((choiceResult: any) => {
      if (choiceResult.outcome === 'accepted') {
        console.log('User accepted install');
      }
      deferredPrompt = null;
    });
  }
}

Installation Criteria

Browsers show the install prompt when:
  1. The app is served over HTTPS
  2. A valid web app manifest exists
  3. A service worker is registered
  4. The user has visited at least once
  5. The user has engaged with the site

Installation Methods

  1. Click the install icon in the address bar
  2. Or: Menu → Install World Monitor

Testing

Lighthouse PWA Audit

Run Lighthouse to check PWA compliance:
npm install -g lighthouse
lighthouse https://worldmonitor.app --view
Target scores:
  • Performance: 90+
  • Accessibility: 95+
  • Best Practices: 95+
  • SEO: 95+
  • PWA: 100

Service Worker Testing

Test service worker in Chrome DevTools:
  1. Open DevTools (F12)
  2. Go to Application → Service Workers
  3. Check “Offline” to simulate offline mode
  4. Verify caching in Application → Cache Storage

Manifest Testing

Validate manifest in DevTools:
  1. Go to Application → Manifest
  2. Check all fields display correctly
  3. Verify icons load
  4. Test install prompt

PWA Builder

Use PWA Builder to validate and generate app packages:
# Visit
https://www.pwabuilder.com

# Enter URL
https://worldmonitor.app

# Download packages for Windows Store, Play Store, etc.

Performance Optimization

Precaching Strategy

Precache only essential assets:
globPatterns: ['**/*.{js,css,ico,png,svg,woff2}'],
globIgnores: [
  '**/ml*.js',        // ML models (lazy loaded)
  '**/onnx*.wasm',    // ONNX runtime (lazy loaded)
  '**/locale-*.js',   // Locale files (lazy loaded)
],

Cache Size Limits

Set reasonable cache limits:
expiration: {
  maxEntries: 500,        // Max 500 map tiles
  maxAgeSeconds: 2592000, // 30 days
}

Network Timeouts

Set aggressive timeouts for faster offline fallback:
handler: 'NetworkFirst',
options: {
  networkTimeoutSeconds: 3, // Fall back to cache after 3s
}

Troubleshooting

Service Worker Not Updating

Solution: Clear cache and unregister old service worker:
navigator.serviceWorker.getRegistrations().then((registrations) => {
  registrations.forEach((registration) => registration.unregister());
});

caches.keys().then((names) => {
  names.forEach((name) => caches.delete(name));
});

Install Prompt Not Showing

Checklist:
  1. Served over HTTPS? (localhost works too)
  2. Valid manifest.webmanifest?
  3. Service worker registered?
  4. User visited before?
  5. App not already installed?

Cache Not Working

Debug:
  1. Open DevTools → Application → Cache Storage
  2. Verify caches exist
  3. Check service worker status
  4. Look for errors in Console

Manifest Not Loading

Check:
  1. Manifest path correct in HTML: <link rel="manifest" href="/manifest.webmanifest">
  2. Manifest served with correct MIME type: Content-Type: application/manifest+json
  3. No CORS errors (same origin)

Browser Support

  • Chrome/Edge: Full PWA support
  • Safari: PWA support on iOS 11.3+, limited on macOS
  • Firefox: Service workers and manifest, no install prompt
  • Samsung Internet: Full PWA support

Build docs developers (and LLMs) love