Tidak suka iklan? Pergi Bebas Iklan Hari ini

localStorage vs sessionStorage vs IndexedDB vs Cookies — Penyimpanan Browser Tanpa Rasa Menyesal Pukul 3 Malam

Diperbarui pada

Panduan praktis memilih penyimpanan browser yang tepat — dengan tabel perbandingan, penjelasan jelas tentang debat JWT, dan cara-cara spesifik yang akan mengganggu malam Anda.

localStorage vs sessionStorage vs IndexedDB vs Cookies — Browser Storage Without the 3am Regret 1
IKLAN · HAPUS?

It’s 3am 😬. A user filed a bug: their shopping cart is empty. You open DevTools, click the Application tab, and stare at the sidebar. Where did you put it? The right answer depends on a handful of questions most developers only think about after the bug is filed.

This isn’t a definitions article. You can find those anywhere. This is the part where we talk about what breaks, what to actually use, and why the JWT-in-localStorage debate mostly misses the point.

The Short Version (a.k.a. the table you’ll bookmark)

localStoragesessionStorageIndexedDBKue
PersistencePermanenTab session onlyPermanenConfigurable (session or expiry date)
Max size5–10 MB5–10 MBGBs (browser quota)~4 KB per cookie
Server accessTIDAKTIDAKTIDAKYes — sent with every request
Async APINo (blocks main thread)No (blocks main thread)Yes (Promise/event-based)TIDAK
JS readableYaYaYaOnly without HttpOnly flag
Web Worker accessTIDAKTIDAKYaTIDAK
Shared across tabsYaNo — each tab is isolatedYaYa
Safari ITP evictionAfter 7 days without interactionOn tab closeAfter 7 days without interactionDepends on Expires attribute

localStorage

Persistent, synchronous, origin-scoped. The workhorse that everyone reaches for and half the time shouldn’t.

What it actually is

localStorage stores string key-value pairs. That’s it. Storage limit is 5MB in most browsers, 10MB in some (Chrome gives you more). It’s scoped to the origin — protocol + domain + port — so http://example.com dan https://example.com have separate storage. Survives tab close, browser restart, everything except the user clearing their browser data or you explicitly calling 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'));

Where it breaks

  • Quota exceeded — throws a synchronous DOMException: QuotaExceededError. If you’re not wrapping writes in try/catch, you’ll find out via a user bug report.
  • Private/incognito — browsers give you a fresh, isolated localStorage with a stricter (or zero) quota. Firefox historically threw quota errors immediately. Never rely on localStorage being available without feature-detecting it.
  • Safari ITP — if a user hasn’t visited your site in 7 days, Safari may clear localStorage. This is documented behavior. It will surprise you at the worst time.
  • XSS — anything in localStorage is readable by any JavaScript running on your origin. If an attacker can inject a script, they get everything.

Gunakan untuk

UI preferences (dark mode, sidebar state, language), non-sensitive caching (last-fetched timestamp, static config), anything that should survive a page refresh but doesn’t contain auth tokens or PII. If you’re thinking about putting a JWT or an API key here — keep reading.

sessionStorage

Everything localStorage does, but with a shorter shelf life and one crucial isolation behavior that bites people constantly.

The tab isolation trap

sessionStorage is per-tab, not per-browser. Opening the same page in a new tab gives you a completely separate sessionStorage. This is bukan obvious to users, and it will not be obvious to you until someone files a bug about their multi-step form data vanishing when they “accidentally” opened a second tab.

The one exception: if the user opens a new tab via window.open() or middle-clicking a link, the new tab gets a copy of the parent’s sessionStorage at the time of opening. After that, the two are isolated. This is the kind of edge case that produces a great Stack Overflow question at 2am.

// 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

Multi-step form state, single-use wizard data, anything that should exist for one session and disappear when the user closes the tab. It’s legitimately useful for checkout flows — you don’t want partial checkout state hanging around from a previous session. Don’t use it if you need data shared across tabs or pages opened independently.

IndexedDB

The grown-up option. Async, transactional, and capable of storing actual JavaScript objects — not just serialized strings. Also has the most painful native API of the three, which is why almost nobody uses it directly.

What it actually is

IndexedDB is a full key-value store with support for indexes and cursor-based querying. Storage limits are generous — browsers allow a percentage of disk space, typically gigabytes in practice. You can store structured objects, Blobs, ArrayBuffers, and Files without manually serializing. It’s available in Web Workers. It’s what PWAs and offline-capable apps use to store data that would be unreasonable to keep in memory.

// 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' });

Where it breaks

  • Safari ITP — same as localStorage: after 7 days without interaction, Safari can evict IndexedDB data. This killed several PWAs before Apple fixed the behavior in newer iOS versions. If your target audience includes iOS Safari users, account for this.
  • iOS low storage — on iOS, the OS can delete IndexedDB data when storage is low. It won’t ask. The data just won’t be there.
  • Private browsing — Chrome allows IndexedDB in incognito (with a per-session quota). Safari in private browsing used to throw errors; behavior varies by version.
  • The native API — if you write raw IDB code without a wrapper, the callback-based event model will produce bugs you’ll spend an afternoon debugging. Use idb atau Dexie.js.

Gunakan untuk

Offline apps, large datasets that would abuse localStorage, anything you’d otherwise build on localStorage that keeps hitting the 5MB ceiling, file caching in PWAs. If you’re storing 50 user-generated documents locally, IndexedDB is the answer. If you’re storing a user’s theme preference, it’s overkill.

Kue

The oldest of the four. The only one the server sees. The only one with a 4KB limit that will absolutely bite you if you try to store a JWT in one.

What makes cookies different

Cookies are sent with every matching HTTP request automatically. This is both the feature and the problem. It means your session cookie reaches your server without any JavaScript involved — and it also means every request to api.example.com is carrying cookie overhead whether you like it or not.

The attributes that actually matter:

  • HttpOnly — JavaScript cannot read this cookie. XSS attacks can’t exfiltrate it. This is table stakes for session cookies.
  • Aman — only sent over HTTPS. Without this on a production site, you’re sending auth cookies over HTTP. Don’t.
  • SameSite=Strict — cookie is only sent when the request originates from your own domain. Effective CSRF protection, but it breaks OAuth redirect flows. SameSite=Lax is a reasonable compromise for most apps.
  • Expires / Max-Age — without these, it’s a session cookie that clears when the browser closes. Set an explicit expiry for “remember me” behavior.
// 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
});

If you’re debugging a gnarly cookie string, IO Tools’ Cookie Parser will break it apart into readable key-value pairs — useful when you’re staring at a 400-character Set-Cookie header and trying to figure out which attribute is wrong.

Where it breaks

  • 4KB limit — this is the total size including name + value + all attributes. A typical JWT is 400–800 bytes. A fat one with many claims is 1–2KB. You don’t have much room.
  • SameSite=Strict kills OAuth — when your IdP redirects back to your app after login, it’s a cross-site request. SameSite=Strict means your session cookie won’t be sent. Use Lax for anything that goes through OAuth flows.
  • Cookie order and path matching — when multiple cookies have overlapping paths, the browser sends them in an undefined order. Don’t rely on priority.
  • Third-party cookie deprecation — Chrome is removing third-party cookies. Cross-site cookie dependencies are a medium-term liability.

The JWT-in-localStorage Debate (the actual attack surface)

This one comes up in every auth discussion, and most people arguing about it are talking past each other.

The concern with storing JWTs in localStorage: if your site has an XSS vulnerability, an attacker’s script can read the token directly and exfiltrate it. The attacker now has a valid auth token they can use from anywhere, on any device, until it expires.

The case for HttpOnly cookies instead: JavaScript can’t read the cookie, so XSS can’t exfiltrate it. The session is still usable in requests (the attacker can make requests from the victim’s browser via XSS), but they can’t steal the token to use elsewhere. This limits the blast radius.

The honest take: the root problem is XSS, not storage location. If you have XSS, an HttpOnly cookie is meaningfully better — the attacker can’t take the token offsite. But fixing XSS is a more meaningful goal, achievable with a strict Content Security Policy, no untrusted third-party scripts, and proper output escaping.

If you’re building an SPA with a strict CSP and no third-party JS you don’t control, localStorage is probably fine for JWTs. If you’re running a site with Google Tag Manager, ad pixels, and a dozen npm dependencies, cookies with HttpOnly are much safer because your XSS attack surface is larger than you think.

When debugging JWT issues, the JWT Decoder on IO Tools is useful — paste the token and see the payload and expiry without writing code. The JWT Expiry Checker is handy for confirming whether a token is still valid when you’re tracing a 401 cascade.

Decision flowchart

Before you open a Stack Overflow tab at 3am, walk through this:

  1. Does the server need to read it? → Cookie. End of discussion.
  2. Is it larger than 5MB or do you need queryability? → IndexedDB.
  3. Should it disappear when the tab closes? → sessionStorage.
  4. Everything else → localStorage, with appropriate caution about what you’re storing.

The one thing everyone gets wrong

Storage APIs are not reliable across all users all the time. They can be cleared by the browser, the OS, privacy settings, or the user. Any architecture that treats client-side storage as a source of truth — rather than a cache — will eventually fail someone.

The pattern that holds up: treat browser storage as a performance optimization over your real source of truth (the server). Cache aggressively, but design your app to recover gracefully when the cache is empty. The 3am debugging sessions are almost never about which storage API you used — they’re about assuming the data would be there when it wasn’t.

Ingin bebas iklan? Bebas Iklan Hari Ini

Instal Ekstensi Kami

Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat

Ke Ekstensi Chrome Ke Ekstensi Tepi Ke Ekstensi Firefox Ke Ekstensi Opera

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!

IKLAN · HAPUS?
IKLAN · HAPUS?
IKLAN · HAPUS?

Pojok Berita dengan Sorotan Teknologi

Terlibat

Bantu kami untuk terus menyediakan alat gratis yang berharga

Belikan aku kopi
IKLAN · HAPUS?