The Complete Guide to Real-time Multiplayer Games on Serverless
How to build multiplayer browser games using serverless architecture. Covers game state sync, player presence, turn-based and real-time patterns with practical code examples.
Building multiplayer games traditionally means running dedicated game servers 24/7, managing socket connections, and handling all the complexity of real-time synchronization.
But what if you could build multiplayer games using the same serverless architecture you use for your web apps? No servers to manage, pay only for what you use, and scale automatically from 2 players to 2,000.
This guide covers everything you need to know about building multiplayer games on serverless platforms like Vercel and Cloudflare Workers.
The Architecture
Multiplayer games on serverless use a different architecture than traditional game servers — for a deeper look at why, see why serverless can't hold WebSocket connections. The key insight: separate the WebSocket connections from the game logic, using a serverless WebSocket service to handle the persistent connections.
- PushFlo (or similar) handles the WebSocket connections
- Your serverless functions handle game logic
- A database (Redis, DynamoDB, Supabase) stores game state
What Types of Games Work on Serverless?
Great Fit
- Turn-based games — Chess, card games, word games
- Casual multiplayer — Trivia, drawing games, party games
- Slow-paced real-time — Tower defense, idle games, tycoons
- Social games — Chat-based games, werewolf, mafia
Challenging but Possible
- Fast-paced real-time — Shooters, racing (requires careful optimization)
- Physics-based — Depends on complexity and update rate
Not Ideal
- Twitch shooters — Sub-16ms requirements are hard to meet
- Large-scale MMOs — Sheer volume of state updates
This guide focuses on turn-based and casual real-time games, which cover 90% of indie multiplayer projects.
Building a Multiplayer Game: Step by Step
Let's build a real-time multiplayer Tic-Tac-Toe game. Simple enough to understand, but the patterns apply to any game.
Step 1: Define the Game State
// types/game.ts
interface GameState {
id: string;
board: (string | null)[]; // 9 cells: 'X', 'O', or null
players: {
X: string; // player ID
O: string | null; // null if waiting for opponent
};
currentTurn: 'X' | 'O';
status: 'waiting' | 'playing' | 'finished';
winner: 'X' | 'O' | 'draw' | null;
createdAt: string;
updatedAt: string;
}
Step 2: Create the Game Logic (Pure Functions)
// lib/game-logic.ts
export function createGame(playerId: string): GameState {
return {
id: generateId(),
board: Array(9).fill(null),
players: { X: playerId, O: null },
currentTurn: 'X',
status: 'waiting',
winner: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
}
export function makeMove(
game: GameState,
playerId: string,
position: number
): GameState {
// Validate and apply move...
// Check for winner...
return updatedGame;
}
Step 3: Real-time Updates with PushFlo
// lib/realtime.ts
export async function publishGameUpdate(game: GameState) {
await fetch('https://api.pushflo.dev/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.PUSHFLO_SECRET_KEY}`,
},
body: JSON.stringify({
channel: `game-${game.id}`,
event: 'update',
data: game,
}),
});
}
Step 4: Client-Side Game Component
// components/TicTacToe.tsx
'use client';
import { useState, useEffect } from 'react';
import { PushFloClient } from '@pushflodev/sdk';
export function TicTacToe({ gameId, playerId }: Props) {
const [game, setGame] = useState<GameState | null>(null);
useEffect(() => {
// Fetch initial state
fetch(`/api/games/${gameId}`)
.then(res => res.json())
.then(setGame);
// Subscribe to updates
const client = new PushFloClient({
publishKey: process.env.NEXT_PUBLIC_PUSHFLO_PUBLISH_KEY!,
});
client.subscribe(`game-${gameId}`, {
onMessage: (updatedGame: GameState) => {
setGame(updatedGame);
},
});
client.connect();
return () => client.disconnect();
}, [gameId]);
const makeMove = async (position: number) => {
// Send move to server
await fetch(`/api/games/${gameId}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ playerId, position }),
});
};
// Render game board...
}
Advanced Patterns
Pattern 1: Optimistic Updates with Reconciliation
For responsive gameplay, update the UI immediately and reconcile with server state.
Pattern 2: Player Presence
Show who's in the game room by publishing join/leave events when players connect and disconnect. PushFlo doesn't have managed presence channels (yet), so keep the authoritative player list in your database and broadcast changes.
Pattern 3: Game Timers
For timed turns, use server-authoritative timers.
Pattern 4: Matchmaking Queue
For games with matchmaking, implement a queue system.
Database Options
| Database | Best For | Latency | Cost |
|---|---|---|---|
| Vercel KV (Redis) | Fast reads/writes | <10ms | $ |
| Supabase | Postgres + Realtime | <50ms | $ |
| PlanetScale | MySQL, scaling | <50ms | $$ |
| DynamoDB | AWS ecosystem | <20ms | $$ |
Conclusion
Building multiplayer games on serverless is absolutely viable for:
- Turn-based games
- Casual real-time games
- Social/party games
- Games with <100ms latency requirements
The architecture is simpler than traditional game servers:
- No servers to manage
- No scaling to worry about
- Pay only for actual usage
- Deploy alongside your web app
The key is separating concerns: let PushFlo handle WebSocket connections, your serverless functions handle game logic, and your database handle state.
Ready to build your multiplayer game? Start with PushFlo free — perfect for game prototypes and indie projects.
Build your multiplayer game today
PushFlo is perfect for game prototypes and indie projects — start free.
Written by Marek
Indie developer building PushFlo — real-time messaging for serverless apps. Every post comes from running the thing in production. More about PushFlo →
Related Articles
Run Multiple Apps on One PushFlo Account with Projects
A step-by-step guide to splitting two apps into isolated PushFlo projects: scoped API keys, identical code, clean channel names, and per-app monitoring.
Introducing Projects: Organize Your Real-time Apps Without Changing a Line of Code
PushFlo now has projects — isolated namespaces for channels and API keys. Group apps and environments, monitor them separately, and switch by swapping one API key.
Do You Really Need Millions of Messages? The Real-time Pricing Trap
Why most real-time services sell you quotas you'll never use. A honest look at what indie developers actually need from WebSocket infrastructure.
