Membuat Website Statis Menjadi PWA dengan Service Worker

Pengertian

PWA (Progressive Web App) adalah website yang bisa "diinstal" di perangkat pengguna dan bekerja layaknya aplikasi: punya ikon di layar utama, bisa dibuka tanpa koneksi, dan memuat lebih cepat pada kunjungan berikutnya. Tiga pilar PWA:

  • Web App Manifest — metadata aplikasi (nama, ikon, warna tema, cara tampil).
  • Service Worker — skrip latar yang mencegat permintaan jaringan dan mengelola cache.
  • HTTPS — wajib, karena service worker dapat mengubah lalu lintas jaringan.

Untuk website statis, PWA memberi nilai tambah besar: pembaca artikel bisa membuka halaman yang sudah dikunjungi meski jaringan tidak stabil.

Kelebihan

  • Lebih cepat pada kunjungan berulang: aset disajikan dari cache lokal.
  • Bisa dibuka offline untuk halaman yang sudah dicache.
  • Bisa diinstal ke layar utama, tampil seperti aplikasi.
  • Tanpa backend tambahan: cukup berkas statis.
  • Gratis dan terbuka: didukung semua browser modern.

Kekurangan

  • Manajemen cache merepotkan: salah strategi bisa menyajikan aset basi.
  • Update tidak instan: service worker baru perlu siklus aktivasi.
  • Penyimpanan terbatas di perangkat pengguna.
  • Sulit di-debug bagi pemula karena berlatar belakang.
  • Tidak dihitung sebagai aplikasi toko: tanpa push notification dan wrapper, fiturnya terbatas dibanding aplikasi native.
Catatan penting: Service worker hanya berjalan di HTTPS (atau localhost). Selalu uji di domain produksi atau server lokal, bukan di file://.

Web App Manifest

Buat site.webmanifest di akar situs:

json
{
  "name": "Masyarakat",
  "short_name": "Masyarakat",
  "description": "Mari berbagi ilmu lewat artikel dan karya digital.",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#0d0d12",
  "theme_color": "#0d0d12",
  "lang": "id",
  "icons": [
    { "src": "/favicon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
    { "src": "/favicon-192.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

Tautkan di setiap halaman:

html
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#f7f7f9" />
<meta name="theme-color" content="#0d0d12" media="(prefers-color-scheme: dark)" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />

Service Worker: Cache Aset Inti

Buat sw.js di akar situs:

javascript
const CACHE = 'masyarakat-v3';
const PRECACHE = [
  '/',
  '/styles.css?v=2',
  '/site-config.js',
  '/nav.js',
  '/offline.html',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(PRECACHE))
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

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

  // Halaman: network-first agar konten baru tetap didahulukan.
  if (request.mode === 'navigate') {
    event.respondWith(
      fetch(request).catch(() => caches.match(request).then((r) => r || caches.match('/offline.html')))
    );
    return;
  }

  // Aset statis: cache-first, perbarui di latar.
  event.respondWith(
    caches.match(request).then((cached) => {
      const network = fetch(request)
        .then((res) => {
          const copy = res.clone();
          caches.open(CACHE).then((cache) => cache.put(request, copy));
          return res;
        })
        .catch(() => cached);
      return cached || network;
    })
  );
});

Naikkan angka versi (v3 → v4) setiap kali aset inti berubah agar cache lama dibersihkan.

Mendaftarkan Service Worker

html
<script>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('/sw.js').catch(() => {});
    });
  }
</script>

Halaman Offline

Sediakan offline.html sederhana yang dipakai saat jaringan mati:

html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Offline - Masyarakat</title>
</head>
<body>
  <main style="max-width:640px;margin:12vh auto;padding:24px;text-align:center;font-family:sans-serif">
    <h1>Anda sedang offline</h1>
    <p>Halaman ini belum tersimpan di perangkat. Sambungkan kembali internet lalu muat ulang.</p>
  </main>
</body>
</html>

Strategi Caching yang Umum

StrategiCocok untukPerilaku
Cache-firstCSS, JS, font, ikonAmbil cache dulu, perbarui di latar
Network-firstHTML, artikelUtamakan jaringan, fallback ke cache
Stale-while-revalidateAset berversiSajikan lama, perbarui diam-diam
Cache-onlyHalaman offlineSelalu dari cache

Mengukur dan Menguji

  1. Buka DevTools → Application → Service Workers untuk memastikan status activated.
  2. Cek Manifest untuk ikon, nama, dan warna.
  3. Centang Offline di tab Network lalu muat ulang halaman yang sudah dicache.
  4. Jalankan Lighthouse dengan kategori PWA untuk memverifikasi instalabilitas.

Troubleshooting

  • Service worker tidak terdaftar: cek console; penyebab umum adalah bukan HTTPS atau path /sw.js salah.
  • Perubahan tidak muncul: naikkan nama cache dan panggil self.skipWaiting() agar SW baru segera aktif.
  • Aset basi terus tersaji: pastikan aset berversi (?v=2) atau gunakan strategi network-first untuk HTML.
  • Offline malah menampilkan halaman error browser: cek path offline.html ada di PRECACHE dan bisa diakses.
  • Ikon PWA tidak muncul: sediakan minimal 192x192 dan 512x512, purpose memadai, dan src dapat diakses.
  • Cache menumpuk: hapus cache lama di event activate; batasi jumlah entri untuk situs besar.

Kesimpulan

PWA pada website statis cukup dengan tiga berkas: site.webmanifest, sw.js, dan offline.html, ditambah pendaftaran service worker. Dengan strategi cache yang tepat — cache-first untuk aset, network-first untuk HTML — situs terasa cepat dan tetap bisa dibaca walau jaringan terputus.

Artikel Terkait