puffin-app/lib/serviceWorkerRegistration.ts
Matt a279bb6aa9
Some checks failed
Build and Push Docker Images / docker (push) Failing after 44s
Add automatic cache clearing and version management to prevent white screen issues
Implements comprehensive service worker solution with:
- Dynamic versioning using git commit hash or timestamp
- Automatic cache invalidation on new deployments
- Hourly update checks and user notifications
- Network-first caching strategy with 24-hour expiration
- Build automation via prebuild script
- Update notification UI component

This prevents stale cached code from causing white screens by ensuring users always get the latest version after deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:32:50 +01:00

107 lines
3.1 KiB
TypeScript

// Service Worker registration with automatic update detection for Next.js
// This ensures users always get the latest version after deployment
type Config = {
onUpdate?: (registration: ServiceWorkerRegistration) => void;
onSuccess?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
// Wait for page load to avoid impacting initial page load performance
window.addEventListener('load', () => {
const swUrl = `/sw.js`;
registerValidSW(swUrl, config);
// Check for updates every hour
setInterval(() => {
checkForUpdates(swUrl);
}, 60 * 60 * 1000); // 1 hour
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
// Check for updates on initial registration
registration.update();
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// New content is available; please refresh
console.log('New content available! Please refresh.');
// Execute onUpdate callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// Content is cached for offline use
console.log('Content cached for offline use.');
// Execute onSuccess callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkForUpdates(swUrl: string) {
navigator.serviceWorker
.getRegistration(swUrl)
.then((registration) => {
if (registration) {
registration.update();
}
})
.catch((error) => {
console.error('Error checking for service worker updates:', error);
});
}
export function unregister() {
if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}
// Force refresh when a new service worker is waiting
export function skipWaitingAndReload() {
if (typeof window !== 'undefined') {
navigator.serviceWorker.ready.then((registration) => {
if (registration.waiting) {
// Tell the waiting service worker to skip waiting and become active
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
});
// Listen for the controller change and reload
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
}
}