Tips & Tricks (Diperbarui: 2/6/2026)

Service Worker dengan Claude Code: cache, update, dan offline UX

Panduan praktis Service Worker dengan Claude Code: cache, lifecycle update, UX offline, pitfall, dan contoh jalan.

Service Worker dengan Claude Code: cache, update, dan offline UX

Service Worker adalah fitur penting untuk PWA dan dukungan offline, tetapi mudah menjadi bug produksi kalau dipasang tanpa batas. Jika kamu meminta Claude Code “tambahkan cache” begitu saja, aplikasi bisa menyimpan HTML lama, menyimpan data privat di browser, atau tetap menampilkan versi lama setelah deploy.

Cara paling mudah memahaminya: Service Worker adalah proxy kecil di antara browser dan server. Ia bisa mencegat request yang memenuhi syarat, lalu memilih response dari network, Cache API, atau halaman offline. Panduan ini membahas keputusan sebelum implementasi, contoh yang bisa dijalankan, invalidasi cache, lifecycle update, UX offline, dan pitfall nyata.

Gunakan referensi resmi saat implementasi: MDN Service Worker API, panduan service worker web.dev, panduan cache web.dev, dan Chrome Workbox docs. Untuk konteks ClaudeCodeLab, lanjutkan ke panduan PWA, strategi caching, dan panduan IndexedDB.

Apa yang dilakukan Service Worker

Service Worker adalah JavaScript yang berjalan di luar halaman. Ia tidak bisa menyentuh DOM secara langsung, jadi tidak bisa mengubah tombol, form, atau React state. Yang bisa ia lakukan adalah mencegat request dan memilih apakah response berasal dari network, cache, atau fallback offline.

Script halaman biasa berakhir saat tab ditutup. Service Worker bersifat event-driven: browser membangunkannya untuk install, activate, fetch, push, dan kadang background sync. Untuk tahap awal, fokus pada fetch, Cache API, dan lifecycle update.

sequenceDiagram
  participant User as Pengguna
  participant Page as Halaman
  participant SW as Service Worker
  participant Cache as Cache API
  participant Net as Server
  User->>Page: Membuka situs
  Page->>SW: Mendaftarkan /sw.js
  Page->>SW: Mengirim fetch request
  SW->>Cache: Mengecek cache
  alt Ada cache
    Cache-->>SW: Response tersimpan
  else Tidak ada cache
    SW->>Net: Mengambil response baru
    Net-->>SW: Response segar
  end
  SW-->>Page: Response untuk render

Jadi Service Worker bukan sihir performa, melainkan pengatur lalu lintas request. Kualitasnya bergantung pada apa yang disimpan, kapan dihapus, dan apa yang dilihat user saat network gagal.

Use case realistis

Use caseManfaatPerhatian
Dokumentasi atau blogArtikel, CSS, gambar, dan font lebih cepat saat kunjungan ulangHTML yang terlalu lama di-cache menutup koreksi
Dashboard SaaSNavigasi dan kerangka UI tetap muncul di jaringan lemahJangan cache billing, account, atau response privat
Form lapanganUser tetap punya draft dan job pending saat offlinePOST masuk IndexedDB queue, bukan Cache API
Katalog ecommerce atau mediaThumbnail dan asset tidak diunduh berulangHarga, stok, dan gambar terlindungi perlu aturan freshness

Masa menguji pola ini di situs belajar kecil. Caching gambar dan font membuat kunjungan kedua terasa lebih ringan. Namun HTML artikel yang dibuat Cache First membuat koreksi typo terlambat sampai ke pembaca. Untuk Claude Code, brief yang aman bukan “cache semuanya”, melainkan “cache resource ini, dengan umur ini, dan exclude bagian ini”.

Brief untuk Claude Code

Masukkan larangan dan cara verifikasi dalam prompt.

Tambahkan Service Worker ke app Vite yang sudah ada.

Requirement:
- Letakkan /sw.js di root public dan gunakan scope /
- Cache hanya static asset dengan method GET
- Gunakan Network First untuk navigasi HTML
- Kembalikan /offline.html jika navigasi gagal saat offline
- Jangan cache API, POST, halaman auth, atau origin lain
- Sertakan tanggal atau versi di nama cache
- Hapus cache lama saat activate
- Tampilkan prompt reload saat worker baru berada di waiting

Verifikasi:
- Cek Chrome DevTools > Application > Service Workers
- Ubah Network ke Offline dan pastikan /offline.html muncul
- Ubah CACHE_VERSION dan pastikan cache lama terhapus

Exclusion ini penting. Bug cache pada API atau halaman privat bisa berubah menjadi bug data.

Implementasi minimal yang bisa dijalankan

Letakkan empat file ini di folder kosong seperti sw-demo, lalu jalankan server lokal. Service Worker butuh HTTPS atau localhost; membuka file HTML langsung tidak cukup.

python -m http.server 5173

Buka http://localhost:5173.

<!-- index.html -->
<!doctype html>
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Service Worker Demo</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        margin: 2rem;
        line-height: 1.7;
      }
      button {
        padding: 0.7rem 1rem;
      }
    </style>
  </head>
  <body>
    <h1>Service Worker Demo</h1>
    <p id="status">Menunggu registrasi.</p>
    <button type="button" onclick="location.reload()">Muat ulang</button>
    <script src="/register-sw.js"></script>
  </body>
</html>
// register-sw.js
const status = document.querySelector("#status");
let reloadRequested = false;
let updatePromptShown = false;

function setStatus(message) {
  if (status) status.textContent = message;
}

function askToReload(worker) {
  if (updatePromptShown) return;
  updatePromptShown = true;

  const ok = window.confirm("Versi baru tersedia. Muat ulang sekarang?");

  if (ok) {
    reloadRequested = true;
    worker.postMessage({ type: "SKIP_WAITING" });
  }
}

async function registerServiceWorker() {
  if (!("serviceWorker" in navigator)) {
    setStatus("Browser ini tidak mendukung Service Worker.");
    return;
  }

  try {
    const registration = await navigator.serviceWorker.register("/sw.js", {
      scope: "/",
    });

    setStatus(`Service Worker terdaftar: ${registration.scope}`);

    if (registration.waiting && navigator.serviceWorker.controller) {
      askToReload(registration.waiting);
    }

    registration.addEventListener("updatefound", () => {
      const worker = registration.installing;
      if (!worker) return;

      worker.addEventListener("statechange", () => {
        const hasOldController = Boolean(navigator.serviceWorker.controller);
        if (worker.state === "installed" && hasOldController) {
          askToReload(worker);
        }
      });
    });
  } catch (error) {
    console.error(error);
    setStatus("Registrasi Service Worker gagal.");
  }
}

navigator.serviceWorker?.addEventListener("controllerchange", () => {
  if (!reloadRequested) return;
  window.location.reload();
});

registerServiceWorker();
// sw.js
const CACHE_VERSION = "2026-06-02-v1";
const CACHE_PREFIX = "claude-sw-demo";
const CACHE_NAME = `${CACHE_PREFIX}-${CACHE_VERSION}`;

const APP_SHELL = [
  "/",
  "/index.html",
  "/offline.html",
  "/register-sw.js",
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((names) =>
      Promise.all(
        names
          .filter((name) => name.startsWith(CACHE_PREFIX))
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name)),
      ),
    ),
  );
  self.clients.claim();
});

self.addEventListener("message", (event) => {
  if (event.data?.type === "SKIP_WAITING") {
    self.skipWaiting();
  }
});

self.addEventListener("fetch", (event) => {
  const { request } = event;
  if (request.method !== "GET") return;

  const url = new URL(request.url);
  if (url.origin !== self.location.origin) return;

  if (request.mode === "navigate") {
    event.respondWith(networkFirstNavigation(request));
    return;
  }

  if (["style", "script", "font", "image"].includes(request.destination)) {
    event.respondWith(staleWhileRevalidate(request));
  }
});

async function networkFirstNavigation(request) {
  const cache = await caches.open(CACHE_NAME);

  try {
    const response = await fetch(request);
    if (response.ok) cache.put(request, response.clone());
    return response;
  } catch {
    return (
      (await cache.match(request)) ||
      (await cache.match("/offline.html")) ||
      new Response("Offline", { status: 503 })
    );
  }
}

async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);

  const fetched = fetch(request)
    .then((response) => {
      if (response.ok) cache.put(request, response.clone());
      return response;
    })
    .catch(() => cached || new Response("Offline", { status: 503 }));

  return cached || fetched;
}
<!-- offline.html -->
<!doctype html>
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Kamu sedang offline</title>
  </head>
  <body>
    <main>
      <h1>Kamu sedang offline</h1>
      <p>Saat koneksi kembali, muat ulang halaman ini.</p>
      <button type="button" onclick="location.reload()">Coba lagi</button>
    </main>
  </body>
</html>

Contoh ini memakai Network First untuk navigasi dan Stale While Revalidate untuk CSS, JavaScript, font, dan gambar. Network First meminta server lebih dulu dan fallback ke cache atau offline.html saat gagal. Stale While Revalidate menampilkan cache segera sambil memperbarui di background. Jangan pakai strategi ini sembarangan untuk berita, harga, stok, atau layar yang butuh autentikasi.

Update dan invalidasi cache

Lifecycle update adalah bagian yang paling sering salah. Saat sw.js berubah, browser memasang worker baru. Jika halaman lama masih terbuka, worker baru bisa berhenti di state waiting. Kode registrasi di atas mendeteksi state itu, bertanya ke user, lalu mengirim SKIP_WAITING hanya jika disetujui.

Worker kemudian memanggil self.skipWaiting(), aktif, menghapus cache lama, dan mengambil alih client. Tanpa alur ini, user bisa tetap memakai app-cache-v1 lama setelah kamu deploy perbaikan.

Nama cache sebaiknya berisi tanggal, nomor release, atau commit ID. Jika build menghasilkan file hash, daftar precache harus sinkron dengan output build. Saat daftar manual mulai rapuh, Workbox layak dipertimbangkan. Tetapi Workbox tetap tidak bisa menentukan data bisnis mana yang aman untuk di-cache.

UX offline

Dukungan offline belum selesai hanya karena Cache API mengembalikan response. User perlu tahu apakah pekerjaannya tersimpan, menunggu sync, atau gagal. Untuk form, jangan simpan POST di Cache API; simpan draft atau pending job di IndexedDB dari sisi halaman, lalu retry saat online. Background Sync membantu, tetapi dukungan browser bervariasi, jadi alur penting tetap perlu event online dan tombol retry yang terlihat.

Saat meminta ke Claude Code, sertakan copy offline, tombol retry, status draft, dan pesan kegagalan sync. Untuk app lapangan, minimal pisahkan status “terkirim”, “tersimpan di perangkat ini”, dan “sync gagal”.

Pitfall umum

Pertama, scope tidak cocok. Worker di /app/sw.js secara default hanya mengontrol /app/, bukan seluruh situs. Jika ingin kontrol penuh, letakkan di /sw.js dan register dengan scope /.

Kedua, cache.addAll() berisi URL 404. Satu file hilang saja membuat seluruh install gagal. Setelah Claude Code menambah file, cek DevTools Application dan Cache Storage.

Ketiga, data privat. Jangan cache /api/me, halaman billing, HTML admin, atau JSON spesifik user tanpa strategi penghapusan yang jelas. Cache browser tetap storage di perangkat user.

Keempat, tidak ada UX update. Worker lama bisa menyimpan JS dan CSS lama. Versioning nama cache, hapus saat activate, dan beri opsi reload saat worker baru waiting.

Terakhir, Service Worker bukan storage permanen. Browser bisa menghapus cache saat ruang sempit. Response opaque cross-origin sulit diukur. Service Worker tidak bisa mengakses DOM. Ia hanya berjalan di HTTPS atau localhost. Jangan menjanjikan “offline penuh” sebelum flow nyata diuji.

Ringkasan dan CTA

Service Worker meningkatkan kunjungan ulang, perilaku offline, dan kualitas PWA. Jalur aman adalah menentukan ownership cache, lifecycle update, aturan data privat, dan layar offline sebelum Claude Code mengedit file.

ClaudeCodeLab membantu konversi PWA, desain cache, form offline, migrasi Workbox, dan review implementasi Claude Code. Jika situsmu perlu lebih cepat tanpa menyajikan data lama atau privat, mulai dari training dan konsultasi Claude Code.

Saat konfigurasi minimal ini dites di Chrome lokal, setelah load pertama panel Application menampilkan claude-sw-demo-2026-06-02-v1. Saat Network diubah ke Offline lalu halaman dimuat ulang, offline.html muncul. Saat CACHE_VERSION diubah, cache lama dihapus pada activate, sehingga contoh ini cocok sebagai dasar verifikasi rilis.

#Claude Code #Service Worker #PWA #offline #caching
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.