True or false? IndexedDB is limited to 25 MB. False. Gone are the days of tiny storage quotas.
True or false? Local storage should be avoided. True. It's synchronous and may cause performance issues by blocking the main thread.
All right, here's another one. True or false? Cookies are a great way to store data. False. They've got their uses, but should never be used for storage.
How about this one. AppCache is a great way to make your app work offline. Yeah, trick question. Absolutely false. AppCache is awful, and it's going away soon, thankfully.
So how should we be storing data and caching our critical app resources on the client? How much can we store? How does the browser deal with eviction? And be sure to stick around to the end, and I'll tell you how you can start Chrome with only a tiny storage limit, so you can test what happens when you exceed your storage quota.
IndexedDB and the cache storage API are supported in every modern browser. They're both asynchronous and will not block the main thread. They're accessible from the window object, web workers, and service workers, making it easy to use them anywhere in your code. There are several other storage mechanisms that are available in the browser, but they've got limited use and may cause significant performance issues.
If you're concerned about storing large amounts of data on the client, don't be. Unless you're trying to store several gigs, modern browsers typically won't even bat an eye. And even then, it really comes down to the amount of disk space available on the device. Of course, implementations vary by browsers.
Firefox allows an origin to store up to 2 gigs. Safari allows an origin to use up to 1 gig, and when that limit is reached, Safari is currently the only browser that'll prompt the user to increase that limit. And Chrome, well, look, it's a little complex, but stick with me here. Chrome and most other Chromium-based browsers limit storage to 80% of the total disk space, and each origin can only use 75% of that. For example, if you had a 10 gig hard disk, Chrome will limit the storage to 8 gigs. Then each origin would be limited to 6 gigs. Essentially, each origin will be allowed to use up to 60% of the total disk space. It sounds complex, but there's an easy way to see what's available.
In many browsers, you can use the Storage Manager API to determine the amount of storage that's available to the origin and how much storage that you're already using. It reports the total number of bytes used and makes it possible to calculate the approximate bytes remaining. Unfortunately, the Storage Manager API isn't implemented in all browsers yet, so you must use feature detection before using it. But even when it is available, you still need to cache over quota errors. In some cases, and I'm looking at you, Chrome, it's possible for the available quota to exceed the actual amount of storage available.
Most Chromium-based browsers factor in the amount of free space when reporting the available quota. Chrome does not, though, and it will always report 60% of the actual disk size. This helps to reduce the ability to determine the size of stored cross-origin resources.
So what should you do when you go over quota? Most importantly, you should always catch and handle right errors, whether it's a quota exceeded error or something else, then, depending on your app design, decide how to handle it. For example, delete content that hasn't been accessed in a long time, or remove data based on its size, or provide a way for users to choose what they want to delete.
Both IndexedDB and the Cache API throw a DOMError named QuotaExceededError when you've exceeded the quota available. For IndexedDB, the transactions onabort handler will be called passing an event. That event will include a DOMException in the error property, and if you check the name of the error, it'll return QuotaExceededError. For the Cache API, rights will reject with a QuotaExceededError DOMException. Data stored in the browser can be cleared in a couple of ways. It's most commonly initiated by the user choosing to clear data in the browser site setting panel, but it can also happen when faced with storage pressure, like low disk space. In this case, browsers typically automatically delete data from the least recently used origins and continue to delete that until the storage pressure has been relieved.
If the app hasn't synced data with the server, it will cause data loss, and means that the app won't have the resources needed to run, both of which can lead to a negative user experience.
Thankfully, research by the Chrome team shows that this doesn't happen very often, and it's far more common for users to manually clear storage. Thus, if a user visits your site often, the chances are small that data will be deleted.
Let's take a look at a specific example of how automatic eviction might happen in Chrome.Origin A is the least recently visited site. Origin B is the next least recently visited site, and so on. Origin E and origin K are getting close to their quota limits, but they haven't reached it yet. And the overall usage is less than the total quota, so nothing is going to be evicted. Origin B has a star next to it because it was granted persistent storage, meaning that it can only be deleted by the user.
Let's say the user visits origin N again, which happens to be a music playing site. The user saves a few more songs for offline listening. Now each origin is still within its quota limit, but Chrome has exceeded the overall limit. To get back under the overall limit, Chrome will start evicting stored data from the least recently used origin first and continue until it's back under the total limit.
Firefox and other Chromium-based browsers work in essentially the same way. Safari is a little different. When it's out of storage, it will prevent anything new from being written. But they recently implemented a new seven-day cap on all writable storage, including IndexedDB, service worker registrations, and the Cache API. This means that after using Safari for seven days and not interacting with the site, it will evict all content for that site. This eviction policy does not apply to progressive web apps that have been added to the home screen, essentially installed PWAs.
Modern computers typically have large hard drives, which makes it hard to test the over-quota failures. So here's a little pro-tip. Create a small RAM disk. Here, I've created a 500 meg RAM disk on my Mac. Then start Chrome using the user data dir flag. This tells Chrome to store the user profile and user data on the RAM disk.
open -a "Google Chrome Canary" --args --user-data-dir=/Volumes/RamDisk500mb
Chrome now thinks my disk is only 500 megs. Thus it's going to limit my storage quota to only 300 megs, which I can quickly fill. This makes it much easier to verify that my code behaves properly when it hits those quota exceeded errors.
Chrome DevTools also have helpful features for understanding what's going on with the data that you've stored.
In the application panel, the clear storage panel will show you how much storage you're using for the origin and makes it easy to clear some or all of that data that you've got stored. The storage panel lets you see what's in local and session storage, as well as what's in IndexedDB, including the actual databases and even the individual entries. And the cache storage panel shows you what's stored in cache storage.
Gone are the days of limited storage and prompting the user to store more and more data. Using the cache storage API and IndexedDB, you can effectively store all the resources that your app needs to run.