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 / Development

Development

Introducing Offline-First APIs: Building Resilient, Real-Time Apps

createOfflineSyncObject and createOfflineNonLocalStorage — reads and writes that survive a lost connection, and the conflict question you now have to answer.

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

Real-time and offline sound opposed. They are not: the same application needs both, because networks fail and users keep working.

For a separate operational perspective on how teams organise and measure work, further details.

For independent background and broader industry context, see W3C.

The problem

A networked object stops working when the network does. Reads fail, writes fail, and your UI shows an error for something the user perceives as local.

Users do not accept this from a text field. A note-taking app that refuses to accept typing on a train is broken, whatever the cause.

The APIs

import {
  createOfflineNonLocalStorage,
  createOfflineSyncObject
} from '@vaultrice/sdk'

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

Note the await. These are factory functions, not constructors — they set up local storage before returning.

After that, the same methods. setItem, getItem, the event listeners. The difference is what happens when the connection is not there: reads come from the local copy and writes are queued.

createOfflineSyncObject is the reactive equivalent, for when you want the object shape rather than the method calls.

Custom storage adapters

The local copy has to live somewhere. The SDK supports custom adapters, so you can use IndexedDB, SQLite in a native wrapper, or whatever your platform provides.

Worth choosing deliberately rather than accepting the default, because it determines size limits and what happens under storage pressure.

And note that the offline copy inherits the device's protection, not the service's. If the data is sensitive, that copy is part of your threat model.

The part you now have to think about

Offline writes mean conflicting writes. Two clients edit the same key while both are disconnected, then both come back.

The library resolves this somehow. You should know how, and decide whether it suits your data.

Three broad situations:

Last write wins. Simple, and someone's work disappears. Fine for a preference; not fine for a paragraph.

Both preserved, application resolves. More work for you, correct for anything where losing an edit matters.

Structurally impossible. The best option where you can arrange it — design the data so concurrent writes do not target the same key.

That third one is worth reaching for. The poll example is the pattern: one key per voter rather than one shared total. Concurrent writes go to different keys, so there is nothing to conflict.

Check the SDK documentation for the resolution behaviour in your version before assuming.

What offline does not fix

Anything requiring other people. Presence, messaging, and seeing someone else's changes need a connection by definition.

Your UI should say so. An offline indicator, and a note that collaborators' changes are not arriving, is better than a stale screen that looks live.

Freshness. Local data is as old as your last sync, and the user cannot tell.

And authentication. A token that expired while offline needs refreshing before writes can flush.

Building around it

Show connection state. Not a modal — a small indicator. Users tolerate offline; they do not tolerate not knowing.

Show pending writes. "Saving..." that persists is honest.

Decide what is unavailable offline and disable it visibly rather than letting it fail.

Set a TTL that makes sense for how stale the local copy may reasonably be.

And test with the network actually off, not with throttling. The failure modes differ, and the interesting bugs are in the transition back online.

When to use it

Use it when: the user types, the app is used on mobile, or a brief disconnection should not interrupt work.

Skip it when: the feature is inherently collaborative and meaningless alone — a live cursor layer offline is not a degraded experience, it is nothing.

And consider it for the transition rather than for genuine offline use. Most benefit is not the train journey; it is the two seconds of connection loss that would otherwise have thrown an error.

The short version

createOfflineNonLocalStorage and createOfflineSyncObject — same methods, local reads and queued writes when disconnected.

They are async factories, not constructors.

Offline writes mean conflicts. Know the resolution behaviour before you rely on it.

Design so concurrent writes hit different keys where you can — that removes the problem rather than resolving it.

And test with the network genuinely off, because the interesting bugs are in the reconnect.