localStorage vs sessionStorage vs IndexedDB vs Cookies — Penyimpanan Browser Tanpa Rasa Menyesal Pukul 3 Malam
Panduan praktis memilih penyimpanan browser yang tepat — dengan tabel perbandingan, penjelasan jelas tentang debat JWT, dan cara-cara spesifik yang akan mengganggu malam Anda.
Pukul 3 pagi 😬. Seorang pengguna melaporkan bug: keranjang belanja mereka kosong. Anda membuka DevTools, mengklik tab Application, dan memandang sisi sampingnya. Di mana Anda menempatkannya? Jawaban yang benar tergantung pada sejumlah pertanyaan yang sebagian besar pengembang hanya mempertimbangkan setelah bug dilaporkan.
Ini bukan artikel definisi. Anda bisa menemukannya di mana saja. Ini adalah bagian di mana kita membahas apa yang rusak, apa yang sebaiknya digunakan, dan mengapa debat tentang JWT di localStorage sebagian besar melewatkan inti masalahnya.
Versi Singkat (a.k.a. tabel yang akan Anda simpan)
| localStorage | sessionStorage | IndexedDB | Kue | |
|---|---|---|---|---|
| Ketahanan | Permanen | Hanya sesi tab | Permanen | Dapat disesuaikan (sesi atau tanggal kedaluwarsa) |
| Ukuran maksimum | 5–10 MB | 5–10 MB | GB (kuota browser) | ~4 KB per cookie |
| Akses server | TIDAK | TIDAK | TIDAK | Ya — dikirim dengan setiap permintaan |
| API asinkron | Tidak (menghambat thread utama) | Tidak (menghambat thread utama) | Ya (berbasis Promise/event) | TIDAK |
| Dapat dibaca oleh JavaScript | Ya | Ya | Ya | Hanya tanpa flag HttpOnly |
| Akses oleh Web Worker | TIDAK | TIDAK | Ya | TIDAK |
| Dibagikan antar tab | Ya | Tidak — setiap tab terisolasi | Ya | Ya |
| Penghapusan oleh ITP Safari | Setelah 7 hari tanpa interaksi | Pada penutupan tab | Setelah 7 hari tanpa interaksi | Tergantung pada atribut Expires |
localStorage
Tetap, bersifat sinkron, berlaku pada asal. Pekerja utama yang digunakan oleh semua orang dan sebagian besar waktu tidak seharusnya digunakan.
Apa yang sebenarnya dimaksud
localStorage menyimpan pasangan kunci-nilai berupa string. Itu saja. Batas penyimpanan adalah 5MB di kebanyakan browser, 10MB di beberapa (Chrome memberikan lebih banyak). Ini terbatas pada asal — protokol + domain + port — sehingga http://example.com dan https://example.com memiliki penyimpanan terpisah. Tahan hingga penutupan tab, restart browser, semua kecuali pengguna menghapus data browser mereka atau Anda secara eksplisit memanggil localStorage.clear().
// Read/write is synchronous — it happens right now, on the main thread
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme'); // 'dark'
// Storing objects? You're serializing manually.
localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alex' }));
const user = JSON.parse(localStorage.getItem('user'));
Di mana hal ini gagal
- Kuota melebihi batas — memicu kesalahan sinkron
DOMException: QuotaExceededError. Jika Anda tidak mengelompokkan penulisan dalam try/catch, Anda akan menemukannya melalui laporan bug pengguna. - Private/incognito — browser memberikan localStorage yang baru, terisolasi dengan kuota yang lebih ketat (atau nol). Firefox secara historis menimbulkan kesalahan kuota segera. Jangan mengandalkan localStorage tersedia tanpa mendeteksinya.
- ITP Safari — jika pengguna belum mengunjungi situs Anda selama 7 hari, Safari mungkin menghapus localStorage. Ini adalah perilaku yang tercatat. Ini akan mengejutkan Anda pada saat terburuk.
- XSS — apa pun yang ada di localStorage dapat dibaca oleh JavaScript yang berjalan di asal Anda. Jika penyerang dapat menyisipkan skrip, mereka mendapatkan semua data tersebut.
Gunakan untuk
Pengaturan antarmuka (mode gelap, status sidebar, bahasa), penyimpanan non-sensitif (waktu terakhir diambil, konfigurasi statis), apa pun yang seharusnya bertahan saat halaman di-refresh tetapi tidak mengandung token otentikasi atau data pribadi. Jika Anda mempertimbangkan menyimpan JWT atau kunci API di sini — lanjutkan membaca.
sessionStorage
Semua yang dilakukan localStorage, tetapi dengan masa simpan yang lebih pendek dan satu perilaku isolasi penting yang sering mengejutkan orang.
Jebakan isolasi tab
sessionStorage adalah per-tab, bukan per-browser. Membuka halaman yang sama di tab baru memberikan sessionStorage yang sama sekali terpisah. Ini bukan jelas bagi pengguna, dan akan tidak jelas bagi Anda sampai seseorang melaporkan bug tentang hilangnya data formulir multi-langkah saat mereka "sengaja" membuka tab kedua.
Satu pengecualian: jika pengguna membuka tab baru melalui window.open() atau klik tengah link, tab baru mendapatkan salinan dari sessionStorage induk saat dibuka. Setelah itu, kedua tab terisolasi. Ini adalah jenis kasus tepat yang menghasilkan pertanyaan hebat di Stack Overflow pada jam 2 pagi.
// Perfect for checkout flows — step data lives until the tab closes
sessionStorage.setItem('checkoutStep', '2');
sessionStorage.setItem('cartSnapshot', JSON.stringify(cart));
// Cleared automatically when the tab closes — no cleanup code needed
Gunakan untuk
Data formulir multi-langkah, data wizard sekali pakai, apa pun yang seharusnya ada selama satu sesi dan hilang saat pengguna menutup tab. Ini benar-benar berguna untuk alur checkout — Anda tidak ingin data checkout yang tidak lengkap tetap ada dari sesi sebelumnya. Jangan gunakan jika Anda membutuhkan data yang dibagikan antar tab atau halaman yang dibuka secara terpisah.
IndexedDB
Pilihan yang lebih dewasa. Asinkron, berbasis transaksi, dan mampu menyimpan objek JavaScript nyata — bukan hanya string yang diserialisasi. Juga memiliki API bawaan yang paling menyakitkan dari ketiga opsi ini, yang sebabkan hampir tidak ada yang menggunakan secara langsung.
Apa yang sebenarnya dimaksud
IndexedDB adalah penyimpanan kunci-nilai penuh dengan dukungan untuk indeks dan penelusuran berbasis kursor. Batas penyimpanan cukup luas — browser memungkinkan persentase ruang disk, biasanya dalam gigabyte dalam praktiknya. Anda dapat menyimpan objek terstruktur, Blobs, ArrayBuffers, dan File tanpa harus mengatur ulang secara manual. Ini tersedia di Web Worker. Ini adalah yang digunakan oleh aplikasi PWA dan aplikasi yang mendukung offline untuk menyimpan data yang tidak masuk akal untuk disimpan di memori.
// Native IDB API — nobody writes this directly in production
const request = indexedDB.open('myDB', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (event) => {
const db = event.target.result;
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.put({ id: 1, name: 'Alex', avatar: someBlob });
};
// What you actually use — Dexie.js or the idb wrapper
import Dexie from 'dexie';
const db = new Dexie('myApp');
db.version(1).stores({ users: '++id, name, email' });
await db.users.add({ name: 'Alex', email: 'alex@example.com' });
Di mana hal ini gagal
- ITP Safari — sama seperti localStorage: setelah 7 hari tanpa interaksi, Safari dapat menghapus data IndexedDB. Ini menghancurkan beberapa PWA sebelum Apple memperbaiki perilaku di versi iOS baru. Jika audiens Anda mencakup pengguna Safari di iOS, pertimbangkan hal ini.
- Penyimpanan rendah di iOS — di iOS, sistem operasi dapat menghapus data IndexedDB saat penyimpanan rendah. Tidak akan meminta. Data akan hilang.
- Penggunaan privasi — Chrome memungkinkan IndexedDB di mode incognito (dengan kuota sesi). Safari di mode privasi dulu menimbulkan kesalahan; perilaku bervariasi per versi.
- API bawaan — jika Anda menulis kode IDB mentah tanpa wrapper, model peristiwa berbasis callback akan menghasilkan bug yang akan Anda debugging selama setengah hari. Gunakan idb atau Dexie.js.
Gunakan untuk
Aplikasi offline, dataset besar yang akan membebani localStorage, apa pun yang Anda bangun di localStorage yang terus mengejutkan batas 5MB, penyimpanan file di PWA. Jika Anda menyimpan 50 dokumen yang dihasilkan oleh pengguna secara lokal, IndexedDB adalah jawabannya. Jika Anda menyimpan preferensi tema pengguna, itu berlebihan.
Kue
Yang paling tua dari keempatnya. Satu-satunya yang dilihat oleh server. Satu-satunya dengan batas 4KB yang akan benar-benar mengejutkan Anda jika Anda mencoba menyimpan JWT di sana.
Apa yang membuat cookie berbeda
Cookie dikirim secara otomatis dengan setiap permintaan yang sesuai. Ini adalah fitur dan masalah. Artinya, cookie sesi mencapai server tanpa perlu JavaScript — dan juga berarti setiap permintaan ke api.example.com mengandung beban cookie tanpa Anda suka atau tidak.
Atribut yang benar-benar penting:
- HttpOnly — JavaScript tidak dapat membaca cookie ini. Serangan XSS tidak dapat mengambilnya. Ini adalah standar dasar untuk cookie sesi.
- Aman — hanya dikirimkan melalui HTTPS. Tanpa atribut ini di situs produksi, Anda mengirimkan cookie otentikasi melalui HTTP. Jangan lakukan.
- SameSite=Strict — cookie hanya dikirim saat permintaan berasal dari domain Anda sendiri. Perlindungan terhadap CSRF yang efektif, tetapi ini menghambat alur redirect OAuth. SameSite=Lax adalah kompromi yang wajar untuk kebanyakan aplikasi.
- Expires / Max-Age — tanpa atribut ini, ini adalah cookie sesi yang hilang saat browser ditutup. Tetapkan batas eksplisit untuk perilaku "ingat saya".
// Setting a cookie from JavaScript (no HttpOnly, obviously)
document.cookie = "theme=dark; Path=/; Max-Age=31536000; Secure; SameSite=Lax";
// Server-side (Node.js + Express) — where the real power is
res.cookie('sessionId', token, {
httpOnly: true, // JS cannot read this
secure: true, // HTTPS only
sameSite: 'lax', // balanced CSRF protection
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days in ms
});
Jika Anda memperbaiki string cookie yang rumit, IO Tools’ Cookie Parser akan memecahkannya menjadi pasangan kunci-nilai yang dapat dibaca — berguna saat Anda memandang header 400 karakter dan berusaha mengetahui atribut mana yang salah. Set-Cookie Kuota 4KB
Di mana hal ini gagal
- — ini adalah ukuran total termasuk nama + nilai + semua atribut. JWT klasik berukuran 400–800 byte. Salah satu yang besar dengan banyak klaim berukuran 1–2KB. Anda memiliki ruang yang sangat terbatas. SameSite=Strict menghancurkan OAuth
- — saat IdP mengarahkan kembali ke aplikasi Anda setelah login, ini adalah permintaan lintas situs. SameSite=Strict berarti cookie sesi tidak akan dikirim. Gunakan Lax untuk apa pun yang melalui alur OAuth. Urutan cookie dan pemetaan path
- — ketika beberapa cookie memiliki jalur yang saling tumpang tindih, browser mengirimkannya dalam urutan yang tidak ditentukan. Jangan mengandalkan prioritas. Penghapusan cookie pihak ketiga
- — Chrome sedang menghapus cookie pihak ketiga. Ketergantungan cookie lintas situs adalah risiko jangka menengah. Debat tentang menyimpan JWT di localStorage (luas area serangan nyata)
Ini muncul dalam setiap diskusi otentikasi, dan kebanyakan orang yang berdebat tentang ini berbicara lewat satu sama lain.
Kekhawatiran menyimpan JWT di localStorage: jika situs Anda memiliki kelemahan XSS, skrip penyerang dapat langsung membaca token dan mengambilnya. Penyerang sekarang memiliki token otentikasi yang valid yang dapat digunakan dari mana saja, di perangkat apa saja, hingga kadaluwarsa.
Kasus untuk cookie HttpOnly sebagai pengganti: JavaScript tidak dapat membaca cookie ini, sehingga XSS tidak dapat mengambilnya. Sesi tetap dapat digunakan dalam permintaan (penyerang dapat membuat permintaan dari browser korban melalui XSS), tetapi mereka tidak dapat membawa token untuk digunakan di luar. Ini membatasi radius serangan.
Pendekatan yang jujur:
masalah utama adalah XSS, bukan lokasi penyimpanan . Jika Anda memiliki XSS, cookie HttpOnly jelas lebih baik — penyerang tidak dapat membawa token ke luar. Namun, memperbaiki XSS adalah tujuan yang lebih bermakna, dapat dicapai dengan kebijakan keamanan konten ketat, tidak ada skrip pihak ketiga yang tidak terkendali, dan pengecualian keluaran yang tepat.Jika Anda membangun SPA dengan kebijakan keamanan konten ketat dan tidak ada skrip pihak ketiga yang Anda kendalikan, localStorage mungkin cukup untuk JWT. Jika Anda menjalankan situs dengan Google Tag Manager, pixel iklan, dan puluhan dependensi npm, cookie dengan HttpOnly jauh lebih aman karena area serangan XSS Anda lebih besar dari yang Anda pikirkan.
Saat memperbaiki masalah JWT,
JWT Decoder di IO Tools berguna — tempelkan token dan lihat payload dan masa berlaku tanpa menulis kode. Yang berguna untuk memastikan apakah token masih valid saat Anda menelusuri kenaikan 401. JWT Expiry Checker Alur keputusan
Sebelum Anda membuka tab Stack Overflow pada jam 3 pagi, telusuri ini:
Apakah server perlu membaca data ini?
- → Cookie. Akhir dari diskusi. Apakah lebih besar dari 5MB atau Anda membutuhkan kemampuan pencarian?
- → IndexedDB. Apakah harus hilang saat tab ditutup?
- → sessionStorage. Semuanya lainnya
- → localStorage, dengan perhatian yang tepat terhadap apa yang disimpan. Hal satu yang selalu salah dilakukan
API penyimpanan tidak dapat diandalkan secara konsisten di semua pengguna dan waktu. Mereka dapat dihapus oleh browser, sistem operasi, pengaturan privasi, atau pengguna. Setiap arsitektur yang menganggap penyimpanan klien sebagai sumber kebenaran — bukan sebagai cache — akan gagal pada seseorang.
Polanya yang tetap berdiri: anggap penyimpanan browser sebagai optimasi performa terhadap sumber kebenaran nyata (server). Simpan secara agresif, tetapi desain aplikasi Anda agar dapat pulih secara lancar ketika cache kosong. Sesi debugging pada jam 3 pagi hampir tidak pernah tentang API penyimpanan mana yang digunakan — mereka tentang mengasumsikan data akan ada ketika sebenarnya tidak ada.
Pola yang berlaku: menganggap penyimpanan browser sebagai optimasi kinerja dibandingkan dengan sumber kebenaran sebenarnya (pelayan). Simpan secara agresif, tetapi desain aplikasi Anda agar dapat pulih secara lancar ketika cache kosong. Sesi debugging pukul 3 pagi hampir tidak pernah terkait dengan API penyimpanan yang digunakan — mereka terkait dengan asumsi bahwa data akan tersedia ketika sebenarnya tidak tersedia.
Anda mungkin juga menyukai
Instal Ekstensi Kami
Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat
恵 Papan Skor Telah Tiba!
Papan Skor adalah cara yang menyenangkan untuk melacak permainan Anda, semua data disimpan di browser Anda. Lebih banyak fitur akan segera hadir!
Alat Wajib Coba
Lihat semua Pendatang baru
Lihat semuaMemperbarui: Kita alat terbaru was added on Jun 26, 2026
