Show HN: RemoteStorage – sync localStorage across devices and browsers (github.com)
When building frontends with Javascript, the native localStorage API is super useful for keeping track of state between sessions in the same browser, but it's not as good a solution when your data needs to be shared across multiple devices or browsers.
For instance, let's say you want to show a welcome modal to all new users that sign up for your product. If you use localStorage to track if a user has already seen this modal, your users will end up getting the experience repeatedly every time they switch devices or browsers, or whenever Chrome decides to nuke your data.
I built remoteStorage to help fix that. Using the same API as localStorage, remoteStorage allows you to easily read and write data on the fly while maintaining state across browsers and devices in order to provide a better user experience.
The project is built as a monorepo in Typescript and contains a JS library that works across any Javascript stack (including Node.js and React Native) and is lightweight (~1 kb minified). The server is built with Nest.js with a disk-persisted Redis Database. It can be deployed in a few minutes using Docker.
One of the biggest challenges when building this was coming up with a secure scheme for handling authentication while still keeping the API dead simple for frontend devs to use. While the project is intended to store non-sensitive data like impression events/preferences, I still wanted to make sure data couldn’t easily leak or be tampered with.
One solution has been to generate a unique secret UUID per user on your own backend to identify the user. Alternatively, you could create a simple wrapper/proxy API around remoteStorage that uses your own authentication method to verify the user's identity before allowing them to access the data (this is super simple to build with React Server Components). Then, you could pick a secure and secret Instance ID that is not publicly available to ensure that only your application can access the data.
Has anyone felt the same pain points with localStorage before? Is this solution useful? Let me know what you think or ideas for how I can improve it :)
68 comments
[ 5.1 ms ] story [ 132 ms ] threadNot sure if this is a good example; how many users sign up for the same product multiple times?
Selling point seems to be the API, I'd prefer an alternative to document.cookie.split(";"), a sane way to store objects, etc, frontend friendly.
Typically use cookies to store data for the server (session id etc).
I like the minimalism too, but I don't think it tells me "exactly what it does". I have no idea from that page what the backend requirements are. Is it a hosted solution? Is it using some browser profile functionality? Do I have to set up a server?
It demonstrates the simplicity of the client side code for sure. But the server stack is obviously where the details are going to be.
[1]: https://pouchdb.com/
I think the main difference here is the use case. remoteStorage is not intended to be used as a database (just like localStorage is not intended to either), but rather to store non PII data such as preferences, impression events, etc. without having to roll and stand up your own DB and API.
The developer's backend could issue a JWT, which is validated by the remoteStorage backend. Then, use the `sub` field as the user ID. JWTs are pretty ubiquitous in frontend applications, so many people would be able to reuse their existing auth setup.
An OpenAPI spec should be enough, right?
The server is nothing special by design, just essentially Docker/Node/Redis with persistence enabled.
The free hosted community server is supposed to be used for testing / proof of concept.
I know it would probably break things but if Google could automatically sync local storage and IndexedDB when I'm signed into Chrome on all my devices then a lot browser of apps wouldn't require making an account or even a server for that matter.
I am also pretty surprised Google hasn't yet built something that solves this. When localStorage first came out, I was expecting a natural extension would be a way to sync it to a centralized store to avoid having state reset between sessions/devices.
Thanks very much for improving the landscape and the help pushing the boundaroes of webapps.
This is super cool.
"built into Chrome" implies that Chrome is the only web browser.
For Sandstorm, we've always want to make sure app data is stored on a server, but an increasing number of web apps love to use localStorage as a cheap hack to avoid writing a backend. I recall a bunch of attempts back in the day, but we were always looking for a simple way to include something with an app written to use localStorage which could store the data on a server backend instead.
But still a very cool project, thank you for sharing!
That being said, the server is blazingly fast as it leverages Redis and Fastify under the hood. In most of my testing, even on WiFi in SF with a server in Oregon, I would achieve <20ms reads/writes.
I’m tempted to rewrite the backend in lambda+dynamoDB for a completely serverless backend (though for anything serious you’d probably want provisioned lambdas to avoid the cold start).
Ideally we have as many different server and client implementations as possible in the monorepo.
Machine A is online and sets "x" to 1.
Machine B is offline and sets "x" to 2.
Machine B goes online.
What the resulting state on A and on B?
What if initially A was offline and B was online?
What happens to storage requests made while offline? Are they cached locally and propagated once online, or are they lost?
Judging by the title, I thought this tool actually just synced the existing localStorage instead of being its own storage. Since localStorage has onChange events it would be trivial to built, it would still be sync, it wouldn’t require any changes to the existing code, etc
But it’s dead simple if e.g. using something like Clerk.dev for your auth.
To add something new, that needs to persist, will not require going to a backend team and begging for a new API from them.
A common difficulty is the simplest scenario, where do I store this frontend user preference? How do I store this that prevents nagging multiple times across devices?
Backend teams will argue this is a frontend piece of data, and they provide APIs and backend storage that is agnostic of a specific frontend.
This feels like a valuable addition.
https://docs.deno.com/runtime/manual/runtime/web_storage_api
It sounds a bit radical but it may work!