Skip to content

feat: the PWA service worker caches assets aggressively but has no cache-busting strategy β€” after a code update, returning users continue to run the stale cached version of the app indefinitelyΒ #96

Description

@prince-pokharna

πŸ› Problem Statement

CubeIt is described as:

"Assets are aggressively cached by a service worker for instant loading times."

Aggressive caching is great for performance but creates a serious update delivery problem. When a new version is deployed to Netlify:

  1. The service worker still serves the old cached bundle.js and index.html from CacheStorage
  2. The user sees the old version of the app β€” potentially with fixed bugs or new features they never receive
  3. The only way to force an update is to manually clear browser storage or unregister the service worker

This is a known PWA anti-pattern that affects every user who installed CubeIt as a home screen app.

πŸ’‘ Proposed Fix

Implement a versioned cache-busting strategy with a user-visible "Update Available" notification:

1. Add cache version to the service worker

// public/service-worker.js

const CACHE_VERSION = 'cubeit-v1.3.0'; // Bump with every release
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/static/js/main.chunk.js',
  '/static/css/main.chunk.css',
  '/manifest.json',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_VERSION).then((cache) => cache.addAll(ASSETS_TO_CACHE))
  );
  self.skipWaiting(); // Activate immediately
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys
          .filter((key) => key !== CACHE_VERSION) // Delete old caches
          .map((key) => caches.delete(key))
      )
    )
  );
});

2. Show an "Update Available" banner in the React app

// src/hooks/useServiceWorker.js

import { useEffect, useState } from 'react';

export function useServiceWorkerUpdate() {
  const [updateAvailable, setUpdateAvailable] = useState(false);
  const [waitingWorker, setWaitingWorker] = useState(null);

  useEffect(() => {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/service-worker.js').then((reg) => {
        reg.addEventListener('updatefound', () => {
          const newWorker = reg.installing;
          newWorker.addEventListener('statechange', () => {
            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
              setUpdateAvailable(true);
              setWaitingWorker(newWorker);
            }
          });
        });
      });
    }
  }, []);

  const applyUpdate = () => {
    waitingWorker?.postMessage({ type: 'SKIP_WAITING' });
    window.location.reload();
  };

  return { updateAvailable, applyUpdate };
}
// src/components/UpdateBanner.jsx
const { updateAvailable, applyUpdate } = useServiceWorkerUpdate();
{updateAvailable && (
  <div className="update-banner">
    πŸŽ‰ A new version of CubeIt is ready!
    <button onClick={applyUpdate}>Update Now</button>
  </div>
)}

πŸ“ Files to Modify

File Change
public/service-worker.js Add versioned cache, old cache cleanup, SKIP_WAITING handler
src/hooks/useServiceWorker.js New β€” update detection hook
src/components/UpdateBanner.jsx New β€” user-visible update prompt
src/App.js Render <UpdateBanner />

Suggested labels: bug, pwa, service-worker, ux

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions