downsides to Using IndexedDB/LocalStorage as a cache?
I'm making a chat app where the user can send/receive messages, which are stored in supabase. I want to cache these messages client-side using IndexedDB or LocalStorage to allow users to instantly view their messages upon page exits and re-entries, without fetching from the db every time. To me, this just seems idiomatic, allowing for instant loading as well as saving bandwidth
"localstorage/indexdb can be cleared by client" -> all caches get stale sometimes, just refetch
"your localstorage is not accessible on other devices" -> that's fine, just fetch from server like normal
"5mb limit for localstorage" -> indexedDB is limited by disk space
maybe I'm missing something obvious?
10 Replies
The tricky part with caching is usually synchronizing the cache with the upstream correctly. Generally it ends up being too much of a headache to implement and maintain properly, and the benefit to doing it is negligible (at least from my experience).
If you are developing something that should be used in places with spotty or slow internet connections, it makes sense to spend extra effort to implement this. But otherwise, it might be more trouble then it's worth.
Just to add to this, since I saw what you wrote on #tech-discussion , how would you check if the messages are stale or not? Are you always fetching the entire conversation whenever the user loads the app/chat page, or do you want to approach it differently?
u could do that and check client side, or u could use server components, and have the client send the hash of the messages in cookies so the server makes the equality check with the hash stored in db.
yeah, seems like the role for the library to handle all the weird edge cases, which is why i’m confused that there isn’t much talk about it from nextjs. and I think it’s useful in most situations, even if u have a fine internet connection, as you can save like 1-2 seconds every time someone enters ur app
So if I understand you correctly you would do something like the following:
1. Generate a hash of the messages from the local DB.
2. Send it to the server via a cookie
3. Have the server fetch the entire conversation from the DB, and generate a separate hash for it.
4. Check if the hashes are equal.
5. If the hashes are not equal, show the data from the remote DB. Otherwise show the data from the local DB.
Ah sorry, you said the DB would keep a hash stored. So you would just be fetching the hash value and comparing it to the one from the cookie.
The potential problem with that approach might be that with every message sent, a new hash needs to be generated (based on the entire convo history) and written to the DB.
So whenever you (or someone else) sends a message, the server needs to download the entire convo, add your message, generate a new hash, and write the hash to the DB.
merkle tree? 💀 seems like over engineering though. or just hash the last 15 messages, because that's realistically what the user is gonna look at, then you can just prefetch the rest
lol yeah, a merkle tree might be a bit too much here 😄
Ok so let's go with only displaying and hashing the latest 15 messages. That means that:
1. On page load you have to fetch the hash from the DB, and compare it with the local hash.
2. If the hashes do not match, you re-sync the local DB data (messages and hash) with the upstream.
3. Every time a user sends a message, the server should add the message to the chat.
4. And also fetch the last 15 messages, hash them, and then update the server hash?
5. When another user sends a message, all the users should be notified of this (with maybe something like WebSockets) and re-sync their local DB.
Honestly, I'm not sure if this would be worth it :/ I think the network/CPU costs might be higher actually, since you'd constantly (on every sent message) be fetching the latest 15 messages, generating a hash for them, and updating the hashed DB.
You could maybe do something with WebSockets?
Cache the user convo locally, fetch the latest 15 messages as soon as the user opens the app (while displaying the cached data), and create a WebSocket connection to the server.
Then messages can get streamed to and from the server in realtime. I think this is the approach generally with chat apps.
I usually just do this w a websocket and most people do too. Chats are realtime so its a perfect use case for it.
With a socket you are gonna have two parts for data :
Initial seed -> Getting the previous history for a chat through a http req or graphql doesn't matter
Listen for new messages -> Get them to the page someway
I've used it with tanstack query to create the initial seed portion and manipulated that query's cache to push new messages onto the front through a listener on the channel/room/topic/whatever
If you want to avoid using a SaaS you can take a look at this: https://docs.soketi.app/
soketi | 1.x | Soketi
Soketi is your simple, fast, and resilient open-source WebSockets server. 📣
Yup, this is what I would do too. Thanks @keef (Rustular CVO) for the example 🙂
thanks both for the help. followup question: once I have the indexedDB, I want to get information from the indexedDB and include this in the first render. However, all nextjs code executes during hydration, after the first render. However, I find it entirely possible to include information from indexedDB in this first render, because it's just client side information. What's the idiomatic way to accomplish this?
@noblica @keef (Rustular CVO)
Not sure if that's possible. Before hydration, your code is not running on the client side, so you don't have access to it.