Devtools & Traffic
Observing a realm: the browser realm panel, what its decoded traffic means versus the true wire cost, and the egress-message count that maps to your Durable Object bill.
A realm exposes two things worth watching in dev — its projected state + sync health, and its traffic (the number that becomes your Durable Object bill). This page is the realm-specific wiring for both. The window that renders them, the server host, the topology band, and the relay are the same across every plane and live in the Devtools section — this page links there rather than repeating it.
The realm panel
Section titled “The realm panel”createNiceDevtools() builds the shared devtools cores and streams them; instrument each connectRealm
with the tap it hands back. The panel that renders it lives in the devtools window,
never in your page.
import { createNiceDevtools } from "@nice-code/devtools";
export const devtools = createNiceDevtools({ topologyId: "my-app", runtime });const realm = connectRealm(gameRealm, { avatar: { type: "player" }, connection: realmConnection(connector), devtools: devtools.realm("game"), // ← the observation seam (one null-check when omitted)});The panel shows one tab per instrumented realm (status dot, confirmed version, pending-write badge) with three views:
- View — the live projected store, plus status / version / pending / avatar identity.
- Timeline — every mutation with its outcome (
created → confirmed / rejected, with reject ids and rolled-back paths), every sync-health diagnostic (resync,probe_escalated,link_redialed,reown_missing, …), and status flips. - Traffic — decoded realm frames per kind (
hydrate,patches,write,ack,batch, …), counts + bytes over rolling windows.
Construct the underlying RealmDevtoolsCore (@nice-code/realm/devtools/browser) yourself only if you
aren’t using createNiceDevtools. subscribeDiagnostics(listener) on the handle is the post-hoc,
multi-listener complement of the onDiagnostic connect option — handy for tests and ad-hoc telemetry
(and one subscriber satisfies the unwired-diagnostics console floor).
Decoded payload vs. true wire cost
Section titled “Decoded payload vs. true wire cost”The realm panel’s Traffic tab shows decoded realm payloads. The true wire cost — encrypted
frames, envelopes, handshakes, keepalives — is measured one layer down, at the carrier, with wireTap:
const connector = connectChannel(runtime, channel, { // …transports, storage… wireTap: devtools.wireTap, // ← every carrier frame, true post-encryption byte sizes});devtools.wireTap is one traffic core for the whole app, already streamed to the window (its per-lane,
per-client breakdown, sparklines, and largest-frame list are documented on
Devtools & Traffic in the window). The realm-specific reading:
egressMessagesis your billing unit. Each outgoing WebSocket message is a Cloudflare request-count unit; the byte totals are your bandwidth.- The
realmlane vs. the panel’s decoded totals is the transport overhead (encryption + framing + batch envelope) per payload byte. egressCoalesceRatio(coalesce ×N) is “how many realm frames rode each billable message” — the number to watch when tuningbroadcastCoalesceMsand confirming the batch envelope is working.
Reading the engine directly
Section titled “Reading the engine directly”With no devtools wired at all, the realm engine exposes its own counters — scrape them from a dev-gated route:
if (url.pathname === "/debug/traffic" && env.STAGE !== "production") { return Response.json(this.engine.getTrafficSnapshot()); // { ingressFrames, ingressBytes, egressMessages ← billing unit, egressFrames ← pre-coalescing, // egressBytes, egressCoalesceRatio ← the batch-envelope win, connections: [...] }}To contribute that same data to a server devtools host (console summaries or a live window feed),
the realm scopes are realmEngineScope(engine, "demo_board") (traffic totals) and
serverWireTrafficScope(engine) (per-frame samples). The host itself — its options, sinks, and how it
streams to a window alongside your action domains — is documented in
Observing Backends:
import { createServerDevtoolsHost } from "@nice-code/devtools-core";import { realmEngineScope, serverWireTrafficScope } from "@nice-code/realm/devtools/server";
createServerDevtoolsHost({ name: "match-api" }) .contribute(realmEngineScope(engine, "demo_board")) .contributeSample(serverWireTrafficScope(engine)) .start();Inside a Durable Object
Section titled “Inside a Durable Object”A DO can’t dial out to a devtools window (an open outbound socket pins it awake) and can’t run the
console sink’s timer across hibernation — so the window dials in instead, riding the DO’s ordinary
WebSocket as a token-gated protocol via createNiceDurableObjectDevtools. The full setup, safety model,
and the honest hibernation caveat are in Observing Backends → a Cloudflare Durable Object.