Service worker adalah file JavaScript yang dijalankan browser di belakang layar, terpisah dari halaman, dan berdiri di antara halaman dan network. Setiap request dari halaman lewat dulu di dia, lalu dia yang memutuskan: jawab dari cache, ambil dari network, atau dua-duanya. Bayangin asisten di rumah yang kamu titipi belanja. Kamu minta gula, dan asisten ngikutin aturan yang kamu kasih: cek lemari dapur dulu, atau langsung ke warung, atau kasih yang ada di lemari sekarang sambil beli yang baru buat nanti.
🧪 Tebakan cepat: situsmu pakai service worker dengan strategi cache first untuk
/css/main.css. Kamu deploy CSS baru dengan nama file yang sama. Pengunjung lama buka lagi situsnya. Dia dapat CSS lama atau baru? Tebak dulu, jawabannya ada di bagian "Versi cache dan update".
Istilah dulu
- Service worker: file JS (biasanya
sw.js) yang gak punya akses ke DOM. Kerjanya cuma mendengarkan event, terutama eventfetch, yaitu setiap kali halaman meminta file atau data. - Cache Storage: tempat menyimpan pasangan request dan response di browser, diakses lewat objek
caches. Bedanya dengan HTTP cache biasa: isinya kamu yang atur lewat kode. - Strategi caching: aturan "cek cache dulu atau network dulu" untuk tiap jenis request.
- Service worker cuma jalan di HTTPS, kecuali di
localhostbuat development. Alasannya: dia bisa mengganti semua response, jadi harus dari sumber yang terjamin.
Siklus hidup: register, install, activate, fetch
Halaman mendaftarkan service worker dengan register. Setelah itu service worker melewati tiga event, selalu berurutan install → activate → fetch:
- install: service worker baru dipasang. Di sini biasanya file penting disimpan duluan ke cache (precache).
- activate: service worker mulai berkuasa. Di sini cache versi lama dihapus.
- fetch: sejak aktif, setiap request dari halaman lewat event ini.
Kalau sudah ada service worker lama yang aktif, versi baru selesai install lalu menunggu (waiting) sampai semua tab yang masih dipegang versi lama ditutup. self.skipWaiting() di install membuatnya langsung aktif, dan clients.claim() di activate membuatnya langsung memegang tab yang sudah terbuka.
Tiga strategi, dicoba di Lab
Lab di halaman ini mengirim request lewat service worker. Waktunya simulasi yang masuk akal: dari cache sekitar 5 ms, dari network sekitar 300 ms. Ini tiga request berturut-turut per strategi, dimulai dari cache kosong:
| Strategi | Request 1 (online) | Request 2 (online) | Request 3 (offline) |
|---|---|---|---|
| Cache first | network, 300 ms, lalu disimpan | cache, 5 ms | cache, 5 ms |
| Network first | network, 300 ms, lalu disimpan | network, 300 ms, data terbaru | cache, 5 ms, sebagai cadangan |
| Stale-while-revalidate | network, 300 ms, lalu disimpan | cache, 5 ms, lalu cache diperbarui di background | cache, 5 ms |
- Cache first: cocok untuk aset statis yang jarang berubah: CSS, JS, font, gambar. Paling cepat, tapi bisa basi.
- Network first: cocok untuk data yang harus terbaru: data API, halaman HTML, saldo. Cache cuma cadangan saat offline.
- Stale-while-revalidate: cocok untuk yang boleh sedikit basi: avatar, profil, daftar kategori. Cepat seperti cache first, dan kunjungan berikutnya dapat versi baru.
Kodenya: sw.js dengan cache first
// sw.js
const CACHE = 'static-v1';
self.addEventListener('install', (event) => {
// precache: simpan file penting sebelum dibutuhkan
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(['/', '/css/main.css', '/js/app.js'])));
});
self.addEventListener('activate', (event) => {
// hapus semua cache yang namanya bukan versi sekarang
event.waitUntil(
caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE).map((key) => caches.delete(key))))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(cacheFirst(event.request));
});
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached; // HIT: gak ke network sama sekali
const response = await fetch(request);
const cache = await caches.open(CACHE);
cache.put(request, response.clone()); // body response cuma bisa dibaca sekali
return response;
}
Di halaman: if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');
event.waitUntil(...)menahan install atau activate sampai Promise-nya selesai.event.respondWith(...)mengambil alih jawaban request. Tanpa ini, request jalan ke network seperti biasa.caches.matchmencari di semua cache; hasilnyaundefinedkalau gak ada.response.clone(): satu salinan disimpan ke cache, satu lagi dikirim ke halaman.
Versi latihan: cache-nya sebuah Map
Di latihan, cache diganti Map (key url, value data), dan network pura-pura: hasilnya selalu "fetched:" + url. Tiap method mengembalikan { source, data }, dengan urutan key persis source lalu data:
cacheFirst(url, cache) {
if (cache.has(url)) return { source: 'cache', data: cache.get(url) };
const fresh = 'fetched:' + url;
cache.set(url, fresh);
return { source: 'network', data: fresh };
}
cacheFirst('/css/main.css', cache) dua kali dengan Map yang sama: pertama { source: 'network', data: 'fetched:/css/main.css' }, kedua { source: 'cache', data: 'fetched:/css/main.css' }. Kalau Map sudah berisi data lain, misalnya 'cached-font', cache first mengembalikan isi itu apa adanya. Network first di latihan ini selalu online, jadi selalu source: 'network' dan tetap menyimpan ke cache. Stale-while-revalidate mengembalikan isi cache lama dulu, lalu mengisi cache dengan 'fetched:' + url.
Versi cache dan update
Pengunjung lama tetap dapat CSS lama. Itu jawaban tebakannya. Cache first menemukan /css/main.css di cache, jadi network gak pernah ditanya, dan URL yang sama berarti cache yang sama, selamanya.
Cara keluarnya dua, biasanya dipakai bareng:
- Nama file ber-hash:
main.3f9a1c.css. Isi berubah, hash berubah, URL-nya baru, jadi pasti ke network. - Versi nama cache: ganti
static-v1jadistatic-v2disw.js. Browser mengeceksw.jssetiap halaman dibuka; beda satu byte saja sudah dianggap service worker baru. Versi baru install, menunggu, lalu di activate menghapus cachestatic-v1.
Workbox: gak perlu menulis semuanya sendiri
Di project nyata, strategi ini biasanya ditulis pakai Workbox, library dari Google (dipakai juga oleh vite-plugin-pwa):
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
registerRoute(({ request }) => request.destination === 'style', new CacheFirst());
registerRoute(({ url }) => url.pathname.startsWith('/api/'), new NetworkFirst({ networkTimeoutSeconds: 3 }));
registerRoute(({ request }) => request.destination === 'image', new StaleWhileRevalidate());
⚠️ Jebakan yang sering ketemu
- Cache first untuk data API atau HTML. User terus melihat data lama, karena network gak pernah ditanya. Pakai network first atau stale-while-revalidate.
- Gak pernah ganti versi cache. Cache lama menumpuk dan file lama terus disajikan. Ganti nama cache dan hapus yang lama di
activate. - Lupa
response.clone(). Body response sudah dibaca waktu disimpan, jadi halaman dapat error "body already used". - Network first tanpa batas waktu. Di jaringan yang lemot (bukan mati), user menunggu lama sebelum cadangan cache dipakai. Workbox punya
networkTimeoutSeconds. - Di latihan: urutan key harus
sourcelaludata, dan data dari cache dikembalikan apa adanya, jangan dibuat ulang jadi'fetched:' + url.
TL;DR: Service worker adalah JS di belakang layar yang mencegat setiap request lewat event fetch. Siklusnya: halaman register, lalu install (precache), activate (hapus cache lama), fetch (mencegat). Versi baru menunggu sampai tab lama ditutup, kecuali pakai skipWaiting dan clients.claim. Cache first untuk aset statis (cepat, bisa basi), network first untuk data yang harus terbaru (cache jadi cadangan offline), stale-while-revalidate untuk yang boleh sedikit basi. Pakai nama file ber-hash dan versi nama cache supaya update sampai ke user. Wajib HTTPS, kecuali localhost. Di project nyata, pakai Workbox.