localStorage in JavaScript: Why you should stop using local storage today

- 👤 Andrés Cruz

🇪🇸 En español

localStorage in JavaScript: Why you should stop using local storage today

If you are building a modern web application today and still relying on localStorage in JavaScript as a storage solution, you are probably accumulating problems that you don't see yet… but they will arrive.

localStorage appeared around 2009 and, although it remains widely documented and taught, it has not evolved at the pace of the modern web. And that matters more than many tutorials admit.

Previously we saw how to use Ambient Light events with JavaScript using AmbientLightSensor

What is localStorage in JavaScript (and why it keeps appearing in all tutorials)

localStorage is part of the Web Storage API and allows you to save key-value pairs in the user's browser persistently. Unlike sessionStorage, the data is not deleted when the tab or browser is closed.

The problem is not what it is, but why it is still recommended without context.

How local storage really works in the browser

  • Data is saved by origin (protocol + domain).
  • Everything is stored as UTF-16 strings.
  • Operations are synchronous.
  • There is no concurrency control, transactions, or isolation.

This might sound acceptable in small examples. In real applications, it is not so much.

localStorage vs sessionStorage: differences that do matter

Both share almost all limitations. The main difference is the duration. Neither solves modern problems like concurrency, structured data, or performance under load.

The big problem with localStorage: it only stores strings

This is the origin of many silent errors.

localStorage does not understand objects, arrays, or types. Everything goes through manual serialization. And this is where the bugs begin.

Serialization and deserialization: the origin of silent bugs

In my experience, a surprising amount of corrupt states in production came from poor management of JSON.stringify() and JSON.parse().

Values like "true", "false", "null", "" or even poorly versioned data end up causing unpredictable behavior. The result is usually the same:

“clear the cache and try again.”

As a customer, in the last year three different services gave me exactly that solution. It wasn't magic: it was poorly managed localStorage.

localStorage does not use structured data (and that leaves it in the past)

Modern JavaScript uses the StructuredClone algorithm to transfer data safely and consistently.

APIs such as:

  • IndexedDB
  • Web Workers
  • postMessage
  • Cache API
  • BroadcastChannel

use structured data without manual serialization.

localStorage does not.

When you work with these modern APIs and go back to localStorage, it feels like going back a decade. And worst of all: there are no plans to update it.

Real security problems with localStorage

This is not theory. It is common practice… and dangerous.

Why you should never save tokens, JWTs, or sessions

I have seen it too many times:

  • JWT
  • Session IDs
  • API keys
    saved in localStorage “because it's easy.”

localStorage is accessible from any script running on your page. An XSS is enough to exfiltrate everything. And since the data is persistent, the impact is multiplied.

You should never store sensitive data in localStorage.

Performance and main thread blocking

localStorage is synchronous. Every read and write blocks the main thread.

Synchronous operations and their impact on UX

In applications with frequent state changes, this directly affects:

  • animations
  • scrolling
  • user interaction

On low-power devices, the problem is exacerbated. I have seen stuttering interfaces simply from reading and writing too much to localStorage.

Asynchrony is not a luxury. It is a requirement for fluid applications.

Structural limitations: concurrency, atomicity, and scalability

localStorage:

  • Has no transactions
  • Does not guarantee atomicity
  • Does not offer isolation
  • Has no locking mechanisms

localStorage and the impossibility of using Web Workers

You cannot access localStorage from Web Workers. That leaves it out of any serious concurrency strategy.

It is not designed to scale. And it never was.

Storage limits and data lifecycle management

The famous ~5 MB limit is still real (and varies by browser).

The real limit of localStorage (and the risk of eviction)

That space is:

  • small for modern apps
  • subject to eviction
  • your responsibility to manage

The browser does not warn you. You must handle failures, cleanup, and recovery. Something almost no one does well.

A brief history of browser storage: cookies, WebSQL, and repeated mistakes

Before localStorage, we had cookies. Then WebSQL.

Why WebSQL died

  • Implementation limited to few browsers
  • Lack of W3C standardization
  • Direct competition with IndexedDB
  • Security concerns

WebSQL was much loved… and yet it died.

The parallelism with localStorage is evident: popular, convenient technology, but poorly aligned with the future.

Why IndexedDB is a real alternative to localStorage

IndexedDB is not perfect. But it solves the important problems.

Asynchronous storage and structured data

  • Asynchronous API (does not block the thread)
  • Structured data (StructuredClone)
  • Much higher quotas
  • Greater reliability

Not because IndexedDB is perfect, but because it fits how web applications work today.

The IndexedDB API is bad (but the concept is correct)

Let's be honest: the native IndexedDB API is clunky, event-based, and verbose.

That's why it makes sense to use lightweight libraries.

Why using a lightweight library makes sense

Good libraries:

  • use Promises
  • reduce boilerplate code
  • avoid common mistakes

Personally, I don't recommend huge libraries. For many cases, you don't need complex versioning or advanced cursors. I even created a small library focused only on the essentials, because most solutions were oversized.

When does it make sense to use localStorage?

To be fair: there are valid cases.

Simple cases where localStorage is not a problem

  • UI flags
  • non-critical preferences
  • trivial data with no impact on security or performance

What you should never save, even "for convenience"

  • tokens
  • sessions
  • complex state
  • data that affects critical logic

There, localStorage stops being a simple solution and becomes technical debt.

Conclusion: localStorage is not the future of storage in JavaScript

localStorage continues to be taught because it is easy to explain.
But ease does not equal good architecture.

New developers gain much more from learning:

  • asynchrony
  • Promises
  • structured data
  • IndexedDB

than trying to understand why "0" breaks a condition or why a user gets null data after an update.

If you can avoid localStorage today, do it.
And if you can't, at least use it knowing exactly what sacrifices you are making.

Frequently Asked Questions about localStorage in JavaScript (FAQ)

Is localStorage secure?

Not for sensitive data. It is vulnerable to XSS and accessible from any script.

How much space does localStorage allow?

Approximately 5 MB, depending on the browser, and subject to eviction.

Is localStorage synchronous or asynchronous?

It is completely synchronous and blocks the main thread.

Does IndexedDB replace localStorage?

For most modern applications, yes, it should.

Does localStorage block the main thread?

Yes. Every read and write operation.

Technical Comparison: localStorage vs. IndexedDB

 

FeaturelocalStorageIndexedDB
Data TypeStrings only (UTF-16)Objects, Blobs, Arrays, etc.
Capacity~5 MB (rigid)% of disk (dynamic, much larger)
Operation ModeSynchronous (Blocks UI)Asynchronous (Does not block)
Security (XSS)Very vulnerableVulnerable (but allows isolation)
Web WorkersNot availableCompatible
TransactionsNoYes (Guarantees integrity)

I agree to receive announcements of interest about this Blog.

Discover why this synchronous API is harming the performance and security of your modern web applications. We analyze its risks compared to IndexedDB and how to properly manage structured data in JavaScript.

| 👤 Andrés Cruz

🇪🇸 En español