Stop Using localStorage!

Julien Etienne
7 min readDec 22, 2023
Image from ducktypelabs.com: Is putting JWTs in local storage “bad”?

The title is not clickbait, the message is abrupt “Stop Using localStorage!” there is no ambiguity here and we are not holding hands and singing Kumbaya to soften the blow.

Problems with localStorage?

localStorage came about around 2009 as a 5MB string based storage. Let’s cut to the chase with some bullet points since our attention spans are in disarray these days:

  • A collection of Strings: It can only store strings. If you want to store or retrieve you have to serialize and deserialize. If you forget to you may be exposed to quirks that have been responsible for all sorts of broken websites. E.g. When you store “true” or “false” there is also a potential `null`, `undefined` or “” to look out for depending on your setup.
  • No Structured Data: JavaScript has a thing called The structured clone algorithm. You really need to know this is a thing. Modern APIs and updated APIs such as postMessage, WebWorkers, IndexedDB, Caches API, BroadcastChannel, Channel Messaging API, MessagePort and the History API all use structured data. It solves the problem with serializing and deserializing JSON within an app. localStorage had not been updated with this feature and there are no discussions around it happening in the future.
  • Common security compromises: To clarify, you should never store sensitive data in any persistent storage that wasn’t designed specifically for it. Developers still commonly store Session IDs, JWTs, credit card details and API keys in localStorage. It’s a very common security hack because it is as easy as window.localStorage
  • Performance: Historically localStorage had it’s slow moments, though there has been some optimisations over time that brings it up to a resonable performance. Despite this, it’s still not desirable for concurrent applications with a need for frequent transactions. If you benchmark on let’s say the latest MacBook without throttling performance you will may not understand the cost implications on low powered devices.
  • Size Limitation: localStorage has a 5MB cap which is subject to eviction. This is a very small amount for modern applications. It’s difficult to store encoded media with such a small amount. And it’s not set in stone, just like all persistent storage on the web localStorage can and will be evicted by the browser at some point. You are expected to manage that part of the data life-cycle, despite there being no reminder to.
  • No Web Worker Access: I hope you’re understanding that localStorage is not an API for the future or to harness concurrency, which strives on low powered devices.
  • No Atomicity or Isolation: localStorage does not guarantee atomicity across multiple operations. There is no locking mechanism to ensure what gets written or to prevent information being written over.
  • No Data Separation or Granular Origin Scoping: localStorage is just an object of strings, there is no data separation, user data is mixed with app data. Although it uses the same-origin policy there is no way to restrict data by a particular doman or subdomain that has access. This can create duplicated work when meeting GDPR standards across multiple domains.
  • No Grouped Transactions: localStorage has no transactions by the database definiton, but it also has no way to group operations. Each operation is synchronous, non-isolated, with no locking.
  • Serialization Gotchas: So many websites are operating with broken state because of poor localStorage management. From the time of this article, just last year a lone as a customer I was told to “Clear your cache” by 3 different web services, which I discovered was due to improper localStorage management. If you’re not familiar with the gotchas when storing data in localStorage you may be creating bugs in your application that you may never personally encounter. These type-errors are not always obvious, especially for new developers.
  • Synchronous Blocking Operations: localStorage is not asynchronous and will block the main thread. It may even make your animations choppy depending on how much you read/ write and at what frequency. Asynchronicity is fundamental to creating fluid applications, especially for mobile devices

Where did WebSQL go?

WebSQL aimed to be a simple SQL database interface for the web. It had some decent support but eventually faced challenges that led to deprecation.

Why did they drop the baby?

  • Single-Vendor Implementation: WebSQL was primarily a webkit thing (Initially Chrome and Safari). A lack of support from other major browser vendors (Mozilla and Microsoft) made developer adoption close to non-existent in the commercial world.
  • No W3C Standardization: this is crucial for adoption. W3 appeared to drop the proposal in 2010.
  • Competition with IndexedDB: IndexedDB was gaining more traction and support during the rise of WebSQL. Unlike WebSQL, IndexedDB was designed to be a standard, cross-browser solution.
  • Security Concerns: Several developers and security specialists showed concerns regarding WebSQL’s safety. They were sceptical of various aspects including lack of permission controls and SOL style vulnerabilities.

Eventually, IndexedDB became the “recommended” standard for client-side storage, being seen as more robust and cross-browser friendly. But what good is “recommended” when the majority of experienced front-end developers at the time of this article have avoided it like the plague?

Despite its shortcomings, at the time WebSQL was highly praised amongst the web community and was a worthy competitor.

What about Cookies?

Cookies were created in 1994 by Lou Montulli, a web browser programmer at Netscape Communications.

Some of you were not even born when cookies were effing stuff up. The title of this article should be “Stop Using Cookies and localStorage” but that’s a very difficult fight, (yes we should use secure cookies)

  • Size Limitations: Cookies are typically limited to roughly 4KB per domain.
  • Data Sent with Every Request: Cookies are sent with every HTTP request to the associated domain. If your data doesn’t need to be transmitted with every request, this can result in unnecessary overhead and increased bandwidth usage.
  • Security Concerns: Cookies are more susceptible to XSS. Because cookies are automatically included with every request to the domain, they can be targeted by malicious scripts.
  • Expiration and Lifetime: Cookies were designed to expire by a given date.
  • Increased latency: Because cookies are automatically included in every HTTP request to the domain, they typically lead to increased latency thus making your website slower to load.

Why IndexedDB

  • Better Performance: Unlike localStorage, IndexedDB operates asynchronously, preventing blocking operations. (The API is event-driven not Promise based)
  • Ample Storage Quota: IndexedDB provides a larger storage quota (dependent on the browser, OS and available storage) compared to localStorage’s 5MB cap.
  • Reliability and Structured Data: Storing and retrieving data in localStroage can yield unpredictable results if not done properly. IndexedDB reduces common type-coercion and embraces the structuredClone algorithm, ensuring data integrity.

but…

You probably don’t want to use IndexedDB directly

The 2nd point of this article

IndexedDB is the exception to the rule of avoiding dependencies. Think of IndexedDB as a backend database, you probably want an ORM or database-library to process queries. This is similar to wanting an IndexedDB library, except because the IndexedDB API is atrocious.

The reason you want to use a library is because mostly they:

  • Are promise based
  • Are easier to use
  • Reduce boilerplate code
  • Focus on more meaningful things.

Personally I don’t recommend using large libraries for IndexedDB because what you’re tyring to accomplish does not require more than a hanful of single digit kBs. Excessive IndexedDB libraries will not be doing anything meaningful for your real-world scenarios.

The one problem I found with most IndexedDB libraries is how they are mostly oriented around versioning, which you probably don’t need at all, especially if you’re just looking for a reasonable localStorage alternative.

Quick plug: I made db64, a library to focus on just the more relative aspects of IndexedDB. Try out the example here

If you do need versioning or cursors I’d recommend idb, which is a comprehensive library that covers all niche cases well.

Conclusion

Although it’s not always possible, in this day and age we should aim to avoid localStorage usage.

New developers will have more meaningful and reusable knowledge to gain playing with Promise(), async/ await and structured data than trying to figure out why the number ‘0’ is making their conditions truthy or why their angry user got nulldeliveries.

To recap there’s a speed advantage, you can store types reliably and you can even use cursors to iterate over entries. With this tech you could build a client-side search-engine with accumulated data fetches that don’t make your animations fidget like how localStorage blocks and interferes with them 👀.

IndexedDB is commonly described as “low-level” . There’s absolutely nothing low-level about IndexedDB, it’s just an API with an old-style and unfriendly syntax. But that doesn't negate it’s underlying capabilities, hence common library usage.

You don’t necessarily need to use the API directly unless you want to, but it does makes sense to use a small wrapper library or why not make your own.

Thanks for the read :)

--

--