Service Worker web developer’a browser/network arasında middleware yazma imkanı veriyor. Doğru kullanımında: offline çalışan web app, instant re-visit, background sync.
2 PWA projesinde Service Worker caching kurdum. Biri news site (offline reading), biri productivity app (instant re-open). Pattern’lar farklı.
Service Worker nedir
Browser’da çalışan bir script, main JS’ten ayrı thread’de. Network request’leri intercept edebiliyor, cache’e erişiyor, push notification alıyor, background sync yapıyor.
Progressive Web App (PWA)’in omurgası.
Registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(reg => {
console.log('SW registered:', reg);
});
});
}HTTPS gerekli (localhost exception). /sw.js scope / olsun diye root’ta.
Cache strategy taxonomy
5 ana pattern:
1. Cache-First: Önce cache’den bak, yoksa network. Static asset (CSS, JS, fonts, images).
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});2. Network-First: Önce network, yoksa cache. Dynamic content (API responses, HTML).
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
});3. Stale-While-Revalidate: Cache’den döndür hemen, background’da network update et. Instant response + eventually fresh.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic').then(cache => {
return cache.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
cache.put(event.request, response.clone());
return response;
});
return cachedResponse || networkFetch;
});
})
);
});4. Cache-Only: Sadece cache. Precached static asset’ler, fallback page.
5. Network-Only: Sadece network. Analytics, POST request, authentication.
Her URL pattern’e göre strategy seçiliyor.
Workbox: hayatı kolaylaştıran library
Google’ın Workbox kütüphanesi bu pattern’ları tek satıra indiriyor:
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({ cacheName: 'images' })
);
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({ cacheName: 'api' })
);
registerRoute(
({ request }) => request.mode === 'navigate',
new StaleWhileRevalidate({ cacheName: 'pages' })
);Declarative, bakımı kolay, test edilmiş.
Precaching: install time
Service Worker install event’inde critical asset’leri cache’e yüklemek. İlk visit’ten sonra her visit offline-capable.
const PRECACHE_VERSION = 'v3';
const PRECACHE_ASSETS = [
'/',
'/offline.html',
'/styles/main.css',
'/scripts/app.js',
'/fonts/inter-var.woff2'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(PRECACHE_VERSION).then(cache => {
return cache.addAll(PRECACHE_ASSETS);
})
);
});Version bump her deploy’da yeni asset’leri cache’liyor.
Cache cleanup: eski version
Yeni SW aktive olunca eski cache’leri temizlemek:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys
.filter(key => key !== PRECACHE_VERSION)
.map(key => caches.delete(key))
);
})
);
});Yoksa her deploy’da cache büyüyor, quota dolayor, browser silmek zorunda kalıyor.
Offline fallback page
Network fail ve cache miss durumunda kullanıcıya gösterilecek fallback:
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => caches.match('/offline.html'))
);
}
});Kullanıcı ağ kaybı olunca “You are offline” sayfası. Tam boş ekrandan daha iyi.
Update flow
Yeni SW deploy edildiğinde mevcut kullanıcı ne oluyor?
Default davranış:
1. Background’da yeni SW download
2. Install event
3. Eski SW halen aktif, yeni SW “waiting”
4. Tüm tab’lar kapanıp tekrar açıldığında yeni SW aktive
Pratikte kullanıcı tab’ı kapatmıyor, yeni version görmüyor. Çözümler:
A. Refresh prompt. Yeni SW waiting state’te ise kullanıcıya “Yeni versiyon hazır, sayfa yenile?” mesajı göster.
B. skipWaiting + clients.claim. Yeni SW install edilir edilmez eski yerine geçiyor. Ama mid-session break riski (kullanıcı farklı version’lardan içerik görebilir).
C. Version check. Main JS’te navigator.serviceWorker.controller.state kontrolü, version mismatch’te page reload.
Ben refresh prompt tercih ediyorum. Kullanıcı açıkça biliyor “yeni version için refresh”.
Runtime cache management
Runtime cache boyutu kontrol altında olmalı. Sonsuz büyüyemez.
Workbox’ta:
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 gün
})
]
})
);100 image’dan fazlası eski olanı evict. 30 günden eski olan otomatik silinir.
Quota management
Browser her origin’e storage quota veriyor (genelde device storage’ın %60’ı, dynamic). Quota dolduğunda:
navigator.storage.estimate().then(estimate => {
const percentUsed = (estimate.usage / estimate.quota) * 100;
if (percentUsed > 80) {
// Cleanup old caches
}
});Persistent storage request (API):
navigator.storage.persist().then(granted => {
if (granted) console.log('Storage will not be cleared');
});Kullanıcı explicit kabul ederse browser quota’yı korur.
Background Sync
Kullanıcı offline iken yaptığı işi online olunca otomatik tamamlamak:
// Main JS
navigator.serviceWorker.ready.then(registration => {
registration.sync.register('send-messages');
});
// SW
self.addEventListener('sync', (event) => {
if (event.tag === 'send-messages') {
event.waitUntil(sendPendingMessages());
}
});
async function sendPendingMessages() {
const db = await openIndexedDB();
const pending = await db.getAll('pending_messages');
for (const msg of pending) {
await fetch('/api/messages', { method: 'POST', body: JSON.stringify(msg) });
await db.delete('pending_messages', msg.id);
}
}Kullanıcı metro’da offline mesaj yazdı, online olunca otomatik gönderiliyor.
Periodic Sync
Periodic background sync: user interaction olmasa bile düzenli sync. News app’te yeni article’ları arka planda fetch etmek gibi.
API yeni ve sınırlı (user opt-in gerekli, browser approval). Şimdilik progressive enhancement.
Offline indicator
Kullanıcıya online/offline state göstermek:
window.addEventListener('online', () => showBanner('Yeniden bağlandınız'));
window.addEventListener('offline', () => showBanner('Çevrimdışısınız, bazı özellikler sınırlı'));Banner veya status indicator. Kullanıcı anlıyor ne olup bittiğini.
Debug disiplin
SW debug pain. Chrome DevTools Application panel SW tab’ı kritik:
- Update on reload: her refresh’te fresh SW
- Bypass for network: SW’yi devre dışı bırak
- Unregister: SW’yi kaldır
Dev’de bu tool’lar şart, üretimde kullanıcılar SW kurban oluyor stale cache’e.
Gerçek sonuçlar
News site projesinde SW caching ile:
– Repeat visit load time: 2.4s → 200ms (-92%)
– Offline read capability: %0 → son 20 article
– Mobile data usage: -%45 (cache hit yüksek)
Productivity app’te:
– Instant open from home screen
– Offline edit + sync
– 60% of users using app offline at least once per month
Yaygın hatalar
1. Over-caching. Hepsini cache’leyip storage tüketiyorsun. Sadece önemli asset’leri cache’le.
2. Stale data served forever. Cache-first strategy’de version bump unutuluyor, kullanıcı 3 ay önceki content görüyor. Version’lama ve TTL şart.
3. POST request cache’leme. POST cache’lenmez by default ama custom logic yazarsan yanlış davranış. GET only cache.
4. Authentication header cache’leme. User A’nın authenticated response’u cache’lenip User B’ye dönebilir. Authentication response’ları cache’lemeyin veya user-scoped cache key.
5. Debug süresi underestimate. SW bug’ları gizli kalabiliyor, regression sezmek zor. Thorough testing, incremental rollout.
Son tavsiye
Service Worker basit başlayıp sofistikeleşiyor. Temel precaching + network-first çoğu site için yeter. Offline-first PWA ise runtime cache + background sync + IndexedDB birleşimi.
Workbox bu karmaşıklığı absorb ediyor. Yeni projede direkt Workbox kullanıyorum, manuel SW yazmıyorum.
SW’nin gerçek gücü: kullanıcı re-engagement. 2. visit instant, offline work, push notification hepsi birlikte PWA’yı native app hissi veriyor.