Service worker ile çevrimdışı çalışma, fetch olayını dinleyip event.respondWith() ile yanıtı kendiniz vermenize dayanır. Yanıtı önce önbellekten mi ağdan mı alacağınızı seçen algoritmaya önbellek stratejisi denir. MDN'in PWA rehberi üç temel strateji anlatır: cache first (önce önbellek), stale while revalidate (önbellekten ver, arkada yenile) ve network first (önce ağ). Doğru seçim kaynağın türüne bağlıdır ve bir uygulama çoğu zaman hepsini birlikte kullanır.
Bu yazıda fetch olayının nasıl çalıştığını, respondWith() kurallarını, her stratejinin kodunu ve ne zaman uygun olduğunu, yanıtın neden klonlanması gerektiğini ve yedek (fallback) sayfayı anlatıyoruz. Service worker kaydı ve yaşam döngüsü service worker yazısında, Cache API yöntemleri Cache API yazısında. Tüm konuların haritası Web API öğrenme rehberinde.
Üç denge: çevrimdışı, hız ve tazelik

MDN önbelleğe almanın iki temel kazancını şöyle sıralar: cihazın ağ bağlantısı yokken uygulamanın az ya da çok çalışabilmesi ve bağlantı olsa bile arayüz önbellekten geldiği için çok daha hızlı yanıt vermesi. Bedeli ise tazeliktir: güncel olması gereken kaynaklar için önbellek daha az uygundur. POST gibi bazı istek türleri için önbellek hiçbir zaman uygun değildir.
Bu yüzden strateji kaynak bazında seçilir. Uygulamanın temel arayüzü (HTML iskeleti, ana CSS ve JavaScript) bir sürüm boyunca değişmez ve hızlı gelmesi gerekir. Ürün listesi ya da mesaj kutusu gibi veriler ise güncel olmalıdır. Önbelleğe alma için kullanılan üç teknoloji Fetch API, Service Worker API ve Cache API'dir; HTTP önbelleğinin kendi kuralları için Cache-Control ve ETag yazısına, önbellek kavramının genel anlatımı için önbellek nedir yazısına bakabilirsiniz.
fetch olayı ve respondWith() kuralları
Service worker etkinleştikten sonra, kontrol ettiği uygulama bir ağ kaynağı istediğinde tarayıcı service worker'ın global kapsamında fetch olayını tetikler. Bu yalnızca ana koddaki açık fetch() çağrıları için değil, sayfa gezinmesi ve sayfanın yüklediği JavaScript, CSS, resim gibi alt kaynaklar için de geçerlidir. Sayfa başka bir origin'den resim gömse bile istek service worker'dan geçer.
İşleyiciye gelen FetchEvent nesnesi isteği event.request olarak verir ve respondWith() yöntemini sunar. respondWith() tarayıcının varsayılan getirme davranışını durdurur ve sizden bir Response ya da Response ile çözülen bir promise bekler; başka bir şey verirseniz Fetch tarafına ağ hatası döner. Bir istek için respondWith() çağırmazsanız istek, service worker hiç araya girmemiş gibi ağa gider. Bu, yalnızca belirli adresleri yakalayıp gerisini ağa bırakmanın en basit yoludur.
MDN güvenlik nedeniyle birkaç genel kural koyar. type değeri "opaque" olan bir yanıtı yalnızca isteğin mode değeri "no-cors" ise döndürebilirsiniz. "opaqueredirect" yanıtı yalnızca mode "manual" ise döndürülebilir. mode "same-origin" olan bir isteğe "cors" türünde yanıt verilemez. Bu kurallar çiğnenirse NetworkError, respondWith() ikinci kez çağrılırsa InvalidStateError alınır. İstek modları ve yanıt türleri Request, Response ve Headers yazısında anlatılıyor.
self.addEventListener("fetch", (event) => {
event.respondWith(caches.match(event.request));
});Yukarıdaki en kısa sürüm yalnızca önbellekte olanı verir. caches.match() eşleşme bulamazsa undefined ile çözülür ve istek hiçbir yanıt alamaz. Gerçek bir uygulamada her zaman bir yedek yol gerekir.
Ne zaman önbelleğe alınır?
MDN dört tipik anı sayar. Birincisi, service worker'ın install işleyicisinde ön önbelleğe alma (precaching): kesin gerekecek dosyalar kurulumda ağdan alınıp saklanır. Burada unutulmaması gereken nokta, service worker kurulumunun PWA kurulumuyla aynı şey olmamasıdır; kullanıcı siteyi hiç uygulama olarak kurmasa bile service worker ilk ziyarette kurulur. İkincisi fetch işleyicisinde, ağdan gelen yanıtı bir sonraki sefer için saklamak. Üçüncüsü kullanıcının isteğiyle, örneğin bir müzik uygulamasının parçaları sonra dinlemek üzere indirmesi; büyük dosyalarda Background Fetch API kullanılabilir. Dördüncüsü Periodic Background Sync ile periyodik yenileme. Son iki API'nin tarayıcı desteği için MDN uyumluluk tablosuna bakın.
const cacheName = "MyCache_1";
const precachedResources = ["/", "/app.js", "/style.css"];
async function precache() {
const cache = await caches.open(cacheName);
return cache.addAll(precachedResources);
}
self.addEventListener("install", (event) => {
event.waitUntil(precache());
});Strateji 1: cache first (önce önbellek)
Ön önbelleğe alınan kaynaklar için önce önbelleğe bakılır, bulunursa döndürülür. Bulunamazsa ağa gidilir; ağ isteği başarılıysa yanıt bir sonraki sefer için saklanır. Ön önbellekte olmayan diğer tüm istekler doğrudan ağa gider. Bu strateji uygulamanın temel arayüzü gibi kesin gereken, bu sürümde değişmeyecek ve olabildiğince hızlı gelmesi gereken kaynaklar için uygundur.
async function cacheFirst(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open("MyCache_1");
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
return Response.error();
}
}
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (precachedResources.includes(url.pathname)) {
event.respondWith(cacheFirst(event.request));
}
});"Zaten ön önbellekte olan bir kaynak için neden ağa geri dönülüyor?" sorusunun yanıtını MDN verir: önbellek tarayıcı ya da kullanıcı tarafından temizlenebilir. Bu nadir olsa da ağ yolu olmazsa uygulama kullanılamaz hâle gelir. Cache first'ün dezavantajı ise yanıt bir kez önbelleğe girdikten sonra yeni bir service worker sürümü kurulana kadar hiç yenilenmemesidir.
Strateji mantığını tarayıcı dışında görebilmek için önbelleği bir Map, ağı da bir fonksiyon olarak dışarıdan veren küçük bir sürüm yazdık ve Node.js 24 ile çalıştırdık. Response nesnesi her iki ortamda aynı standarda göre çalışır; gerçek service worker'da Map yerine caches, fonksiyon yerine fetch kullanılır.
// Önbellek ve ağ dışarıdan verilir; böylece mantık tarayıcı dışında da denenebilir.
async function cacheFirst(istek, onbellek, ag) {
const kayitli = onbellek.get(istek);
if (kayitli) return kayitli.clone();
try {
const agYaniti = await ag(istek);
if (agYaniti.ok) onbellek.set(istek, agYaniti.clone());
return agYaniti;
} catch (err) {
return new Response("Çevrimdışısınız", { status: 503 });
}
}
const onbellek = new Map();
let cevrimici = true;
const ag = async (istek) => {
if (!cevrimici) throw new TypeError("ağ yok");
return new Response(`ağdan: ${istek}`);
};
console.log(await (await cacheFirst("/app.js", onbellek, ag)).text()); // ağdan: /app.js
cevrimici = false;
console.log(await (await cacheFirst("/app.js", onbellek, ag)).text()); // ağdan: /app.js
const yok = await cacheFirst("/yeni.js", onbellek, ag);
console.log(yok.status, await yok.text()); // 503 ÇevrimdışısınızStrateji 2: stale while revalidate (önbellekten ver, arkada yenile)
MDN bu stratejiye "cache first with cache refresh" de der. Cache first'e benzer, farkı şudur: önbellekte bulunsa bile istek her seferinde ağa da gönderilir ve gelen yanıt önbelleği yeniler. Böylece cache first'ün hızını korurken oldukça taze yanıtlar elde edersiniz, yeter ki kaynak sık istensin. Yanıt hızının önemli, tazeliğin önemli ama zorunlu olmadığı kaynaklar için iyi bir seçimdir.
function isCacheable(request) {
const url = new URL(request.url);
return !url.pathname.endsWith(".json");
}
async function cacheFirstWithRefresh(request) {
const fetchResponsePromise = fetch(request).then(async (networkResponse) => {
if (networkResponse.ok) {
const cache = await caches.open("MyCache_1");
cache.put(request, networkResponse.clone());
}
return networkResponse;
});
return (await caches.match(request)) || (await fetchResponsePromise);
}
self.addEventListener("fetch", (event) => {
if (isCacheable(event.request)) {
event.respondWith(cacheFirstWithRefresh(event.request));
}
});Önbellek güncellemesi bir then() işleyicisinde asenkron yapılır; uygulama ağ yanıtını beklemeden önbellekteki sürümü kullanır. Bu örnekte JSON dosyaları stratejinin dışında bırakılmıştır, yani her zaman ağdan gelir. Kullanıcı bir sonraki ziyarette yenilenmiş sürümü görür.
Strateji 3: network first (önce ağ)

Cache first'ün tersidir: önce ağ denenir; başarılıysa yanıt döndürülür ve önbellek güncellenir, ağ başarısızsa önbelleğe bakılır. En taze yanıtın önemli olduğu ama eski bir yanıtın hiç yanıt olmamasından iyi olduğu durumlar için uygundur. MDN'in örneği bir mesajlaşma uygulamasının son mesajlar listesidir.
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open("MyCache_1");
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
return cachedResponse || Response.error();
}
}
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (url.pathname.match(/^\/inbox/)) {
event.respondWith(networkFirst(event.request));
}
});Bazı istekler için eski bir yanıt hiç yanıt olmamasından daha kötüdür. MDN'in örneğine göre mevcut ürün listesini gösteren bir sayfada güncel olmayan liste kullanıcıyı yanıltır; bu tür istekler için "network only" (yalnızca ağ) doğru seçimdir, yani respondWith() hiç çağrılmaz. Bir uygulamada stratejiler genellikle adrese göre dağıtılır. Aşağıdaki seçici fonksiyon bu yazıdaki örneklerin kurallarını bir araya getirir; URL nesnesi tarayıcıdan bağımsız olduğu için Node.js ile çalıştırdık:
const onbellekten = ["/", "/app.js", "/style.css"];
function stratejiSec(adres) {
const url = new URL(adres);
if (onbellekten.includes(url.pathname)) return "cache-first";
if (url.pathname.match(/^\/inbox/)) return "network-first";
if (url.pathname.endsWith(".json")) return "network-only";
return "stale-while-revalidate";
}
console.log(stratejiSec("https://example.com/app.js")); // cache-first
console.log(stratejiSec("https://example.com/inbox/42")); // network-first
console.log(stratejiSec("https://example.com/api/urunler.json")); // network-only
console.log(stratejiSec("https://example.com/img/logo.png")); // stale-while-revalidate| Strateji | Önce | Ağ yoksa | Uygun kaynak |
|---|---|---|---|
| Cache first | Önbellek | Önbellekteki yanıt | Uygulama kabuğu, sürüme bağlı statik dosyalar |
| Stale while revalidate | Önbellek, arkada ağ | Önbellekteki yanıt | Hızın önemli, tazeliğin önemli ama zorunlu olmadığı kaynaklar |
| Network first | Ağ | Önbellekteki son yanıt | Mesaj listesi, haber akışı |
| Network only | Ağ | Hata | Güncel ürün listesi gibi eski hâli yanıltıcı veriler, POST istekleri |
Yanıt neden klonlanır?
Bütün stratejilerde ağdan gelen yanıt önbelleğe networkResponse.clone() olarak konur, asıl yanıt sayfaya döndürülür. Bunun nedeni Response gövdesinin bir akış olması ve yalnızca bir kez okunabilmesidir. Aynı yanıtı hem önbelleğe hem sayfaya vermek için iki kopya gerekir; her biri bir kez okunur. Bu davranışı Node.js ile doğruladık:
const yanit = new Response("merhaba", { headers: { "Content-Type": "text/plain" } });
const kopya = yanit.clone();
console.log(await yanit.text()); // merhaba
console.log(yanit.bodyUsed); // true
console.log(await kopya.text()); // merhaba
try {
await yanit.text();
} catch (err) {
console.log(err.name); // TypeError
}İkinci okumada fırlatılan hata TypeError türündedir. Klonlama, gövde okunmadan önce yapılmalıdır; gövdesi tüketilmiş bir yanıt klonlanamaz. Gövde yöntemlerinin tamamı Fetch API Request ve Response yazısında var.
MDN'in service worker rehberindeki bir başka ayrıntı da önbelleğe yazma işinin beklenmemesidir. Sayfaya yanıt vermeden önce önbelleğe yazmanın bitmesini beklemek gereksiz gecikme yaratır. Ancak service worker'ın önbellek dolmadan sonlandırılmaması için bu promise event.waitUntil() ile tarayıcıya bildirilir:
const putInCache = async (request, response) => {
const cache = await caches.open("v1");
await cache.put(request, response);
};
const cacheFirst = async (request, event) => {
const responseFromCache = await caches.match(request);
if (responseFromCache) {
return responseFromCache;
}
const responseFromNetwork = await fetch(request);
event.waitUntil(putInCache(request, responseFromNetwork.clone()));
return responseFromNetwork;
};
self.addEventListener("fetch", (event) => {
event.respondWith(cacheFirst(event.request, event));
});Yedek sayfa ve hata yanıtları
MDN'in PWA en iyi uygulamaları rehberi, bir PWA'nın en azından tarayıcının genel hata sayfası yerine kendi çevrimdışı sayfasını göstermesini önerir. Bunun için yedek dosya kurulumda önbelleğe alınır ve ağ başarısız olduğunda onunla yanıt verilir. Yedek de bulunamazsa yine bir Response döndürülmelidir, çünkü respondWith() bir yanıt bekler. MDN'in örneği bu son durumda 408 durum kodlu düz metin yanıt üretir. Bir diğer seçenek Response.error()'dır: türü error olan bir yanıt üretir ve sayfadaki fetch() çağrısının reddedilmesine yol açar.
const hata = Response.error();
console.log(hata.type); // error
console.log(hata.status); // 0
console.log(hata.ok); // false
const yedek = new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" },
});
console.log(yedek.status); // 408
console.log(yedek.ok); // false
console.log(yedek.headers.get("content-type")); // text/plainTek sayfalık uygulamalarda CycleTracker eğitimi farklı bir yol izler: event.request.mode === "navigate" olan, yani bir sayfa arayan istekleri her zaman önbellekteki ana sayfaya yönlendirir; diğer isteklerde önbellekte yoksa new Response(null, { status: 404 }) döndürür. Bu yaklaşım ağa hiç gitmez ve uygulamanın tüm gereksinimlerinin önbellekte olduğunu varsayar. Durum kodlarının anlamları HTTP durum kodları yazısında.
self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") {
event.respondWith(caches.match("./"));
return;
}
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request.url);
if (cachedResponse) {
return cachedResponse;
}
return new Response(null, { status: 404 });
})(),
);
});Eski önbellekleri silmek ve kota
Önbellek alanı sınırlıdır ve sınır aşılırsa tarayıcı uygulamanın önbelleğe alınmış verisini silebilir. MDN, pratikte bunun çok nadir olduğunu ama kullanıcının önbelleği her an temizleyebileceğini vurgular; sınırlar ve silme kuralları için depolama kotası yazısına bakın. Cache API'deki öğeler kendiliğinden güncellenmez ve süreleri dolmaz; siz silene kadar kalır. Bu yüzden eski sürümlerin önbellekleri activate olayında temizlenmelidir. O anda eski service worker sürümlerinin çalışmadığı kesindir, dolayısıyla eski veri artık gerekmez.
const deleteOldCaches = async () => {
const cacheKeepList = ["v2"];
const keyList = await caches.keys();
const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key));
await Promise.all(cachesToDelete.map((key) => caches.delete(key)));
};
self.addEventListener("activate", (event) => {
event.waitUntil(deleteOldCaches());
});Sık yapılan hatalar

caches.match()sonucunu kontrol etmedenrespondWith()çağırmak. Eşleşme yoksaundefineddöner ve istek yanıtsız kalır.- Ağ yanıtını klonlamadan hem önbelleğe hem sayfaya vermek. Gövde bir kez okunabilir.
- Başarısız yanıtları önbelleğe almak. MDN örnekleri yalnızca
networkResponse.okdoğruysa saklar. - POST isteklerini önbelleğe almaya çalışmak. MDN bu tür istekler için önbelleğin hiç uygun olmadığını belirtir.
- Ürün listesi gibi eski hâli yanıltıcı verileri cache first ile sunmak. Bunlar için network only ya da network first seçin.
- Önbelleğe yazma promise'ini
waitUntil()ile bildirmemek. Service worker iş bitmeden durdurulabilir. - Eski önbellek sürümlerini hiç silmemek. Kayıtlar kendiliğinden süresi dolmadığı için alan dolmaya devam eder.
Sık Sorulan Sorular
Hangi stratejiyi seçmeliyim?
Tek bir doğru yoktur. MDN'in önerisine göre uygulama kabuğu için cache first, sık istenen ama tazeliği kritik olmayan kaynaklar için stale while revalidate, güncel olması gereken ama eski hâlinin de işe yaradığı veriler için network first, eski hâli yanıltıcı olan veriler için network only kullanılır.
respondWith() çağırmazsam ne olur?
İstek, service worker hiç araya girmemiş gibi ağa gider. Bu yüzden yalnızca belirli adresler için respondWith() çağırmak, geri kalan trafiği etkilemeden strateji uygulamanın en temiz yoludur.
Önbellekteki dosyaları nasıl güncellerim?
Service worker dosyasını değiştirin, örneğin bir sürüm sabitini artırın. Yeni sürüm kurulumda yeni adlı önbelleği doldurur, etkinleştiğinde eskisini siler. Yalnızca HTML ya da CSS'i değiştirmek service worker güncellemesini tetiklemez.
Önbellekteki yanıt HTTP Cache-Control başlığına uyar mı?
Cache API'deki kayıtlar siz silene ya da güncelleyene kadar kalır ve kendiliğinden süresi dolmaz. HTTP önbelleğinin kuralları ayrı bir katmandır; ikisini karıştırmamak için Cache-Control yazısına bakın.
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 · Service worker kaydı ve yaşam döngüsü · Cache API kullanımı · PWA ve web app manifest · Depolama kotası · Önbellek nedir