Every real-time UI I have shipped looked perfect in the demo. The price ticked up, the balance moved the second a transaction confirmed, the chart slid along like it was breathing. Then it met a real network. A laptop waking from sleep, hotel wifi, a phone switching from cell to wifi mid-swap. That is when the illusion broke, and it broke in a way the demo never showed me.
I learned this building live trading surfaces. A token swap and a cross-chain bridge while I was at Shiba Inu, where the number on screen was a balance someone was about to move real money against. When a websocket drops and reconnects, the worst outcome is not a blank screen. It is a screen that looks fine and is quietly wrong.
What a dropped socket actually does to your UI
The connection rarely dies politely. Most of the failure modes are silent, which is exactly why they bite in production and not in the demo.
- It dies without telling you. A TCP socket can sit half open for a long time. Your code still thinks it is connected, no messages arrive, and the UI freezes on the last value it ever saw. No error, no spinner, just a stale number sitting there looking confident.
- The events you missed are gone. While you were offline the server kept emitting. A price moved, an order filled, a balance changed. You reconnect, new events start flowing, and the gap in the middle is invisible. Your state is now built on a version of the world that skipped a chapter.
- Everyone reconnects at once. If the server restarts, every client tries to reconnect in the same second. That thundering herd can knock the server straight back over.
None of these show up when you test against localhost with the tab in focus. They show up three weeks after launch when a user emails you a screenshot of a balance that has not been true for two minutes.
Reconnecting is the easy twenty percent
Most tutorials stop at the reconnect loop, and the reconnect loop is the part that is basically solved. Use exponential backoff with jitter so clients do not all retry on the same tick. Start around a second, double each attempt, cap it near thirty seconds. Check your auth token before you reconnect, because firing a connection with an expired token just burns a retry and slows recovery. A dozen libraries do all of this for you.
The hard part is what happens in the half second after you are connected again. You have a fresh, healthy socket and a UI full of data you can no longer trust. Getting the bytes flowing again is not the same as being correct.
Resync, do not just resume
The rule I follow now is simple. On every reconnect, assume your local state is stale and rebuild it. There are two patterns I trust, and which one you want depends on how big your state is.
- Sequence numbers. Every message carries a number that only goes up. The client remembers the last one it processed. On reconnect it tells the server that number and the server replays everything after it. This is precise and cheap on bandwidth. The catch is the server has to buffer recent messages, and if a client was gone longer than the buffer, you need a fallback.
- Full snapshot. The server sends the current state on reconnect and the client throws away what it had and replaces it. Wasteful if the state is large, but it is dead simple and it is always correct.
For anything tied to money I default to the snapshot. A balance or a quote is small, and correctness beats elegance every time. For a high volume feed where a full snapshot would be huge, I use sequence replay with a snapshot as the fallback when the buffer has expired. The thing I never do is let new events paper over the gap and hope nobody notices.
Tell the user when the data is stale
This is the part almost every UI skips, and it is the part that actually protects users. A live number with no freshness signal is a lie waiting to happen. The fix is a heartbeat. If no message arrives within a few seconds, mark the data stale before the user acts on it.
When state goes stale I dim the number, show a small reconnecting indicator, and disable the action that depends on it. You should not let someone sign a swap against a quote that is forty seconds old. That is the same class of bug I wrote about in the token swap frontend pitfalls that cost real money, and it is closely related to being honest about cost in a gas-aware frontend. Showing uncertainty feels worse in a design review and saves you in production.
How I wire it now
One connection manager owns the socket. It holds a small state machine with connected, reconnecting, and stale states, runs the backoff, kicks off a resync on every reconnect, and exposes a freshness timestamp the UI can read. Components subscribe to data and read that timestamp. They never touch the socket directly, so the messy reconnection logic lives in exactly one place instead of leaking into every widget.
That separation is most of the battle. The transport is a raw pipe with no memory of what you missed, so the honesty has to be built on top of it by you. Treat the socket as something that will drop, design the moment it comes back, and your real-time UI stops lying the first time the network does something rude. If you want more on the frontend side of this, I keep related notes in my full-stack writing, and the rest is on the homepage.
Building something where this matters?
I am open to senior full-stack, Web3, or AI engineering roles, fully remote and any timezone. If the live data layer or the hard part of your product is fighting you, that is the work I like.
Get in touch →