Cloud|

Realtime UI

Update an open page from application events while preserving reload and recovery behavior.

2 min read Updated 2026-08-22 #realtime#websocket#cursors

Realtime updates enhance a server-rendered page. They do not replace its reload path.

Start with an authorized snapshot. Subscribe from that snapshot's cursor. Cover each event with an authoritative state update, then advance the cursor.

Connect a live WebSocket

tsx
import { createLiveWebSocket } from "@k2b/cloud/browser/live";
import { onCleanup, onMount } from "solid-js";

const live = createLiveWebSocket<InventoryEvent>({
  url: "/api/inventory/ws",
  initialCursor: props.cursor,
  subscribe: (cursor) => ({
    type: "subscribe",
    payload: { itemId: props.itemId, fromCursor: cursor },
  }),
  parse: (raw) => InventoryEventSchema.parse(JSON.parse(raw)),
  onMessage: (event, controls) => {
    void inventory.invalidate({ cursor: event.cursor })
      .then(() => controls.markApplied(event.cursor))
      .catch(() => {
        // Reconnect replays from the last applied cursor.
      });
  },
  onFatal: (error) => setLiveError(error.message),
});

onMount(() => live.connect());
onCleanup(() => live.dispose());

The helper owns one socket, visibility-aware activity, reconnect backoff, cursor resume, fatal close classification, and disposal.

The application owns authentication, subscription payloads, runtime validation, permissions, and domain updates.

When one application has two current realtime concerns, keep one physical socket and use typed logical channels. onOpen can send the additional current subscription through controls.send(), and the returned connection exposes the same send() operation for later subscribe or unsubscribe messages. Keep each channel's recovery state independent: a durable invalidation cursor must not be advanced by unrelated ephemeral stream events.

Advance only after coverage

For a server-backed snapshot, call markApplied() only after the matching query invalidation has committed a covering snapshot. If one event affects several queries, wait for all matching invalidations.

Apply an event directly only when it contains the complete authoritative projection. If apply or invalidation fails, do not advance. A reconnect can replay the event from the last known good cursor.

When the server reports cursor overflow or the local state cannot reconcile, reload the authorized snapshot.

See Server-backed state for the query invalidation contract.

Handle access changes

The WebSocket route must authorize the subscription and every resource it streams.

Close code 1008 is terminal by default and surfaces an access error. Do not keep reconnecting after permission is lost.

Close codes 1011 and 1013 are also terminal by default. Return null from a custom classifyClose handler only when the application can safely reconnect.

Preserve reload behavior

The URL must still identify the visible resource and view. A reload asks the server for a fresh authorized result.

Do not keep the only copy of edits or selected resources in the socket client.

For server event semantics, see Topics and live events.

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.