Sign-ups paused

Sign-ups and billing are temporarily unavailable while we rebuild our infrastructure. The SDK and documentation remain available, and existing accounts are unaffected.

Vaultrice

Blog / Comparisons

Comparisons

What's the Difference Between localStorage and NonLocalStorage?

One is a synchronous string map scoped to an origin. The other is a networked object with push updates. Where each fits, and where swapping goes wrong.

5 min read Updated 2026-08-XX @vaultrice/sdk

The names invite the comparison, and the API was deliberately shaped to feel familiar. That familiarity is useful and it hides some genuine differences that matter when you choose.

For a separate operational perspective on how teams organise and measure work, identifying mouse jigglers.

For independent background and broader industry context, see web.dev.

Side by side

localStorage NonLocalStorage
Scope One origin, one browser Any client with the object id and credentials
Calls Synchronous Asynchronous
Values Strings only Structured values
Updates elsewhere Not visible Pushed over WebSocket
Survives device change No Yes
Works offline Always With the offline APIs
Size A few megabytes Per your plan's limits
Needs credentials No Yes
Fails when Quota exceeded, private mode Network, auth, limits

The three differences that actually change your code

It is asynchronous

// localStorage
const theme = localStorage.getItem('theme')

// NonLocalStorage
const theme = await nls.getItem('theme')

Which means it cannot go everywhere localStorage goes. Synchronous reads during render, in a constructor, or in code that cannot await are the places a mechanical swap breaks.

The usual pattern: keep a local copy for synchronous reads, and let the remote object be the source of truth that updates it.

It is networked

Every call can fail in ways localStorage cannot — offline, timeout, auth expiry, rate limit.

Handle it. Not with a blanket try/catch that swallows the error, but by deciding what your UI does when the value is not available yet.

And there is latency. Small, and not zero. For anything that must feel instant, write locally and reconcile.

Other clients change it

This is the whole point and it is also the adjustment.

localStorage changes only when your tab changes it. A NonLocalStorage object can change because someone else did something, at any moment.

nls.on('setItem', 'theme', (item) => {
  applyTheme(item.value)
})

Which means your state is no longer owned solely by your component. That is the shift from local state to shared state, and it is worth being deliberate about which of your values belong in each category.

What stays familiar

setItem and getItem, named the same, doing the recognisable thing.

A flat key-value shape within an object.

No schema to define before you can store something.

And the mental model — a named box you put things in.

When to use which

Use localStorage for: anything private to one browser on one origin, synchronous needs, values that would be meaningless elsewhere, and things you would not want leaving the device.

Use NonLocalStorage for: state shared between devices, between tabs, between domains, or between people. Anything where another client changing it should be visible here.

Use both, frequently. A local cache for immediate reads, backed by the shared object. That is the normal arrangement rather than a compromise.

The offline case

localStorage works offline by definition. A networked object does not, unless you ask for it.

import { createOfflineNonLocalStorage } from '@vaultrice/sdk'

const nls = await createOfflineNonLocalStorage(credentials, {
  id: 'my-object-id',
  ttl: 60000
})

This reads and writes locally when there is no connection and syncs when there is. Which is the closest thing to a drop-in replacement, at the cost of thinking about conflicts.

Where swapping goes wrong

Putting everything in it. Form state while typing, UI toggles, transient flags — these belong in component state or localStorage. Shared state is for things that are genuinely shared.

Assuming synchronous behaviour somewhere subtle, like an initial render path.

Ignoring the id. In localStorage there is no address to get wrong. Here the id is the address, and a guessable one is a shared one.

And storing anything sensitive without reading the security guide. There are levels above the default, including end-to-end encryption, and the default is not the strongest.

The short version

Same shape, different medium. Familiar API, networked semantics.

Three real changes: it is asynchronous, it can fail, and other people can change it.

Keep a local copy for synchronous reads and treat the remote object as the source of truth.

Not everything belongs in shared state — most UI state does not.

And the offline APIs get you closest to a drop-in replacement, if that is what you need.