Mobile — Mobile App Architecture

Event-Driven Mobile Architecture with WebSockets

Direct answer

Event-driven mobile architecture means the server pushes domain events over a single multiplexed WebSocket connection, and the client treats those events as instructions to patch or invalidate its local cache — not as the source of truth itself. The parts that make it production-grade are reconnection with backoff, sequence numbers to detect gaps, a resync path for when gaps occur, and lifecycle handling so the socket behaves correctly when the app backgrounds or the network flaps.

Realtime features fail in mobile apps not because WebSockets are hard to open, but because phones live on elevators, subways, and battery savers. An event-driven architecture that survives production is mostly about what happens when the connection is not there. Here is how I structure it.

Key facts, with sources

  • React Native's New Architecture became the default in 0.76, the legacy bridge was retired in 0.82, and Hermes V1 shipped as the default JavaScript engine in 0.84. (TO THE NEW Blog)
  • Microsoft retired Visual Studio App Center and CodePush on March 31, 2025, forcing every team that depended on it to migrate their over-the-air update architecture. (microsoft/react-native-code-push GitHub issue)
  • In the State of React Native 2024 survey, Redux drew the most negative feedback at around 18% dissatisfaction, while React's built-in state management (31% positive) and Zustand (21% positive) were the best regarded. (InfoQ)
  • Over 80% of State of React Native 2024 respondents work in teams of up to five developers, meaning most mobile architectures must be maintainable by very small teams. (SSOJet (State of React Native 2024 highlights))
  • Published production examples report Shopify at 86% unified code across its apps and Instagram sharing 85 to 99% of code between iOS and Android. (CatDoes)

Events update a cache, they are not the state

The core architectural decision: WebSocket events must never be the only place data lives. The client's source of truth is its query cache, populated by normal REST fetches; events are hints that mutate or invalidate that cache. A message like order-status-changed either patches the cached order directly or marks the query stale so it refetches. Either way, a user who missed events — fresh install, long offline stretch — still sees correct data because the fetch path works without the socket.

Teams that instead accumulate state purely from an event stream on the client end up rebuilding event sourcing on a phone, with all its replay and ordering problems and none of the server-side tooling. Keep the socket as an accelerator on top of a request/response foundation, and the app degrades gracefully to slightly-stale instead of wrong.

One connection, multiplexed channels

Open exactly one WebSocket per app session and multiplex logical channels over it — chat, notifications, live order tracking — using a subscribe message with a channel name. Multiple sockets multiply reconnection logic, keepalive traffic, and battery cost, and mobile OSes are already hostile enough to background connections without you maintaining three of them.

Structure every message with an envelope: channel, event type, sequence number, and payload. A single dispatcher on the client routes envelopes to per-feature handlers, which keeps feature code decoupled from transport concerns. This is also the layer where I put schema validation — messages arrive from the network, so parse them defensively and drop malformed ones with a logged warning rather than letting one bad payload throw inside a render cycle.

Reconnection with backoff and jitter

Connections will drop constantly — cell handoffs, backgrounding, proxies timing out idle sockets. The client owns reconnection: exponential backoff with a cap, jitter so a fleet of clients does not reconnect in synchronized waves after a server restart, and a reset of the backoff counter once a connection proves stable. Listen to connectivity and app-state changes to reconnect immediately when the network returns rather than waiting out a long backoff timer.

Reconnecting WebSocket hook with capped backoff
import { useEffect, useRef } from 'react';

export function useLiveEvents(url: string, onEvent: (e: unknown) => void) {
  const attempt = useRef(0);

  useEffect(() => {
    let ws: WebSocket;
    let timer: ReturnType<typeof setTimeout>;
    let closed = false;

    const connect = () => {
      ws = new WebSocket(url);
      ws.onopen = () => {
        attempt.current = 0;
      };
      ws.onmessage = (msg) => onEvent(JSON.parse(msg.data));
      ws.onclose = () => {
        if (closed) return;
        const delay = Math.min(1000 * 2 ** attempt.current++, 30_000);
        timer = setTimeout(connect, delay + Math.random() * 500);
      };
    };
    connect();

    return () => {
      closed = true;
      clearTimeout(timer);
      ws.close();
    };
  }, [url, onEvent]);
}

Gap detection and resync

Every reconnect means possible missed events, and pretending otherwise produces the classic realtime bug: a UI that silently drifts from reality until the user force-refreshes. Sequence numbers solve detection — each channel's events are numbered, the client remembers the last one processed, and a jump in the sequence means a gap.

Recovery has two tiers. If the server keeps a short replay buffer, the client resubscribes with its last sequence number and receives the missed events. If the gap exceeds the buffer, or after long offline periods, fall back to a full resync: refetch the affected queries over REST and resume the stream from the current position. Design the resync path first, because it doubles as your cold-start behavior. The event replay path is an optimization on top, not the foundation.

Mobile lifecycle and battery reality

A phone is not a browser tab. When the app backgrounds, the OS will suspend your JavaScript and kill the socket within seconds to minutes, and fighting that wastes battery and reviewer goodwill. My policy: disconnect deliberately on background, reconnect and resync on foreground, and let push notifications cover anything urgent enough to reach a backgrounded user — the notification tap becomes an entry point that triggers a targeted refetch.

In the foreground, keep the protocol quiet. Heartbeats only at the interval needed to keep intermediaries from closing idle connections, server-side batching for chatty channels, and coalescing of rapid-fire events into single cache updates. I profile radio wakeups on realtime features before shipping; a chatty socket is invisible in development and very visible in battery reviews.

When to hire senior help

Architecture is the cheapest place to buy senior expertise, because decisions about state management, navigation, offline strategy, and update infrastructure made in week one determine costs for years. A short engagement with a senior mobile architect before or during MVP planning routinely prevents the rewrite-at-scale scenario that hits teams around their first major growth phase. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Adopting Redux with sagas and heavy boilerplate for a five-screen MVP when built-in React state or Zustand would cover the actual requirements
  • Scattering business logic inside UI components instead of isolating a data layer, making later backend changes or native module swaps expensive
  • Building deployment architecture on a hosted OTA service with no exit plan, a risk the March 2025 CodePush shutdown made concrete for thousands of teams
  • Assuming permanent connectivity and bolting on caching later, instead of designing offline storage and sync conflict resolution before the data layer hardens

Frequently asked questions

Should my React Native app keep the WebSocket open in the background?

No. Both mobile platforms suspend background JavaScript and tear down sockets quickly, so background connections are unreliable by design. Disconnect cleanly when the app backgrounds, reconnect and resync when it returns, and use push notifications for anything that must reach a backgrounded user. This is more reliable and dramatically better for battery than fighting the OS.

How do I stop a realtime app from showing stale data after reconnecting?

Number every event per channel and track the last sequence the client processed. On reconnect, compare sequences: a gap means missed events. Recover by replaying from a server-side buffer when the gap is small, or by refetching the affected data over REST when it is not. Never resume the stream silently and assume nothing happened during the disconnect.

WebSockets or server-sent events for a mobile app?

If the client only receives updates and never sends messages over the stream, server-sent events are simpler to operate and proxy-friendly. WebSockets earn their complexity when communication is genuinely bidirectional — chat, collaborative editing, live presence. Either way, the hard architectural work is identical: reconnection, gap detection, resync, and treating the stream as cache updates over a fetch-based foundation.

What state management should a new mobile app use?

For most apps, React's built-in state plus a light library like Zustand is enough; in the State of React Native 2024 survey those two drew the most positive sentiment while Redux drew the most negative at about 18% dissatisfaction. Heavier tooling is justified mainly by large teams, complex shared state, or strict audit requirements.

Do we need offline support from day one?

If users operate in the field, in transit, or in markets with unreliable networks, yes, because retrofitting offline-first sync onto an online-only data layer is one of the most expensive refactors in mobile. If the app is unusable without live data anyway, graceful error and retry handling may be sufficient.

What are over-the-air updates and should our app use them?

OTA updates push JavaScript-level fixes directly to users without waiting for app store review, which is valuable for hotfixes. Microsoft's CodePush was retired on March 31, 2025, so current options are EAS Updates, a self-hosted CodePush server, or third-party services, and updates must stay within store policies that prohibit changing an app's core purpose.

Bottom line: Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership