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.
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.
| Keputusan | Contoh | Risiko jika diabaikan |
|---|---|---|
| Start URL dan scope | / atau /app/ | Service Worker tidak mengontrol halaman yang tepat |
| Resource yang dicache | HTML, CSS, JS, gambar, offline.html | 404 atau versi lama bisa tersimpan lama |
| Perilaku offline | Halaman offline, halaman terakhir, error API | Pengguna 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
| Cek | Lokasi | Lulus jika |
|---|---|---|
| Manifest | Application > Manifest | name, start_url, dan ikon tanpa error |
| Service Worker | Application > Service Workers | /sw.js sudah activated |
| Offline reload | Network Offline lalu reload | offline.html atau halaman terbaru tampil |
| Cache Storage | Application > Cache Storage | cache static/runtime sesuai rencana |
| Lighthouse | Report | performance, 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.
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.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.