Membuat website bisa di-install cukup satu sore: manifest, beberapa ikon, dan HTTPS. Membuatnya berperilaku seperti aplikasi butuh waktu jauh lebih lama. Perbedaannya hampir seluruhnya ada di service worker, script yang berdiri di antara aplikasi dan jaringan. Script inilah yang menentukan apa yang tampil ketika sinyal hilang, apa yang terjadi pada form yang dikirim dari basement parkiran, dan apakah pengguna mendapat versi baru setelah deploy atau tetap tertahan di versi bulan lalu. Kapan PWA lebih tepat dibanding aplikasi native sudah kami bahas di artikel terpisah. Artikel ini membahas cara membangun bagian offline dan update dengan benar, sekaligus apa saja yang berubah untuk PWA setahun terakhir.
Apa yang Berubah untuk PWA di 2025 dan 2026
Beberapa perubahan dalam satu setengah tahun terakhir membuat PWA lebih mudah di-install, terutama di iPhone. Tidak satu pun yang mengubah pekerjaan service worker yang dibahas di bawah.
| Platform | Perubahan | Artinya |
|---|---|---|
| iOS dan iPadOS 26 | Setiap situs yang ditambahkan ke Layar Utama otomatis terbuka sebagai web app. Manifest tidak lagi wajib, dan WebKit menyebut kini tidak ada syarat installability sama sekali di Safari. | Instalasi tetap lewat menu Bagikan, tetapi situs apa pun bisa menjadi ikon seperti aplikasi. Kalau ada, manifest tetap menentukan nama, ikon, dan mode tampilan. |
| iOS dan iPadOS 18.4 | Declarative Web Push: pesan push bisa mendeskripsikan notifikasinya sendiri, tanpa harus dirakit oleh service worker. | Push di perangkat Apple menjadi lebih andal. Di iPhone, web push tetap ditujukan untuk web app yang sudah ditambahkan ke Layar Utama. |
| Firefox 143 di Windows | Firefox bisa meng-install situs sebagai web app yang terbuka di jendela taskbar tersendiri. | Pertama kalinya setelah bertahun-tahun Firefox desktop menawarkan instalasi. Belum tersedia di macOS dan Linux. |
| Chrome | Kriteria installability mencantumkan HTTPS, manifest dengan nama, ikon 192 px dan 512 px, start_url, dan mode tampilan sejenis standalone. Service worker tidak lagi tercantum. | Prompt install bisa muncul tanpa dukungan offline, jadi service worker kini ditambahkan karena fiturnya, bukan untuk membuka instalasi. |
| Lighthouse 12 | Kategori PWA dihapus pada 2024. Sebagian pemeriksaannya dipindah ke Best Practices. | Tidak ada lagi skor PWA yang bisa dikejar. Anda perlu checklist dan pengujian sendiri. |
Cara Kerja Service Worker
Service worker adalah file JavaScript yang dijalankan browser terpisah dari halaman. Script ini tidak punya akses ke DOM. Tugas utamanya mencegat setiap request jaringan dari halaman dalam cakupannya, lalu memutuskan cara menjawabnya: dari jaringan, dari cache, atau dengan membuat respons sendiri. Service worker hanya berjalan di HTTPS, dengan pengecualian localhost untuk development.
- 1Register: halaman memanggil navigator.serviceWorker.register dengan URL script. Lokasi file menentukan cakupan default, jadi worker di /sw.js mengendalikan seluruh situs.
- 2Install: browser mengunduh script dan memicu event install. Di sinilah biasanya app shell di-cache lebih dulu: HTML utama, CSS, JavaScript, dan ikon.
- 3Menunggu: kalau service worker lama masih mengendalikan tab yang terbuka, worker baru menunggu. Ia baru mengambil alih setelah semua tab yang memakai versi lama ditutup.
- 4Activate: worker baru mengambil kendali dan memicu event activate. Di sinilah cache dari versi lama dihapus.
- 5Fetch: sejak saat itu, setiap request dari halaman yang dikendalikan melewati event fetch, tempat strategi caching Anda berjalan.
Browser memeriksa versi baru script service worker setiap kali navigasi, dan minimal sekali setiap 24 jam. Kalau ada satu byte saja yang berubah, siklus install dimulai lagi. Langkah menunggu itulah sumber bug PWA yang paling umum, yang akan kita bahas di bawah.
Memilih Strategi Caching per Jenis Konten
Tidak ada satu strategi caching yang benar untuk seluruh aplikasi. Setiap jenis request butuh strateginya sendiri, dan kebanyakan bug muncul karena satu strategi diterapkan ke semuanya.
| Strategi | Cara kerja | Cocok untuk |
|---|---|---|
| Cache first | Jawab dari cache; ke jaringan hanya kalau datanya belum ada. | File yang punya versi atau hash di namanya: bundle JS dan CSS, font, ikon. |
| Network first | Coba jaringan dulu; pakai cache kalau gagal atau terlalu lama. | Halaman dan data API yang sebaiknya terbaru tetapi masih berguna walau sedikit lama, seperti daftar produk atau riwayat pesanan. |
| Stale while revalidate | Langsung jawab dari cache, lalu perbarui cache dari jaringan di belakang. | Data yang lebih penting tampil cepat daripada paling baru: avatar, gambar katalog, data referensi. |
| Network only | Selalu ke jaringan, tidak pernah di-cache. | Login, pembayaran, checkout, dan apa pun yang menulis data. |
| Cache only | Hanya menjawab dari yang sudah di-cache saat install. | Halaman fallback offline dan app shell inti. |
Tambahkan timeout pada network first. Tanpa timeout, request di sinyal 3G yang lemah bisa menggantung 30 detik sebelum fallback ke cache berjalan, dan rasanya lebih buruk daripada benar-benar offline. Tiga sampai lima detik adalah titik awal yang wajar untuk halaman.
Yang Sebaiknya Tidak Di-cache
- Respons API berisi data pribadi di perangkat yang dipakai bersama, seperti tablet toko atau HP keluarga. Setelah di-cache, data itu masih bisa dibaca setelah logout kecuali cache dibersihkan saat logout, dan itu memang harus dilakukan.
- Request POST, PUT, dan DELETE. Cache API hanya menyimpan respons GET; penulisan data saat offline butuh mekanisme lain, yang dibahas di bawah.
- Respons dari domain lain tanpa CORS, yang disebut opaque response. Service worker tidak bisa memeriksa apakah request itu berhasil, sehingga halaman error bisa ikut ter-cache, dan Chrome menghitung masing-masing sekitar 7 MB terhadap kuota storage.
- File service worker itu sendiri. Sajikan sw.js dengan Cache-Control: no-cache supaya browser selalu memeriksa versi terbaru.
- Seluruh isi situs secara default. Pre-cache ratusan megabyte gambar saat kunjungan pertama menghabiskan kuota pengguna paket prabayar, dan mereka tidak akan berterima kasih untuk itu.
Update: Bug yang Membuat Pengguna Tertahan di Versi Lama
Keluhan paling umum setelah PWA rilis adalah pengguna tidak melihat perbaikan yang sudah di-deploy tadi pagi. Penyebabnya biasanya langkah menunggu tadi. Service worker baru ter-install lalu menunggu, karena aplikasinya masih terbuka di suatu tempat, dan di HP aplikasi jarang benar-benar ditutup. Sementara itu worker lama terus menyajikan HTML lama dari cache, yang memuat JavaScript lama.
Solusi yang menggoda adalah memanggil skipWaiting di event install supaya worker baru langsung mengambil alih. Cara ini berhasil, tetapi ada harganya: halaman yang sudah terbuka dimuat oleh versi lama, dan bisa saja meminta file yang sudah dihapus worker baru, atau memanggil API yang sudah berubah. Muncul error yang sulit direproduksi. Pola yang lebih aman:
- 1Deteksi service worker yang sedang menunggu dari halaman, lewat event updatefound pada registration dan properti waiting.
- 2Tampilkan banner yang jelas, misalnya "Versi baru tersedia", dengan tombol Muat Ulang.
- 3Saat pengguna menekannya, kirim pesan ke worker yang menunggu supaya memanggil skipWaiting, lalu muat ulang halaman setelah event controllerchange muncul.
- 4Untuk perbaikan kritis, seperti celah keamanan atau alur pembayaran yang rusak, update boleh dipaksa, tetapi lakukan di momen yang aman, misalnya di navigasi berikutnya, bukan di tengah pengisian form.
- 5Jaga API backend tetap kompatibel ke belakang minimal satu rilis, karena sebagian pengguna masih menjalankan frontend versi sebelumnya selama beberapa hari.
Gunakan nama file dengan hash konten untuk setiap bundle, yang sudah otomatis dilakukan Next.js, Vite, dan tool sejenis, lalu hapus cache lama di event activate berdasarkan nama versi. Tanpa pembersihan ini, setiap rilis menambah satu salinan aplikasi lagi di perangkat.
Form dan Data Saat Offline
Menampilkan halaman dari cache saat offline adalah separuh yang mudah. Separuh yang sulit adalah menerima input saat offline, misalnya pesanan dari sales di daerah tanpa sinyal atau absensi di lokasi proyek. Pola yang terbukti jalan:
- Simpan setiap kiriman di IndexedDB sebagai entri outbox, dengan ID unik yang dibuat di perangkat. localStorage terlalu kecil dan memblokir main thread.
- Tampilkan statusnya dengan jelas: tersimpan di perangkat, sedang dikirim, terkirim, atau gagal. Pengguna yang tidak melihat status akan mengirim pesanan yang sama tiga kali.
- Kirim isi outbox saat koneksi kembali. Chrome dan Edge di Android mendukung Background Sync, yang tetap mencoba lagi walaupun aplikasi sudah ditutup. Safari dan Firefox tidak mendukungnya, jadi coba lagi juga saat aplikasi dibuka dan saat event online muncul.
- Buat server idempotent: pakai ID dari perangkat sebagai idempotency key supaya kiriman yang terkirim dua kali hanya tersimpan sekali.
- Tentukan sejak awal apa yang terjadi kalau ada konflik. Kalau stok berubah selama pesanan tertahan offline, apakah server menolak, menyesuaikan, atau menandainya untuk ditinjau? Ini aturan bisnis, bukan teknis, jadi sepakati bersama penggunanya.
Kalau aplikasi butuh database lokal lengkap dengan sinkronisasi dua arah, ratusan data yang diedit offline setiap hari, dan aturan konflik yang rumit, pertimbangkan sync engine atau aplikasi native. PWA bisa melakukannya, tetapi usahanya membengkak dengan cepat.
Batas Storage dan Penghapusan Data
Browser memberi ruang jauh lebih besar dari perkiraan banyak orang. Menurut WebKit, Safari mengizinkan satu origin memakai sampai 60 persen disk di aplikasi browser, dan Chrome memakai batas yang mirip. Risiko yang lebih besar bukan kuotanya, melainkan penghapusan otomatis.
- Safari menghapus seluruh storage yang bisa ditulis script untuk sebuah situs, termasuk IndexedDB dan cache service worker, setelah tujuh hari pemakaian Safari tanpa interaksi pengguna dengan situs itu. WebKit menyatakan web app yang ditambahkan ke Layar Utama punya hitungan harinya sendiri dan tidak diharapkan kehilangan data first-party dengan cara ini.
- Saat ruang perangkat menipis, browser menghapus data dari situs yang paling lama tidak dipakai lebih dulu.
- Panggil navigator.storage.persist() untuk meminta storage permanen. Chrome mengabulkannya berdasarkan sinyal seperti instalasi dan tingkat pemakaian, dan WebKit menyebut ikut mempertimbangkan apakah situs berjalan sebagai web app di Layar Utama.
- Jangan pernah menjadikan perangkat sebagai satu-satunya salinan data penting. Kirim ke server secepat mungkin, dan perlakukan storage lokal sebagai cache dan outbox.
Tool: Tulis Sendiri atau Pakai Library
Service worker yang hanya untuk push notification cukup pendek untuk ditulis sendiri, dan dokumentasi Next.js punya contohnya. Begitu routing dan strategi caching masuk, library akan menghemat waktu. Workbox dari Google sudah lama menjadi standar. Serwist berkembang dari Workbox dan menjadi pilihan yang dirujuk dokumentasi Next.js untuk caching offline, dengan contoh untuk Turbopack maupun webpack. Next.js juga punya hook eksperimental useOffline untuk UI yang sadar koneksi dan pengulangan otomatis navigasi serta Server Action yang gagal, yang menutup sebagian masalah tanpa service worker penuh.
Menguji Tanpa Menipu Diri Sendiri
- Di Chrome DevTools, panel Application menampilkan service worker, cache, dan storage. Matikan "Update on reload" setelah development selesai, karena kalau tidak, Anda tidak akan pernah melihat bug update yang dialami pengguna.
- Uji offline dengan opsi Offline di panel Network, dan juga dengan throttling jaringan lambat. Sinyal lemah berperilaku berbeda dari tanpa sinyal.
- Uji di iPhone sungguhan dengan situs yang sudah ditambahkan ke Layar Utama, lewat Safari Web Inspector di Mac. Safari 26 bisa otomatis menginspeksi service worker milik web app di Layar Utama.
- Uji jalur update: install versi 1, deploy versi 2, lalu buka aplikasi seperti cara pengguna membukanya, bukan dengan hard refresh.
- Karena Lighthouse tidak lagi punya kategori PWA, buat checklist singkat untuk proyek: bisa di-install, bisa dibuka offline, bisa mengirim saat offline lalu tersinkron, ter-update setelah deploy, dan membersihkan data saat logout.
Checklist Singkat
- 1Manifest dengan name, short_name, start_url, display bernilai standalone, serta ikon 192 px, 512 px, dan maskable.
- 2Pre-cache hanya app shell dan halaman fallback offline.
- 3Satu strategi caching per jenis request, dengan timeout pada network first.
- 4Tanpa cache untuk login, pembayaran, penulisan data, atau data pribadi di perangkat bersama, dan cache dibersihkan saat logout.
- 5sw.js disajikan dengan no-cache, cache lama dihapus saat activate, dan banner update alih-alih skipWaiting diam-diam.
- 6Input offline disimpan di IndexedDB, dikirim dengan idempotency key, dan dicoba ulang saat aplikasi dibuka dan saat event online.
- 7Storage permanen diminta, dan data penting dikirim ke server secepat mungkin.
- 8Diuji di HP Android kelas menengah dan di iPhone dengan aplikasi yang sudah ditambahkan ke Layar Utama.
PWA dinilai di momen terburuknya: sinyal lemah, deploy sejam yang lalu, dan pengguna yang harus mengirim sesuatu saat itu juga.
Poin penting
- Instalasi kini lebih mudah: iOS 26 membuka situs apa pun di Layar Utama sebagai web app, Firefox 143 bisa meng-install web app di Windows, dan Chrome tidak lagi mencantumkan service worker sebagai syarat instalasi.
- Service worker menentukan perilaku aplikasi saat offline dan saat update. Pilih strategi caching per jenis request, bukan satu untuk semuanya.
- Jangan pernah meng-cache login, pembayaran, penulisan data, opaque response, atau file sw.js itu sendiri, dan bersihkan data pribadi di cache saat logout.
- Versi baru menunggu sampai tab lama ditutup. Tampilkan banner update alih-alih memaksa skipWaiting, dan jaga API tetap kompatibel selama satu rilis.
- Input offline disimpan di outbox IndexedDB dan dikirim dengan idempotency key; Background Sync hanya ada di browser berbasis Chromium.
- Lighthouse tidak lagi memberi skor PWA, jadi uji sendiri instalasi, pemakaian offline, sinkronisasi, dan update di perangkat Android dan iPhone sungguhan.


