Browsers provide a dizzying number of methods for storing data. Some of them, everyone knows about, a lot of them, many people have never heard of. And if you've ever popped open dev tools and looked at the Application tab, you may have wondered, “What is all of this?”.

- Local Storage: If you've ever built a dark mode into an app, you may have used local storage. This is a synchronous, low storage option that does not expire with session. Limited to about 5MB, blocks the main thread, can only store strings, and is not available in web workers or service workers.
- Session Storage: Tab specific, and scoped to the lifetime of a tab. Similar to local storage, this is limited to about 5MB of data, blocks the main thread, can only contain strings, and is not accessible from web and service workers. This is great for a small amount of session specific data such as for multi-step forms, and temporary user customizations.
- IndexedDB: Asynchronous, does not block the main thread, accessible by both web and service workers, and stores... a lot. The amount varies based on browser and your local disk space. For example, you have an estimate of 0 GB available for ethang.dev shared between IndexedDB and Cache API. (Try opening FireFox, Safari, and Chromium browsers to see the difference.) It also allows you to create indexes, use cursor iteration, and stores most complex objects.
- Web SQL: Deprecated, don't use this.
- Cookies: Sent with HTTP requests and perfect for things like authentication tokens. Cookies should not be used for storage. Unfortunately cookies have been abused in the past by third-parties for tracking which sites you visit. This has made them the target of privacy laws. An alternative for better, more structured analytics reporting would make use of the Beacon API.
- Private State Tokens: This is a way to validate a users identity between two websites/origins without the use of cookies and sending private information. An issuer can validate a users identity and store a token in the browser. Then, from another origin a publisher can retrieve that token. Unfortunately, this feature seems to be in Chromium browsers only.
- Interest Groups: This is another, as of now, Chromium only feature that is meant to help move away from third-party cookies. In summary, advertisers can create an “interest group” based on a users interests and behaviors. This keeps information in the browser and allows advertisers to create targeted ads without the legal risks of collecting identifying information from cookies.
- Shared Storage: Yet another Chromium only feature designed to allow for cross-site storage while still protecting user privacy. It is a much more general storage than Private State Tokens and Interest Groups in that it allows anything to be shared between sites.
- Cache Storage: My favorite in the list, a feature in all browsers that simply stores Request/Response pairs. This shares a storage limit with IndexedDB (0 GB). The Cache API is available in both service and web workers, and does not block the main thread. Basically, when you add a Request to Cache Storage, it will resolve and store the Response as a key/value pair. Responses can then be retrieved from storage by using the original Request. Requests are considered “unique” based on URL, Vary Header, and HTTP method.
Why Haven't I Heard of Most of These?
I would guess that most developers only really know of cookies, session storage, and local storage. Why aren't we using at the very least IndexedDB and Cache Storage more often? I think the short answer to that is TanStack Query and state management. Web standards are very slow to change, and a lot of these were created as proposals in reaction to modern web development. When building a React app, it is generally smarter to choose TanStack Query over Cache Storage. And if needed Redux Toolkit over IndexedDB. Mobile developers are familiar with tools like localForage. All of these options allow you to configure various methods of persisting data in the browser. But they also bring a lot more features and tooling that are out of scope for JS standardization.
With React Server Components, React is looking to change the game again. No other framework has pulled off an architecture with the seamless integration between client and server that RSC has. What this means for web apps is, in a lot of cases things like authentication tokens may never need to touch a client, and when they do, they can be passed to the client via React component props. All of this without losing reactivity. In comparison, Astro does not support rerendering server-only content. Rails and Turbolinks can only synchronously refetch server content, and the client will lose state in the process. Pulling browser only APIs into this can complicate matters.

That's not to say I would discourage the use of any of these storage APIs. The more well known cookies, session storage, and local storage have a place in every web stack. IndexedDB and Cache API may be good to reach for in small apps that don't have a lot of interactivity outside of form submissions that just need a little offline storage. In fact, I found it very easy to create a client-side fetcher that makes use of the Cache API and Indexed DB for cache invalidation in about 100 lines of code. But I wouldn't use this strategy in most production apps. I would still recommend TanStack Query in the simplest apps just for the ergonomics. Once you go beyond the complexity of a website and start building apps, you're likely going to need tooling that can match that complexity anyway.