Service Worker Caching — Performance

Service Worker Caching Service Worker adalah JavaScript yang berjalan di background thread terpisah dari halaman. Ia bertindak sebagai proxy antara aplikasi…

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

Siklus hidup: register, install, activate, fetch

Halaman mendaftarkan service worker dengan register. Setelah itu service worker melewati tiga event, selalu berurutan install → activate → fetch:

  1. install: service worker baru dipasang. Di sini biasanya file penting disimpan duluan ke cache (precache).
  2. activate: service worker mulai berkuasa. Di sini cache versi lama dihapus.
  3. 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

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');

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:

  1. Nama file ber-hash: main.3f9a1c.css. Isi berubah, hash berubah, URL-nya baru, jadi pasti ke network.
  2. Versi nama cache: ganti static-v1 jadi static-v2 di sw.js. Browser mengecek sw.js setiap halaman dibuka; beda satu byte saja sudah dianggap service worker baru. Versi baru install, menunggu, lalu di activate menghapus cache static-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

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.

Yang akan kamu pelajari