Back to Blog
Blog Post

Building Real-Time Multiplayer with PartyKit WebSockets

7 min read
PartyKit
WebSockets
Next.js
TypeScript

How I built a multiplayer financial board game that syncs game state across players in real time — and what I learned about WebSocket architecture along the way.

Wealth Flow multiplayer game showing a deal card and player financials

When I set out to build Wealth Flow — a multiplayer financial education game inspired by CASHFLOW 101 — I knew the hardest part wouldn’t be the game logic. It would be keeping multiple players perfectly in sync, in real time, without the experience feeling janky.

Here’s how I approached it, what worked, and what I’d do differently.

Why PartyKit?

I evaluated several options for real-time communication: Socket.IO with a custom server, Ably, Pusher, and PartyKit. PartyKit won for a few reasons:

  • Server-side rooms are first-class. Each game session is its own “party” — an isolated server instance with its own state. No shared-nothing headaches.
  • Durable Objects under the hood. PartyKit runs on Cloudflare’s edge network using Durable Objects, which means each room has strongly consistent state without needing a database for ephemeral game data.
  • Dead simple API. The mental model is just: write a class, handle messages, broadcast to connections.
export default class GameRoom implements Party.Server {
  constructor(readonly room: Party.Room) {}

  onConnect(conn: Party.Connection) {
    // Send current game state to the new player
    conn.send(JSON.stringify({ type: "sync", state: this.gameState }));
  }

  onMessage(message: string, sender: Party.Connection) {
    const action = JSON.parse(message);
    this.gameState = applyAction(this.gameState, action);
    this.room.broadcast(JSON.stringify({ type: "update", state: this.gameState }));
  }
}

That’s the core pattern. Everything else is details.

The State Synchronization Problem

The trickiest part of any multiplayer game is deciding who owns the truth. I went with an authoritative server model:

  1. Client sends an action (roll dice, buy property, draw card)
  2. Server validates and applies the action to the canonical game state
  3. Server broadcasts the new state to all connected clients
  4. Clients render whatever the server says

This eliminates an entire class of bugs around state divergence. The tradeoff is latency — every action round-trips through the server. For a turn-based board game, this is totally acceptable. For a twitch shooter, you’d need client-side prediction.

Handling Edge Cases

Real-time multiplayer has a long tail of edge cases that don’t show up until real users are involved:

Player Disconnects

What happens when a player’s connection drops mid-turn? I implemented a reconnection window: the server keeps the player’s seat reserved for 60 seconds. If they reconnect, they get the full current state and pick up where they left off. If they don’t, the game advances past their turn.

Race Conditions

Two players clicking “ready” at the exact same moment. Two actions arriving on the server in the same tick. The solution is simple but important: process messages sequentially. PartyKit’s Durable Object model guarantees single-threaded execution per room, which eliminates most concurrency issues by design.

State Size

As games progress, the state object grows — transaction histories, property portfolios, event logs. I had to be intentional about what gets included in broadcast messages vs. what gets stored but only sent on demand.

What I’d Do Differently

Optimistic updates. Even in a turn-based game, the 50-100ms round trip is perceptible. I’d add optimistic UI updates for the acting player while waiting for server confirmation.

Delta updates. Broadcasting the full game state on every action works fine for a board game, but it’s wasteful. Sending only the diff would reduce bandwidth and make the system more scalable.

Better error boundaries. When a WebSocket message fails to parse or an action is invalid, the current error handling is functional but not graceful. I’d invest more in structured error types that the client can render meaningfully.

Key Takeaway

Real-time multiplayer sounds intimidating, but the core pattern is straightforward: centralize state, validate on the server, broadcast to clients. The complexity lives in the edge cases — disconnects, reconnects, race conditions, and state management. Pick a platform that handles the hard infrastructure (PartyKit is excellent for this), and you can focus on building the actual game.

Hire me today
KP
Kolbey's Assistant Available for work
Hey — I'm Kolbey's portfolio assistant. Ask me about his skills, projects, experience, or how to get in touch.