Skip to content
staticbackend.

WebSockets and live updates for a static HTML site

Run the socket on Cloudflare Durable Objects: 100,000 requests and 13,000 GB-s a day free, and 100 incoming messages bill as 5 requests. Pusher's free Sandbox stops at 100 concurrent connections.

// worker.ts: one Durable Object per chat room, with hibernation
import { DurableObject } from "cloudflare:workers";

export class Room extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    if (request.headers.get("Upgrade") !== "websocket") {
      return new Response("Expected a WebSocket upgrade", { status: 426 });
    }

    const { 0: client, 1: server } = new WebSocketPair();
    this.ctx.acceptWebSocket(server);
    return new Response(null, { status: 101, webSocket: client });
  }

  async webSocketMessage(sender: WebSocket, message: string | ArrayBuffer) {
    for (const peer of this.ctx.getWebSockets()) {
      if (peer !== sender) peer.send(message);
    }
  }
}

export default {
  async fetch(request: Request, env: { ROOM: DurableObjectNamespace }) {
    const room = new URL(request.url).searchParams.get("room") ?? "lobby";
    return env.ROOM.get(env.ROOM.idFromName(room)).fetch(request);
  },
};
// wrangler.jsonc: the Workers Free plan needs SQLite-backed classes
{
  "durable_objects": { "bindings": [{ "name": "ROOM", "class_name": "Room" }] },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Room"] }]
}
// In the static page
const socket = new WebSocket("wss://rooms.example.workers.dev/?room=lobby");
socket.onmessage = (event) => console.log(event.data);
socket.onopen = () => socket.send("hello");

acceptWebSocket is the Hibernation API: when a room goes quiet, Cloudflare evicts the object from memory while the client connections stay open, and wakes it on the next message. Idle rooms stop accruing duration charges that way. The Workers Free plan only supports SQLite-backed Durable Objects, which is why the migration uses new_sqlite_classes (Durable Objects pricing).

Realtime services for static sites compared

Service Free allowance Paid entry What you run
Cloudflare Durable Objects 100,000 requests and 13,000 GB-s a day, 5 GB SQLite storage Workers Paid, $5 a month Your own code on Cloudflare
Supabase Realtime 200 concurrent connections, 100 messages a second Pro from $25 a month Hosted broadcast, presence and Postgres changes
Pusher Channels 200,000 messages a day, 100 concurrent connections Startup, $49 a month: 1 million a day, 500 connections Hosted pub/sub channels
Ably Up to 500 monthly active users Pro, $29 a month billed annually Hosted pub/sub channels

How Durable Objects bill a WebSocket

Opening a connection counts as 1 request. Outgoing messages are free, and incoming messages are billed at a 20:1 ratio, so 100 messages from clients count as 5 requests. On the free plan's 100,000 requests a day, that is roughly 2 million incoming messages a day before anything else uses the quota. Each object can also keep a small state blob per connection with serializeAttachment, capped at 16,384 bytes (WebSocket best practices).

Supabase Realtime when the data is already in Supabase

Supabase Realtime pushes broadcast messages, presence and database changes to the browser over the same client library the site already uses for queries. The free plan allows 200 concurrent connections, 100 messages a second, 100 channel joins a second and broadcast payloads up to 256 KB. Free projects pause after 1 week of inactivity, which also stops Realtime.

Pusher and Ably: hosted channels, no server code

Pusher Channels needs no server code for public channels: the page subscribes with an app key, and you publish from wherever events happen. The free Sandbox covers 200,000 messages a day and 100 concurrent connections; Startup is $49 a month for 1 million messages a day and 500 connections. Ably now prices by monthly active users: free up to 500, Pro at $29 a month billed annually for 1,000.

PartyKit's hosted platform is gone

PartyKit turned off deploys on October 16, 2026 and shut its hosted platform down on October 23, 2026, deleting remaining projects and room data (shutdown post). New projects are pointed to PartyServer, which runs on Durable Objects in your own Cloudflare account, and existing ones have a migration guide.

Keeping a public socket cheap and safe

A WebSocket endpoint on a static site is reachable by anyone who reads the page source, so assume it will be scripted:

  • Check the Origin header in the Worker and reject upgrades from other sites.
  • Cap message size and rate per connection, and close sockets that exceed it. Every incoming message is billed.
  • Cap connections per room. One object holding 10,000 sockets fans every message out 10,000 times.
  • Validate room names server-side, or a client can create a new billed object per request.
  • Send auth tokens in the first message, not in the URL, where they end up in logs.

Where to host the page

The socket lives on Durable Objects, Pusher, Ably or Supabase, and the page only opens a connection, so any static host works. For a no-account demo link, ship.page publishes a 10 MB HTML file for 7 days; for a permanent free site, Cloudflare Pages and GitHub Pages cost $0. See the free HTML hosting benchmark and the Netlify Drop alternatives test.

Frequently asked questions about WebSockets on static sites

Can GitHub Pages or ship.page host a WebSocket server?

No. Static hosts serve files only. Run the socket on Cloudflare Durable Objects (100,000 requests a day free), Pusher, Ably or Supabase Realtime, and open it from the page with new WebSocket(...) or the vendor's client library. The page itself can stay on any static host.

Does a hibernating Durable Object drop its WebSocket clients?

No. With the Hibernation API, Cloudflare can evict an idle object from memory while client connections stay open. The next incoming message wakes the object and calls webSocketMessage. In-memory variables are lost on eviction, so keep per-connection state in serializeAttachment (up to 16,384 bytes) or in storage.

How many people can use Pusher's free plan at once?

100 concurrent connections, with 200,000 messages a day, on the Sandbox plan. Startup raises that to 500 connections and 1 million messages a day for $49 a month. For bigger free audiences, Durable Objects bill per request instead of per connection.

Back to backend guides