CDN · SQLite WASM
Durable Objects in your browser
Prevent race conditions between browser tabs. Give each cart, draft, or local job one actor that updates its state one call at a time. State survives reloads and stays coordinated across tabs sharing the same local database.
Read the five-minute guideWhy this library exists
Race conditions in browser JavaScript can appear when tabs share state or repeat the same work. Bugcrowd’s session-polling example combines leader election, Web Locks, and BroadcastChannel with React effects, polling flags, and listener cleanup to coordinate its tabs.
Opening a second tab shouldn’t mean building a coordination protocol. Solid Objects gives shared local state one actor, with ordered operations and persistence across reloads. Your components call it; the library coordinates access across tabs on the same origin using the same database. Server-side coordination is still needed across devices.
How this compares
The hard part in a tab is not storage. The hard part is the second tab, and the reload in the middle of a write. This table does not rank the projects.
| Solid Objects A runtime in the tab | Cloudflare Durable Objects A managed platform | celld A daemon on your VMs | SQL transaction In the local database | |
|---|---|---|---|---|
| Runs in the tab with no server | Yes SQLite WASM and OPFS | No | No | Yes |
| State survives a reload | Yes OPFS | Yes On the server | Yes On the server | Yes In the local database |
| One call at a time for each identity | Yes | Yes | Yes | No Only inside one transaction |
| Safe when a second tab opens | Yes Web Locks elect one holder | Yes One object on the server | Yes One object on the server | No Two tabs can both commit |
| Works offline | Yes | No | No | Yes |
| Queued writes replay to a server | Yes transmit, with idempotent ingest | No | No | No |
| Timers that survive a reload | Yes Reminders are rows | Yes Alarms | Yes Alarms | No |
| The same actor code runs on the server | Yes Node and Rails | Yes Workers | Yes Workers | No |
| Runs with no vendor account | Yes | No | Yes | Yes |
The browser runtime keeps the last write local, so it works offline.
solid-objects/transmit replays queued writes to a Node or Rails server with
at-least-once delivery and idempotent ingest. It does not merge two concurrent edits to
one field the way a CRDT does. Calls to one actor serialize instead.
For collaborative text, use a CRDT. The full table, with a primary source
for every row, is in
docs/comparisons.md.
Practical answers
Race conditions in browser JavaScript: frequently asked questions
Lost updates, duplicate work, and recovery: what to use, what to keep, and where Solid Objects fits.
How do I prevent race conditions between browser tabs?
Give shared local state one coordinated owner instead of letting each tab maintain
and overwrite its own copy. Solid Objects runs actors in browser module workers with
durable state in SQLite WASM and OPFS. With sharedSqliteWasm, tabs on
the same origin using the same database coordinate through Web Locks and
BroadcastChannel. Calls to one actor identity are ordered, so tabs can update a
shared cart, draft, or local job without implementing their own ownership protocol.
Why does localStorage still lose updates between tabs?
A read-modify-write sequence spans multiple operations: two tabs can read the same value, calculate different updates, and then overwrite each other. A storage event can report a change but does not make that whole sequence atomic. Solid Objects puts the read, decision, and durable state change inside one actor operation. Call that operation from each tab rather than saving competing copies of the actor’s state. For a small isolated change, a native lock or a suitable database transaction may be enough.
Why not just use navigator.locks or BroadcastChannel?
navigator.locks coordinates a critical section, while BroadcastChannel
carries messages between browser contexts. Neither by itself supplies durable actor
state, a persistent mailbox, or restart-safe reminders. Use the native APIs directly
for a small coordination task. Solid Objects builds on them when you also need
persistent per-resource workflows and recovery, keeping state transitions and
scheduled work on one actor. See the
Web Locks API documentation
for the native primitive.
Can I prevent two tabs from starting the same local job?
Have both tabs call an operation on the same job actor. The operation checks durable state before starting or staging work, so a later call can see that the job is already underway or finished. A shared actor gives the decision one home instead of scattering busy flags across tabs. Retries and external effects can still repeat; use a stable job identifier and idempotent external actions. A disabled button alone cannot coordinate another tab.
Do I need Solid Objects for stale fetch responses in React?
Usually not for an isolated component request. Cleanup, cancellation, or checking which request is current can keep an older response from replacing newer UI state. Solid Objects becomes useful when several components or tabs share a durable workflow, such as a draft with queued operations or a local job that must resume after reload. It coordinates the shared state; it does not automatically make every component’s network response or rendering order correct.
Does actor state survive a page reload or browser restart?
Committed state lives in OPFS rather than only in a tab’s JavaScript memory, so a new runtime can reopen the same local database and continue. This is subject to browser storage availability: users can clear site data, storage can be evicted, and private browsing may have different persistence rules. Important data still needs an appropriate backup or server synchronization strategy. The browser quickstart demonstrates reopening a database with a durable ticket hold.
Do reminders run after every tab has closed?
No. A stored reminder survives closing the runtime, but browser JavaScript cannot promise to keep executing after all relevant tabs and workers stop. Due work can resume when the application opens the same database and starts the runtime again. Background tabs can also be throttled or suspended. Use a server runtime for deadlines that must be processed while the browser is closed; a durable reminder preserves intent, not a guarantee of exact wall-clock execution.
Can I coordinate tabs without a backend, daemon, or cloud subscription?
Yes, for state local to that browser’s shared origin and storage context. The MIT-licensed Solid Objects browser runtime uses a module worker, SQLite WASM, OPFS, Web Locks, and BroadcastChannel. It does not require Redis, a server-side actor daemon, or a paid cloud coordination service. Serve the application in a secure context with the required browser capabilities. Cross-device coordination and an authoritative shared service still require a backend.
Can a browser actor prevent two customers buying the last item?
Not by itself. Different customers and devices do not share one browser database, and a client is not a trusted authority for stock or payments. Put the authoritative reservation on a server-side actor in Rails or Node, or use an appropriate database transaction and constraints. Browser actors can coordinate local state and pending work; Solid Objects transmission can durably deliver writes to an authorized server runtime. Server-side validation still decides whether a purchase is allowed.
When should I choose Solid Objects for browser concurrency?
Choose it when a local cart, draft, room, or job needs shared state across tabs, ordered operations, and persistence across reloads. A simple Web Lock is enough when all you need is a short critical section. Component state remains appropriate for temporary UI state. Solid Objects is pre-1.0, and its browser runtime depends on supported storage and worker APIs. Start with the runnable browser guide and test reloads, tab failover, and your target browsers.