// In main app:
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(reg => console.log(
'SW registered:', reg.scope))
.catch(err => console.error(
'SW registration failed:', err));
}
// sw.js
const CACHE_NAME = 'pixelcraft-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/assets/main.js',
'/assets/main.css',
'/manifest.json',
];
// Install: pre-cache static assets
self.addEventListener('install',
(event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache =>
cache.addAll(STATIC_ASSETS))
);
});
// Fetch: strategy per resource type
self.addEventListener('fetch',
(event) => {
// Static assets → cache-first
if (event.request.url.match(
/\.(js|css|png|jpg|woff2?)$/)) {
event.respondWith(
caches.match(event.request)
.then(cached =>
cached || fetch(event.request))
);
return;
}
// API requests → network-first
if (event.request.url
.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_NAME)
.then(cache =>
cache.put(
event.request, clone));
return response;
})
.catch(() =>
caches.match(event.request))
);
return;
}
});
function useOnlineStatus(): boolean {
const [isOnline, setIsOnline] =
useState(navigator.onLine);
useEffect(() => {
const goOnline =
() => setIsOnline(true);
const goOffline =
() => setIsOnline(false);
window.addEventListener(
'online', goOnline);
window.addEventListener(
'offline', goOffline);
return () => {
window.removeEventListener(
'online', goOnline);
window.removeEventListener(
'offline', goOffline);
};
}, []);
return isOnline;
}
function OfflineBanner() {
const isOnline = useOnlineStatus();
if (isOnline) return null;
return (
<div className="bg-amber-600
text-white text-center py-1
text-sm">
You're offline. Changes will
sync when you reconnect.
</div>
);
}
// Store pending changes in IndexedDB
async function queueOfflineAction(
action: OfflineAction
) {
const db = await openDB(
'pixelcraft-offline', 1);
await db.add('pending-actions', {
...action,
timestamp: Date.now(),
});
}
// Sync when back online
window.addEventListener('online',
async () => {
const db = await openDB(
'pixelcraft-offline', 1);
const actions = await db.getAll(
'pending-actions');
for (const action of actions) {
try {
await api(
action.url, action.options);
await db.delete(
'pending-actions', action.id);
} catch (err) {
console.error(
'Sync failed:', action, err);
}
}
});
// manifest.json
{
"name": "PixelCraft",
"short_name": "PixelCraft",
"start_url": "/",
"display": "standalone",
"background_color": "#0a0a0f",
"theme_color": "#e94560",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
// index.html:
// <link rel="manifest"
// href="/manifest.json">
git switch -c feature/PIXELCRAFT-079-offline-pwa
git add public/ src/
git commit -m "Add Service Worker + offline sync + PWA manifest (PIXELCRAFT-079)"
git push origin feature/PIXELCRAFT-079-offline-pwa
# PR → Review → Merge → Close ticket ✅
Cache hierarchies operate at every level of computing.
The Service Worker is a programmable cache — you control the strategy per resource type. The same pattern exists at every layer.