Use Cases (Diperbarui: 2/6/2026)

Membuat PWA dengan Claude Code: Manifest, Service Worker, dan Offline

Panduan praktis membuat PWA dengan Claude Code: manifest, ikon, Service Worker, fallback offline, strategi cache, dan validasi.

Membuat PWA dengan Claude Code: Manifest, Service Worker, dan Offline

PWA, atau Progressive Web App, adalah pola implementasi yang membuat aplikasi web terasa lebih dekat dengan aplikasi terpasang. Pengguna bisa menambahkan ikon ke layar utama, membuka aplikasi dalam mode standalone, memuat aset penting dari cache, dan tetap melihat halaman yang jelas saat koneksi terputus.

Kesalahan yang sering terjadi adalah menganggap PWA selesai setelah menambahkan manifest.webmanifest. Dalam praktiknya, Anda juga perlu ikon dengan ukuran benar, Service Worker, halaman fallback offline, strategi cache, pemeriksaan installability, serta validasi dengan Chrome DevTools dan Lighthouse. Satu path ikon yang 404 atau HTML lama yang tersimpan di cache bisa membuat masalah produksi yang sulit dilacak.

Artikel ini menunjukkan cara membangun PWA dengan Claude Code secara bertahap dan mudah diuji. Jika Anda baru memakai Claude Code, baca dulu panduan mulai Claude Code. Untuk rujukan resmi, gunakan web.dev Learn PWA, panduan PWA installable dari MDN, best practices PWA dari MDN, pembaruan kriteria install PWA dari Chrome, dan dokumentasi Claude Code.

Gambaran Arsitektur

PWA adalah rangkaian beberapa file. HTML menghubungkan manifest, entry point aplikasi mendaftarkan Service Worker, lalu Service Worker mengatur request di dalam scope-nya. Saat jaringan gagal, ia bisa mengembalikan halaman offline yang sudah disiapkan.

Pengguna membuka situs
  -> index.html menghubungkan manifest.webmanifest
  -> register-sw.js mendaftarkan /sw.js
  -> sw.js melakukan precache app shell
  -> fetch memilih strategi berdasarkan jenis resource
  -> navigasi offline menerima offline.html

Sebelum meminta Claude Code mengubah file, tentukan tiga hal.

KeputusanContohRisiko jika diabaikan
Start URL dan scope/ atau /app/Service Worker tidak mengontrol halaman yang tepat
Resource yang dicacheHTML, CSS, JS, gambar, offline.html404 atau versi lama bisa tersimpan lama
Perilaku offlineHalaman offline, halaman terakhir, error APIPengguna tidak paham kondisi aplikasi

Untuk blog, situs kursus, dan dashboard kecil, mulai dari strategi konservatif: Network First untuk navigasi HTML, Cache First untuk gambar dan ikon, lalu Stale While Revalidate untuk CSS, JavaScript, dan font. Untuk pembahasan cache yang lebih luas, baca juga strategi cache dengan Claude Code.

Prompt untuk Claude Code

Jangan hanya menulis “buat jadi PWA”. Beri Claude Code daftar file, strategi, dan cara verifikasi.

Ubah aplikasi Vite/React yang sudah ada ini menjadi PWA.

Kebutuhan:
- Tambahkan public/manifest.webmanifest
- Referensikan ikon PNG 192x192, 512x512, dan maskable 512x512
- Tambahkan public/offline.html
- Tambahkan public/sw.js sebagai Service Worker
- Daftarkan Service Worker dari src/register-sw.js
- Gunakan Network First untuk navigasi HTML
- Gunakan Cache First untuk gambar
- Gunakan Stale While Revalidate untuk CSS, JS, dan font
- Jangan cache request POST atau cross-origin
- Tampilkan notifikasi saat versi Service Worker baru tersedia
- Akhiri dengan checklist manual untuk DevTools dan Lighthouse

Batasan:
- Jangan menambahkan fetch handler kosong hanya agar terlihat installable
- Jelaskan setiap file yang berubah
- Tandai path yang bergantung pada base path produksi

Dalam percobaan Masa pada landing page kursus kecil, kode cepat dibuat oleh Claude Code. Masalah pertama justru ada pada mismatch start_url dan scope. Prompt yang memaksa asumsi itu ditulis membuat review jauh lebih mudah.

Manifest dan Ikon

Buat public/manifest.webmanifest. name adalah nama lengkap aplikasi, short_name dipakai di ruang sempit, start_url adalah halaman awal saat aplikasi dibuka, dan scope menentukan URL yang masuk ke aplikasi.

{
  "id": "/",
  "name": "ClaudeCodeLab PWA Demo",
  "short_name": "CCLab",
  "description": "Demo PWA offline yang dibuat dengan Claude Code",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0f766e",
  "orientation": "portrait-primary",
  "prefer_related_applications": false,
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

Hubungkan manifest di HTML head.

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0f766e" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />

Gunakan file PNG nyata. Minimal siapkan 192x192 dan 512x512, lalu tambahkan ikon maskable 512x512 dengan ruang aman di sekitar logo. Setelah Claude Code menambahkan path, buka tiap URL ikon di browser untuk memastikan respons 200.

Service Worker

Tambahkan public/sw.js. Contoh ini melakukan precache app shell, menghapus cache lama, dan hanya menangani request GET dari origin yang sama.

const VERSION = "2026-06-02";
const STATIC_CACHE = `static-${VERSION}`;
const RUNTIME_CACHE = `runtime-${VERSION}`;

const APP_SHELL = [
  "/",
  "/offline.html",
  "/manifest.webmanifest",
  "/icons/icon-192.png",
  "/icons/icon-512.png",
  "/icons/icon-maskable-512.png"
];

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

self.addEventListener("activate", (event) => {
  const allowedCaches = [STATIC_CACHE, RUNTIME_CACHE];

  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(
          keys
            .filter((key) => !allowedCaches.includes(key))
            .map((key) => caches.delete(key))
        )
      )
      .then(() => self.clients.claim())
  );
});

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(networkFirstPage(request));
    return;
  }

  if (request.destination === "image") {
    event.respondWith(cacheFirst(request));
    return;
  }

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

async function networkFirstPage(request) {
  const cache = await caches.open(RUNTIME_CACHE);

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

async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  if (response.ok) {
    const cache = await caches.open(RUNTIME_CACHE);
    await cache.put(request, response.clone());
  }
  return response;
}

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

  const networkPromise = fetch(request)
    .then((response) => {
      if (response.ok) cache.put(request, response.clone());
      return response;
    })
    .catch(() => undefined);

  if (cached) return cached;
  return (await networkPromise) || new Response("Network error", { status: 504 });
}

Kode ini sengaja tidak menyimpan POST, resource cross-origin, atau API privat. Login, pembayaran, keranjang, permission, dan stok barang harus punya aturan cache server dan autentikasi yang jelas. Cache browser adalah penyimpanan, bukan sekadar trik kecepatan.

Halaman Offline dan Registrasi

public/offline.html harus sederhana dan tidak bergantung pada network eksternal.

<!doctype html>
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Sedang offline</title>
  </head>
  <body>
    <main>
      <h1>Anda sedang offline</h1>
      <p>Muat ulang setelah koneksi kembali. Halaman yang baru dibuka mungkin masih tersedia.</p>
      <p><a href="/">Kembali ke beranda</a></p>
    </main>
  </body>
</html>

Daftarkan Service Worker dari src/register-sw.js.

export async function registerServiceWorker() {
  if (!("serviceWorker" in navigator)) return;

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

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

        worker.addEventListener("statechange", () => {
          if (worker.state === "installed" && navigator.serviceWorker.controller) {
            document.querySelector("[data-refresh-app]")?.removeAttribute("hidden");
          }
        });
      });
    } catch (error) {
      console.error("Service Worker registration failed:", error);
    }
  });
}

Panggil sekali dari entry point.

import { registerServiceWorker } from "./register-sw.js";

registerServiceWorker();

Notifikasi update penting karena Service Worker punya lifecycle sendiri. Versi baru bisa menunggu ketika tab lama masih dikontrol worker sebelumnya.

Tombol Install dan Validasi

Beberapa browser Chromium menyalakan beforeinstallprompt saat aplikasi memenuhi syarat. Anggap ini progressive enhancement, bukan satu-satunya cara install.

let deferredPrompt = null;

window.addEventListener("beforeinstallprompt", (event) => {
  event.preventDefault();
  deferredPrompt = event;
  document.querySelector("[data-install-app]")?.removeAttribute("hidden");
});

document.querySelector("[data-install-app]")?.addEventListener("click", async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  const choice = await deferredPrompt.userChoice;
  console.info("Install result:", choice.outcome);
  deferredPrompt = null;
});

Untuk validasi, jangan hanya mengejar skor PWA lama. Gunakan DevTools Application untuk Manifest, Service Worker, Cache Storage, dan mode Offline. Lighthouse tetap berguna untuk performance, accessibility, best practices, dan SEO.

npm run build
npx serve dist -l 4173
npx lighthouse http://localhost:4173 --view --only-categories=performance,accessibility,best-practices,seo
CekLokasiLulus jika
ManifestApplication > Manifestname, start_url, dan ikon tanpa error
Service WorkerApplication > Service Workers/sw.js sudah activated
Offline reloadNetwork Offline lalu reloadoffline.html atau halaman terbaru tampil
Cache StorageApplication > Cache Storagecache static/runtime sesuai rencana
LighthouseReportperformance, SEO, accessibility tidak turun

Use Case, CTA, dan Jebakan

PWA paling berguna untuk penggunaan berulang: blog teknis dan kursus yang dibaca saat perjalanan, dashboard internal yang dibuka tiap hari, panduan event di tempat dengan jaringan padat, atau commerce dengan banyak gambar.

Untuk monetisasi, jangan menjual “bisa diinstall” saja. Ukur install click, offline fallback hit, returning user, completion rate, dan klik CTA produk. Template dan prompt pack Claude Code bisa dilihat di product library. Untuk tim, gabungkan PWA dengan review cache, validasi path deploy, dan event analytics.

Jebakan umum: scope dan start_url tidak cocok; HTML dicache dengan Cache First; data API privat masuk cache; ikon 404; dan debugging tanpa unregister Service Worker lama. Jika hasil tidak masuk akal, buka Application panel, Unregister worker, hapus Cache Storage, lalu ulangi first visit.

Hasil Uji

Dalam uji Masa pada landing page kursus Vite kecil, Claude Code cepat membuat manifest, halaman offline, dan registrasi. Waktu paling banyak habis untuk memeriksa URL ikon, reload saat Offline, dan membersihkan cache lama setelah deploy baru. Pendekatan paling aman adalah mengirim fallback offline minimal dulu, lalu menambah cache hanya untuk halaman dan aset yang jelas membantu pengguna yang kembali.

#Claude Code #PWA #Service Worker #offline #mobile
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.