Remote Function
server.remoteFunction(name, args?, options?) calls a method on your Server class from the
client. args is an array, mapped positionally onto the method’s parameters. There are three modes:
| Mode | Option | Resolves with | Rate limit | Server failure visible? | Use for |
|---|---|---|---|---|---|
| Standard | — | the method’s return value | 10 calls/s per function name | yes — the promise rejects | anything whose result or failure matters |
| Fire-and-forget | needResponse: false | null, once the call is queued | 10 calls/s per function name | no | cosmetic or continuously re-sent updates |
| Throttled | throttle: <ms> | null, immediately | none | no | high-frequency streams such as position |
The rate limit is counted on the client: the 11th call to the same function name within a rolling
second rejects with Too many calls to the function. Use throttle option. before anything is
sent. Each function name has its own budget.
Standard Calls
const { connected, server } = useGameServer();
if (!connected) return "Connecting...";
const handleCalculate = async () => {
const result = await server.remoteFunction("add", [1, 2]);
console.log(result); // 3
};Standard calls run in order. Each settles exactly once — on the server’s reply, on a disconnect, or
after a 30-second timeout. A rejection is an Error carrying two extra fields:
code— machine-readable cause, e.g.TIMEOUT,DISCONNECTED,ISOLATE_DIEDretryable— whether retrying the same call can succeed
try {
await server.remoteFunction("buyItem", ["sword"]);
} catch (e) {
if (e.retryable) retryLater();
else showError(e.message); // e.code tells you which failure it was
}Fire-and-forget Calls
With needResponse: false no reply is awaited: a failure on the server, or a socket that drops
before the queue flushes, is invisible to the caller. Never use it for anything whose loss a player
would notice.
server.remoteFunction("updateMyNickname", [{ nick: "karl" }], {
needResponse: false,
});Throttled Calls
For real-time updates where intermediate values can be skipped, pass throttle in milliseconds
(values below 30 are raised to 30). needResponse is forced to false, so a throttled call never
reports a server-side failure; it rejects only when the socket is already down, which is what the
.catch(() => {}) absorbs.
const handleMouseMove = (e) => {
server
.remoteFunction("updateMyPosition", [{ x: e.clientX, y: e.clientY }], {
throttle: 50,
})
.catch(() => {});
};Throttling drops the LAST call of a burst, not just the middle ones. The throttle runs on the leading edge only, so the first call in each window goes through and everything after it is discarded — including the final one that carries the state you actually want to land. For a drag that ends when the player lets go, the resting position is exactly what gets dropped.
Either opt into the trailing edge with throttleTrailing: true, or send one unthrottled call at
the end of the interaction. Only rely on plain throttling for values you re-send continuously
anyway, such as a per-frame position in a loop that never stops.
The throttle window is keyed by function name. To throttle several entities through one function
independently, give each its own throttleKey:
server
.remoteFunction("updateBall", [{ ballId: 1, position }], { throttle: 50, throttleKey: "1" })
.catch(() => {});
server
.remoteFunction("updateBall", [{ ballId: 2, position }], { throttle: 50, throttleKey: "2" })
.catch(() => {});Reserved Names
joinRoomandleaveRoomnever reach the server:remoteFunction()rejects them before sending, with<name>() is no longer available via remoteFunction(). Use server.<name>() directly.A server function with either name is registered but unreachable, so call a matchmakerfindMatchorcreateRoom, and enter the room withjoinRoom(roomId)fromuseGameServer().- Names beginning
$on,$roomTickor$roomare refused by the server withReserved function: <name>— and withneedResponse: falsethe call is dropped without any reply, so it simply vanishes. Keep your own function names clear of these prefixes.
Connection Status
connected is a boolean: the client either reached the server or it did not. if (!connected) return "Connecting..." is fine for a prototype, but in a shipped game a player staring at “Connecting…” cannot tell a cold-starting server from a full region from a game that is simply broken. useGameServer() also returns a reactive connectionStatus — { phase, reason?, retryAfterMs? } — that says which it is.
phase | Meaning |
|---|---|
idle | Not connected and not trying — initial, or after disconnect() |
connecting | First attempt; never connected in this session |
connected | Live |
reconnecting | Was connected, dropped, retrying |
unavailable | Gave up — a terminal failure, or the retry deadline passed |
reason is a machine-readable cause. Common values while waiting are 'not_ready' (the server is starting up), 'at_capacity' (the region is busy), 'draining' (the server instance you were routed to is shutting down — reconnecting picks a fresh one) and 'reserving' (another allocation holds the reservation); 'rate_limited' also shows up here, when the verse is at its connection quota. Terminal reasons such as 'recovering' — the verse is in a broken state after a crash and the SDK has given up — arrive with phase === 'unavailable' instead of while connecting. After a socket close, reason carries the close reason string instead. retryAfterMs is the server’s suggested wait before the next attempt — use it for a countdown.
You do not have to drive reconnection yourself: the SDK retries automatically and honors the server’s suggested timing. The only action connectionStatus asks of you is a retry button on unavailable — and that button must call disconnect() before connect(), because a terminal close (duplicate login, verse stopped, admin kick, auth failure) latches the connection shut and a bare connect() only logs a warning and returns.
const { connectionStatus: cs, connect, disconnect } = useGameServer();
if (cs.phase === "connected") return <Game />;
if (cs.phase === "unavailable") {
const retry = () => { disconnect(); connect(); };
return <button onClick={retry}>Unavailable — Retry</button>;
}
// connecting / reconnecting
return (
<div>
{cs.reason === "not_ready"
? `Starting server… ${Math.ceil((cs.retryAfterMs ?? 0) / 1000)}s`
: "Connecting…"}
</div>
);For finer classification of a raw close code, the SDK also exports isTerminalCloseCode / isTransientCloseCode.