localStorage vs cloud storage for small web apps
localStorage is where most small web apps keep data first. It’s instant, free and private. It also stays in one browser and can vanish. Here’s when it’s enough, when to move data online, and how to use both.
Written by jstash, which makes a cloud storage option mentioned here. Updated 4 October 2026.
Summary
- localStorage: no setup, no account, works offline, and the data stays on the device. But it holds up to 5 MiB of text, lives in one browser, and can be cleared without warning.
- Cloud storage: follows the person to any device and survives a cleared browser. It needs a login, a network connection and a service you trust with their data.
- Rule of thumb: if losing the data would be annoying rather than harmful, and nobody expects it on another device, keep it local. Otherwise store it online, and keep a local copy so the app still opens instantly.
What localStorage does well
- Nothing to set up. No account, no server, no key.
- Fast and offline. It reads straight from the device.
- Private by default. The data never leaves the browser, so there’s nothing on a server to secure or disclose.
- It lasts. “The stored data is saved across browser sessions” and has no expiry date.
function saveLocal(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
// Storage is full, or the browser blocks it
}
}
function loadLocal(key, fallback) {
try {
return JSON.parse(localStorage.getItem(key)) ?? fallback;
} catch {
return fallback;
}
}
localStorage stores strings, so values go through JSON. setItem throws when storage is full, and localStorage itself throws when the person has blocked sites from saving data, so wrap both.
Where localStorage lets you down
- One browser, one device. Their phone has its own empty copy. So does a second browser on the same laptop.
- Private windows forget it. It’s cleared when the last private tab closes.
- Safari can delete it. With cross-site tracking prevention on, Safari deletes data a site’s scripts created, including localStorage and IndexedDB, once the person has gone seven days of Safari use without interacting with the site (MDN, WebKit). Web apps added to the home screen keep their own count, based on actual use.
- Browsers can evict it. Site data is “best-effort” by default and can be removed when the device runs low on space.
navigator.storage.persist()asks the browser to keep it, but the browser may say no. - It’s small and it blocks. Up to 5 MiB per origin, and reads and writes block the page until they finish. IndexedDB holds far more and doesn’t block, but it’s still one browser on one device.
- Any script on the page can read it. Don’t keep tokens, passwords or anything sensitive there: “a single Cross Site Scripting can be used to steal all the data” in it.
What cloud storage adds, and what it costs
Cloud storage here means saving each person’s data to a service online, under their account. It adds two things:
- It follows the person. They sign in on their phone and their data is there.
- It survives. A cleared browser, a new laptop or Safari’s seven-day rule don’t touch it.
And it costs:
- A login. To hand someone their data back, the service has to know who they are. If your app has no accounts yet, adding sign-in is the bigger change.
- A network. Saves can fail or arrive late, so your app needs to cope with being offline.
- Trust. You’re now keeping personal data with another company. Tell people what you store, and pick a service whose limits, price and future you’re comfortable with.
Side by side
| Feature | localStorage | IndexedDB | Cloud storage |
|---|---|---|---|
| Setup | None | None, but a lower-level API | A login and a service |
| Room | Up to 5 MiB per origin | Much more, depending on disk size | Set by the service |
| What it holds | Strings, so JSON | Structured data and files | Depends on the service |
| On their other devices | No | No | Yes |
| Works offline | Yes | Yes | Only with a local copy |
| Survives a cleared browser | No | No | Yes |
| Who holds the data | Their browser | Their browser | The service |
When localStorage is enough
- Preferences for visitors without accounts: theme, language, a dismissed banner.
- Drafts and half-finished forms that only matter for a session or two.
- Games and tools people use on one device, where starting over isn’t a disaster.
- Anything you’d rather not hold about people at all.
When to move to cloud storage
- People already sign in, and expect their data on every device.
- Losing it would cost them real work: notes, saved lists, progress built up over weeks.
- Many of your visitors use Safari and can go seven days of browsing between visits, after which Safari may have deleted their local data.
Use both: a local copy plus cloud storage
You don’t have to choose. Keep localStorage as a fast local copy and the cloud as the real one. Here save() and load() are the jstash Apps helpers from the Apps guide; the same pattern works with any cloud store.
// After sign-in: show the local copy at once, then the saved one
let settings = loadLocal('settings', { theme: 'light' });
render(settings);
const saved = await load('settings'); // null until something is saved
if (saved) {
settings = saved;
saveLocal('settings', saved);
render(settings);
}
// On every change: update both
async function update(next) {
settings = next;
saveLocal('settings', next);
render(next);
await save('settings', next);
}
// On sign-out: forget the local copy, so the next person here does not see it
function onSignOut() {
try { localStorage.removeItem('settings'); } catch {}
}
- When the app opens, the saved copy wins. That keeps it simple, and it’s right for most settings and progress.
- If the same person might edit on two devices at once, send the document’s
ETagasIf-Matchwhen you save. jstash then refuses a save that would overwrite a newer one, with412 ETAG_MISMATCH, so you can load it again and reapply the change (endpoints). - Save when something changes, not on a timer, and wait for typing to pause before saving. Most services limit saves; jstash Free allows 100 per person a day.
Cloud storage options for a small app
- Your own small backend: a serverless function and a database. Full control, but server code to run.
- Supabase or Firebase: a hosted database your page talks to directly, protected by security rules you write. Large free plans, queries and shared data.
- jstash Apps: each signed-in person’s own JSON documents, checked against the login you already use (Clerk, Supabase Auth, Firebase Authentication or Google). No server and no rules, but small: Free is 1 app and 100 people, there are no queries or shared data, and every request needs a signed-in person.
How to save user data in a static website compares these in detail, with code for each.
More guides
- How to save user data in a static website
- How to add per-user cloud storage to a Clerk app
- How to save JSON from a frontend without exposing an API key
- How to add a database to a static website — and when you don’t need one