Stack Guide

How to add realtime features without regretting it

Realtime is the layer most likely to be over-engineered. A live dashboard, a notification badge, and a collaborative document have almost nothing in common technically, and only one of them needs a stateful socket server. This guide sorts the options into four tiers by cost and complexity, names a default for each, and starts with the tier everyone skips.

About 6 min read. Recommendations verified August 2026.

The recommended realtime stack

This is the stack for the case where you have decided you genuinely need persistent connections and want to own the server logic. Everything here runs on Cloudflare's edge, where a single stateful object per room is the whole architecture.

Layer Pick Why
Transport WebSockets Bidirectional and universally supported. Use Server-Sent Events instead when only the server ever talks, which is most notification and streaming cases.
Server Durable Objects One single-threaded object per room, with storage attached and no race conditions to reason about. Runs near the users of that room, not in one region.
Idle cost Hibernation API Connections stay open while the object is evicted from memory, so an idle room with 200 sockets costs nothing until a message arrives.
Client PartySocket A thin WebSocket wrapper with reconnection, backoff, and buffering already handled. Writing your own reconnect logic is a classic three-day detour.
Room state DO SQLite storage Strongly consistent storage colocated with the object. Ideal for the last N messages, cursor positions, and anything the room needs to rehydrate after eviction.
System of record Postgres The socket layer is a transport, not a database. Durable state belongs in your primary database, written through your normal API.
Presence In-memory per room Who is here and where their cursor is should be ephemeral. Never persist it - stale presence is worse than no presence.
Collaborative text Yjs The mature CRDT for shared editors, with bindings for ProseMirror, CodeMirror, TipTap, and Monaco. Automerge is the alternative with a stronger document-history story.
Auth Short-lived token Your API issues a signed token scoped to one room; the socket server verifies it on connect. Do not trust anything the client sends after that.

The verdict

Poll until polling hurts. Then take the hosted option that matches your database. Build on Durable Objects when the server has to be authoritative, and reach for a sync engine only when offline support is a real requirement rather than a nice idea.

The four tiers, cheapest first

Tier 1: polling is enough, and it usually is. A fetch every five to thirty seconds, with an ETag so unchanged responses cost almost nothing, covers notification counts, job status, dashboards, inbox badges, and most "live" numbers on a marketing page. It works through every proxy and corporate firewall, needs no new infrastructure, survives a deploy without dropping anything, and is trivially cacheable. Move up a tier when the interval you need drops below about two seconds, or when the payload is too large to re-send.

Tier 2: hosted realtime. Someone else runs the socket fleet and you subscribe to changes. This is the right answer for chat, live lists, notifications, and dashboards in a product that already has a database. It costs money per connection or per message and gives you very little to operate.

Tier 3: your own WebSocket server. Justified when the server must be authoritative - a multiplayer game loop, a rate-limited AI agent stream, an auction, anything where the client cannot be trusted with the rules. Durable Objects and PartyKit make this dramatically less work than it was, because each room is one object with its own storage instead of a cluster plus Redis plus sticky sessions.

Tier 4: a sync engine. The client keeps a local copy of the data and the engine reconciles it with the server. This is the only tier that makes offline work properly, and it is the one that changes your application architecture, because reads stop being requests. Do not adopt it for latency alone.

Hosted options and sync engines worth knowing

Match the tool to the database you already have. That single constraint eliminates most of this list for you.

Supabase Realtime

Tier 2 - already on Postgres

Streams Postgres changes, plus broadcast channels and presence, with row-level security applied to subscriptions. The obvious pick if Supabase is already your backend. Watch the concurrent peak connection quota - the free plan allows only a few hundred, and large numbers need a paid plan or a custom limit.

Convex

Tier 2 and 4 - greenfield

A reactive backend where you write TypeScript functions instead of queries, and every subscribed client updates automatically when the underlying data changes. There is no sync code to write at all. The tradeoff is that Convex replaces your database and your API, so it is a foundation decision, not a feature you bolt on.

Liveblocks

Tier 2 - collaboration features

Rooms, presence, cursors, comments, and notifications as a managed product with React hooks. If the requirement is literally "make it feel like Figma", this is months of work you do not do. Billing is per monthly active user, which gets expensive in a free consumer product and is fine in B2B.

PartyKit

Tier 3 - stateful edge rooms

The friendly framing of Durable Objects: each party is a room with its own server class, storage, and connection list, deployed to Cloudflare's edge. Now part of Cloudflare, and the fastest way to get a custom authoritative socket server running without touching a load balancer.

Electric

Tier 4 - read-path sync

Syncs filtered subsets of Postgres tables, called shapes, into the client in real time. Writes still go through your existing API, which keeps your server-side validation and business logic exactly where it is. That deliberate limitation makes it the least invasive sync engine to adopt.

PowerSync

Tier 4 - offline-first apps

Full bidirectional sync between Postgres, MySQL, or MongoDB and a client-side SQLite database, with a persistent upload queue for writes made while offline. This is the one to use when the app must work on a plane or a warehouse floor, and it pairs naturally with a mobile app stack.

The parts that bite in production

  • Reconnection is the feature. Phones sleep, laptops close, and networks flap constantly. Exponential backoff with jitter, a resume token so the client can ask for what it missed, and a visible connection state in the UI. Assume every connection dies within the hour, because it does.
  • Every deploy disconnects everyone. A rolling deploy drops all sockets at once and they all retry at the same moment. Jittered backoff is what stops that from being a self-inflicted denial of service.
  • Authenticate on connect, authorize per message. A socket that was valid when it opened may not be valid twenty minutes later. Re-check permissions on writes and expire tokens on the server, not just in the client.
  • Ordering is not free. Two clients editing the same field need a rule: last write wins, server-authoritative, or a CRDT. Pick it deliberately, because the default is whatever arrived last and that is rarely what the user meant.
  • Throttle before you broadcast. Cursor and typing events at 60 per second times 50 users is 3,000 messages a second in one room. Batch on a frame interval and coalesce, or your bill and your client both suffer.
  • Keep a polling fallback. Some corporate proxies still break long-lived connections. A degraded polling mode beats a permanently spinning UI for the users behind them.

Realtime questions, answered

Do I actually need WebSockets, or is polling enough?

Polling is enough far more often than people admit. If a five to thirty second refresh would satisfy the user, poll with an ETag so unchanged responses are nearly free, and skip an entire piece of infrastructure. Move to persistent connections when you need sub-second updates, when the payload is too big to re-send repeatedly, or when clients need to push to each other rather than just read.

WebSockets or Server-Sent Events?

Use Server-Sent Events when data only flows from server to client - notifications, live logs, progress, streaming AI responses. It is plain HTTP, it reconnects automatically, and it works through proxies that mangle upgrades. Use WebSockets when the client also sends frequently: chat, cursors, multiplayer input, collaborative editing. Sending an occasional client message over a normal POST alongside SSE is a perfectly good hybrid.

What is a sync engine and do I need one?

A sync engine keeps a local copy of your data on the client and reconciles it with the server, so reads become local lookups instead of network requests. Electric syncs filtered shapes out of Postgres and leaves writes to your existing API; PowerSync does full two-way sync with a client-side SQLite database and an offline write queue. Adopt one when working offline is a genuine requirement, not merely to make an online app feel faster.

How many concurrent connections can I actually handle?

More than you think, and the limit is usually commercial rather than technical. A single modest server handles tens of thousands of idle sockets; what breaks is message fan-out, not connection count. Hosted platforms cap you by plan - Supabase's free tier allows only a few hundred concurrent connections - while Durable Objects scale by adding rooms rather than servers. Size your plan on peak concurrency and messages per second, not on registered users.

Where to go next

Realtime sits on top of a database and a host, so settle those first in databases and hosting, and weigh the edge platform question in Vercel vs Cloudflare. If the hosted route appeals, Supabase vs Firebase covers the two biggest managed backends, and the SaaS stack guide shows where a realtime layer fits into a full product.