Service worker, bir origin ve yol için kaydedilen, sayfa ile ağ arasında vekil sunucu gibi duran, olay güdümlü bir worker türüdür. navigator.serviceWorker.register("/sw.js") ile kaydedilir; ardından tarayıcı onu indirir, install olayıyla kurar, activate olayıyla etkinleştirir ve kapsamındaki sayfaların ağ isteklerini fetch olayıyla yakalamasına izin verir. Yalnızca güvenli bağlamda (HTTPS ya da localhost) çalışır.
Bu yazı kayıt, kapsam (scope), yaşam döngüsü durumları, yeni sürümün neden beklediği, skipWaiting() ile clients.claim() farkı ve güncelleme akışı üzerinedir. Önbellek stratejilerini çevrimdışı önbellek stratejileri yazısına, Cache API yöntemlerini Cache API yazısına bıraktık. Konunun genel haritası Web API öğrenme rehberinde.
Service worker ne yapar, ne yapamaz?
Service worker bir JavaScript dosyasıdır ve ilişkili olduğu sitenin sayfalarını kontrol eder: gezinme ve kaynak isteklerini yakalar, değiştirir, yanıtları ayrıntılı biçimde önbelleğe alır. MDN'in tanımıyla, ağ yokken uygulamanın nasıl davranacağı üzerinde tam denetim verir. Bunun yanında push bildirimleri ve arka plan senkronizasyonu gibi API'lerin de temelidir.
Bir worker bağlamında çalıştığı için DOM'a erişimi yoktur ve sayfanın ana JavaScript'inden farklı bir iş parçacığında çalışır. Tamamen asenkron tasarlanmıştır; bu nedenle senkron XHR ve Web Storage (localStorage) service worker içinde kullanılamaz. Veri saklamak için IndexedDB kullanılabilir. MDN ayrıca service worker global kapsamında dinamik import() çağrısının hata fırlattığını, statik import deyiminin ise (module türünde kayıtta) izinli olduğunu belirtir. Genel worker kavramlarını Web Workers yazısında anlattık.
Service worker sürekli çalışmaz. Tarayıcı, bir süre boşta kalan service worker'ı durdurabilir ve işlemesi gereken bir olay geldiğinde yeniden başlatır. Bu yüzden global değişkenlere durum saklamak güvenilir değildir; üstelik tek bir service worker kapsamındaki tüm sayfalara hizmet eder, her sayfanın kendine ait bir kopyası yoktur.
Kayıt: register() ve kapsam

Kayıt sayfa tarafında yapılır. Önce "serviceWorker" in navigator ile destek kontrol edilir; destek yoksa site normal bir web sitesi olarak çalışmaya devam eder. register() bir Promise döndürür ve ServiceWorkerRegistration nesnesiyle çözülür. Aşağıdaki örnek MDN'in temel örneğinden uyarlandı; await kullanımı için async/await yazısına bakabilirsiniz.
// app.js (sayfa tarafı)
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
registerServiceWorker();register(scriptURL, options) üç seçenek alır. scope, service worker'ın hangi adres aralığını kontrol edeceğini belirler. type değeri "classic" (varsayılan) ya da "module" olabilir. updateViaCache ise güncelleme sırasında service worker betiği ve içe aktardıkları için HTTP önbelleğinin nasıl kullanılacağını söyler: "all" ana betik ve içe aktarılanlar için önbelleğe bakar, "imports" yalnızca içe aktarılanlar için önbelleğe bakıp ana betiği her zaman ağdan alır, "none" hiçbirinde HTTP önbelleğini kullanmaz. Bu seçenek yalnızca betiğin kendisini etkiler, betiğin getirdiği diğer kaynakları etkilemez.
Kapsam kuralı en sık karışan konudur. Varsayılan kapsam, service worker betiğinin bulunduğu dizindir: /js/sw.js yalnızca /js/ altındaki adresleri kontrol edebilir. Kapsamı bu dizinden daha geniş yapmak isterseniz sunucunun betik yanıtına Service-Worker-Allowed başlığını eklemesi gerekir; scope seçeneği tek başına yalnızca daraltmaya yarar. MDN, betik adresini sayfaya göre değil site köküne göre (/sw.js) yazmayı önerir, çünkü böylece aynı kayıt kodu her sayfadan aynı sonucu verir. Aşağıdaki çözümlemeyi URL nesnesiyle Node.js 24'te çalıştırdık:
// Varsayılan kapsam: betiğin bulunduğu dizin ("./" betik URL'sine göre çözülür)
const varsayilanKapsam = (betikURL) => new URL("./", betikURL).href;
console.log(varsayilanKapsam("https://example.com/sw.js")); // https://example.com/
console.log(varsayilanKapsam("https://example.com/js/sw.js")); // https://example.com/js/
// Bir sayfa URL'si kapsamın içinde mi?
const kapsamda = (sayfa, kapsam) => new URL(sayfa).href.startsWith(kapsam);
console.log(kapsamda("https://example.com/js/panel.html", "https://example.com/js/")); // true
console.log(kapsamda("https://example.com/index.html", "https://example.com/js/")); // falseBir belge, farklı kapsamlı birden çok kaydın içine düşebilir. Bu durumda tarayıcı belgeyi en özel (en uzun) kapsama sahip kayıtla eşleştirir, böylece her belge için tek bir service worker çalışır. MDN yine de çakışan kapsamlar tanımlamamanın daha güvenli olduğunu not eder. Aynı kapsam ve betik adresiyle register() çağırmak kurulumu yeniden başlatmaz, mevcut kaydı döndürür; bu yüzden kontrol edilen bir sayfadan koşulsuz çağırmak genelde güvenlidir. Yine de her çağrı betik için bir ağ isteği gönderir; bu yük önemliyse önce getRegistration() ile mevcut kaydı kontrol edebilirsiniz.
Kayıt neden başarısız olur?
- Sayfa güvenli bağlamda değil (HTTPS ya da localhost yok).
- Betik yolu yanlış. Yol uygulamanın kök dizinine göre değil origin'e göre yazılmalıdır.
- Betik, uygulamadan farklı bir origin'de.
register()bu durumdaSecurityErrorfırlatır. scopeseçeneği betik konumunun izin verdiğinden geniş ve sunucuService-Worker-Allowedgöndermiyor.- Betik geçerli bir JavaScript MIME türüyle sunulmuyor.
- Tarayıcıda tüm çerezleri engelleme, gizli gezinme ya da kapanışta çerez silme gibi ayarlar açık.
- Adres
http:ya dahttps:dışında bir şema kullanıyor ya da yol%2Fveya%5Ciçeriyor; bu durumdaTypeErroralınır.
Yaşam döngüsü: indirme, kurulum, etkinleştirme

MDN yaşam döngüsünü üç adımda özetler: indirme, kurulum ve etkinleştirme. Kullanıcı service worker ile kontrol edilen bir sayfaya ilk geldiğinde betik hemen indirilir. Sonrasında betik iki durumda yeniden kontrol edilir: kapsam içindeki bir sayfaya gezinme olduğunda ve service worker'da bir olay tetiklendiğinde, eğer betik son 24 saatte indirilmemişse. İndirilen dosya mevcut olandan bayt bayt farklıysa ya da ilk kez görülüyorsa kurulum denenir.
install olayı her zaman ilk olaydır ve genellikle çevrimdışı çalışma için gereken dosyaları önbelleğe almak için kullanılır. event.waitUntil() içine verilen promise reddedilirse kurulum başarısız sayılır ve kurulmakta olan worker atılır. Bu iyi bir şeydir: önbellek eksik dolmuş bir worker devreye girmez, kodunuzu düzeltip bir sonraki kayıtta yeniden denersiniz.
// sw.js
const addResourcesToCache = async (resources) => {
const cache = await caches.open("v1");
await cache.addAll(resources);
};
self.addEventListener("install", (event) => {
event.waitUntil(
addResourcesToCache(["/", "/index.html", "/style.css", "/app.js"]),
);
});
self.addEventListener("activate", (event) => {
// Eski önbellekleri temizlemek için uygun an
});Kurulum başarılıysa ve bu ilk service worker ise, açık sayfaların kapanmasını beklemeden activate olayını alır. activate içinde waitUntil() ile verilen iş bitene kadar fetch ve push gibi işlevsel olaylar bekletilir; böylece veritabanı şemasını yükseltmek ya da eski önbellekleri silmek için güvenli bir an elde edersiniz.
Burada önemli bir ayrıntı var: etkinleşen ilk service worker, kayıttan önce açılmış sayfaları hemen kontrol etmez. Bir belge yaşamına service worker ile ya da onsuz başlar ve bunu ömrü boyunca korur. Mevcut sayfalar ancak yeniden yüklendiğinde kontrol altına girer. Bu varsayılanı değiştirmek için clients.claim() kullanılır.
| state değeri | Anlamı |
|---|---|
| parsed | İndirildi ve çalıştırılabilir olduğu doğrulandı; statechange ile hiç bildirilmez |
| installing | Kuruluyor; install içinde waitUntil ile süre uzatılabilir |
| installed | Kuruldu, bekleyen (waiting) worker |
| activating | Etkinleşiyor; işlevsel olaylar henüz gönderilmez |
| activated | Etkin, fetch ve push gibi olayları işlemeye hazır |
| redundant | Yerine yenisi geçiyor ya da kurulum başarısız olduğu için atıldı |
Bu değerler ServiceWorker.state özelliğinden okunur; değişimler statechange olayıyla izlenir. registration.installing, registration.waiting ve registration.active özellikleri de ilgili aşamadaki worker'ı verir.
Yeni sürüm neden bekler? skipWaiting() ve clients.claim()
Service worker dosyasını değiştirip yayınladığınızda yeni sürüm arka planda indirilir ve kurulur, ama hemen etkinleşmez. Eski sürüm, kontrol ettiği sayfalara hizmet vermeye devam eder; yeni sürüm "worker in waiting", yani bekleyen worker olur. Eski sürümün kontrol ettiği tüm sayfalar kapanıp bekleyen olayları bittiğinde yeni sürüm activate olayını alır. Bu bekleyiş, aynı anda iki farklı sürümün aynı önbelleği farklı varsayımlarla kullanmasını önler.
Bekleyişi atlamak için yeni sürüm self.skipWaiting() çağırır. MDN, bu yöntemin her an çağrılabildiğini ama yalnızca yeni kurulmuş ve bekleme durumunda kalacak bir worker varsa etkisi olduğunu, bu yüzden genellikle install işleyicisinde çağrıldığını belirtir. Dönen promise güvenle göz ardı edilebilir. clients.claim() ise etkin service worker'ın kapsamındaki tüm istemcileri hemen kontrol etmesini sağlar ve o istemcilerde navigator.serviceWorker üzerinde controllerchange olayını tetikler.
self.addEventListener("install", (event) => {
self.skipWaiting();
event.waitUntil(addResourcesToCache(["/", "/app.js"]));
});
self.addEventListener("activate", (event) => {
event.waitUntil(clients.claim());
});| Yöntem | Nerede çağrılır | Ne yapar |
|---|---|---|
| self.skipWaiting() | Genellikle install işleyicisinde | Bekleyen worker'ı etkin worker olmaya zorlar |
| clients.claim() | activate işleyicisinde, waitUntil içinde | Kapsamdaki açık sayfaları yeniden yüklemeden kontrol altına alır |
| registration.update() | Sayfa tarafında | Betiği yeniden getirir, bayt bayt farklıysa yeni worker'ı kurar |
Güncelleme akışını yönetmek
Güncelleme kontrolünü elle de tetikleyebilirsiniz. registration.update() betiği yeniden getirir ve yeni dosya bayt bayt aynı değilse kurar; önceki getirme 24 saatten eskiyse tarayıcı önbelleklerini atlar. Sayfa tarafında updatefound olayı, kaydın installing özelliği yeni bir worker edindiğinde tetiklenir. Bunu kullanıcıya "yeni sürüm hazır" demek için kullanabilirsiniz.
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
registration.addEventListener("updatefound", () => {
const yeni = registration.installing;
yeni.addEventListener("statechange", () => {
if (yeni.state === "installed" && navigator.serviceWorker.controller) {
// Yeni sürüm kuruldu ve bekliyor: kullanıcıya yenileme önerin
}
});
});
}
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("The controller of current browsing context has changed.");
});navigator.serviceWorker.controller sayfayı şu anda kontrol eden worker'ı verir; ilk ziyarette null olduğu için yukarıdaki kontrol, "ilk kurulum" ile "güncelleme" durumunu ayırmaya yarar. navigator.serviceWorker.ready ise hiçbir zaman reddedilmeyen ve sayfanın kaydında etkin bir worker olana kadar bekleyen bir promise'tir; etkin worker gerektiren işler (örneğin kalıcı bildirim göstermek) için kullanılır.
MDN'in CycleTracker eğitimi, service worker dosyasında bir sürüm sabiti tutmayı önerir. Gerekçe şudur: HTML, CSS ya da resimler değiştiğinde tarayıcı bunu kendiliğinden fark etmez; yalnızca service worker dosyası değiştiğinde güncelleme süreci başlar. Sürüm sabitini artırmak dosyayı değiştirir ve önbellek adını da yeniler. Eski önbellekleri activate içinde silmek için önbellek adlarını filtreleyen mantığı Node.js ile denedik:
const SURUM = "v3";
const ONBELLEK_ADI = `uygulama-${SURUM}`;
const mevcutlar = ["uygulama-v1", "uygulama-v2", "uygulama-v3", "resimler"];
const silinecekler = mevcutlar.filter(
(ad) => ad.startsWith("uygulama-") && ad !== ONBELLEK_ADI,
);
console.log(JSON.stringify(silinecekler)); // ["uygulama-v1","uygulama-v2"]Gerçek service worker'da bu liste await caches.keys() ile alınır ve silme caches.delete(ad) ile yapılır. Kaydı tamamen kaldırmak gerekirse registration.unregister() kullanılır; promise, kayıt bulunamadıysa false, bulunduysa true ile çözülür.
Service worker olayları
Service worker'ın aldığı olaylar iki gruba ayrılır. Yaşam döngüsü olayları install ve activate'tir. İşlevsel olaylar ise fetch (her ağ isteğinde), sync (arka plan senkronizasyonu) ve push (sunucudan gelen push mesajı) olaylarıdır. Bunlara ek olarak sayfalarla konuşmak için message, bildirim tıklamaları için notificationclick vardır. Push akışı push bildirim yazısında anlatılıyor.
MDN ayrıca bir performans ayrıntısına değinir: uzun süre ziyaret edilmeyen bir sayfa açıldığında tarayıcı, içeriğin önbellekten mi ağdan mı geleceğini öğrenmek için service worker'ın başlamasını beklemek zorunda kalabilir. Gezinme ön yüklemesi (navigation preload) ve InstallEvent.addRoutes() ile statik yönlendirme bu gecikmeyi azaltmak için tasarlanmıştır; tarayıcı desteği için MDN uyumluluk tablosuna bakın.
Sık yapılan hatalar

- Service worker dosyasını
/js/sw.jsaltına koyup tüm siteyi kontrol etmesini beklemek. Varsayılan kapsam betiğin dizinidir. - Yeni sürümü yayınlayıp hemen devreye girmesini beklemek. Açık sekmeler kapanana kadar yeni sürüm bekler.
- Kurulumda
waitUntil()kullanmamak. Promise beklenmezse önbellek dolmadan worker kurulmuş sayılabilir. - Service worker içinde
localStoragekullanmaya çalışmak. Senkron Web Storage burada yoktur; IndexedDB kullanın. - Global değişkende oturum durumu saklamak. Tarayıcı boşta kalan service worker'ı durdurup yeniden başlatabilir.
- Service worker dosyasının kendisini önbellek listesine eklemek. CycleTracker eğitimi bunu açıkça yapmamayı söyler.
- Kullanıcıdan gelen bir adresi
register()çağrısına vermek. MDN bu parametreyi XSS açısından riskli bir giriş noktası olarak işaretler;worker-srcCSP yönergesiyle betik kaynaklarını sınırlayın.
Sık Sorulan Sorular
Service worker localhost'ta çalışır mı?
Evet. Tarayıcılar http://localhost adresini güvenli bağlam olarak kabul eder, bu yüzden yerel geliştirmede HTTPS sertifikası gerekmez. Yayına aldığınızda ise site HTTPS üzerinden sunulmalıdır.
Service worker'ı değiştirdim ama tarayıcı eskisini kullanıyor, neden?
Yeni sürüm kurulmuş ve bekliyor olabilir. Eski sürümün kontrol ettiği tüm sekmeleri kapatın ya da yeni sürümde skipWaiting() kullanın. Ayrıca betiğin HTTP önbelleğinden gelip gelmediğini updateViaCache seçeneği ve önbellek başlıklarıyla kontrol edin; başlıklar için Cache-Control yazısına bakın.
Bir sitede birden çok service worker olabilir mi?
Farklı kapsamlarla birden çok kayıt yapılabilir, ancak bir belgeyi her zaman tek bir service worker kontrol eder: en özel kapsama sahip olan. MDN çakışan kapsamlardan kaçınmayı önerir.
PWA olmak için service worker şart mı?
MDN'e göre bir PWA'nın kurulabilir olması için service worker gerekmez, ama çoğu PWA en azından basit bir çevrimdışı deneyim için service worker kullanır. Kurulabilirlik koşulları PWA ve web app manifest yazısında.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · Çevrimdışı önbellek stratejileri · Cache API kullanımı · Web Workers · PWA ve manifest · Karma içerik hatası