Skip to main content

Cloudflare server in production

@rpgjs/server/cloudflare runs the same framework-agnostic server module as the Node adapter. Each Signe namespace and room id is routed deterministically to a Durable Object. WebSockets remain attached during hibernation, while persisted room state lets RPGJS rebuild a new in-memory instance when the object wakes. If this is your first deployment, complete the shared project and publisher setup in Put an MMORPG online first. This page serves the built browser client and the room Worker from the same public Cloudflare URL.

Mental model

The Worker is the public HTTP and WebSocket entry point. It routes each RPGJS room id to one deterministic Durable Object:
Players connected to the same room share the same authoritative simulation. Different map rooms do not share in-memory state, which is why world topology updates must be sent to every affected map room. RPGJS uses Cloudflare’s hibernatable WebSocket path. While a room is idle, Cloudflare can remove its JavaScript instance from memory without disconnecting its WebSockets. In-memory physics, chunks, events, and world managers are then lost. RPGJS persists the authoritative map source and world topology first, and rebuilds those transient resources when the Durable Object starts again. Do not treat class properties as durable storage. See Cloudflare’s WebSocket hibernation lifecycle for the underlying runtime behavior.

1. Install Wrangler

Install the current Wrangler CLI in your starter project:
The repository sample pins an older Wrangler release for its GLIBC 2.31 test host. A new project on a current operating system should use Wrangler 4 or later.

2. Create the Worker entry

Create src/entries/cloudflare.ts:

3. Configure the Durable Object and browser assets

Configure RPGJS_ROOMS as a SQLite Durable Object class in wrangler.jsonc and export RpgServerDurableObject from the Worker entry:
Use a recent compatibility date and generate bindings with wrangler types. Keep the existing migration tags after deployment; add a new migration rather than rewriting an already deployed one. Cloudflare documents the available Durable Object migration operations. The binding name in wrangler.jsonc, the Env interface, and createRpgServerWorker() must all be exactly RPGJS_ROOMS. SQLite-backed Durable Objects persist authoritative room state between Worker instances.

Maps and worlds are published externally

The Worker does not read project files from a filesystem and the browser client is not trusted to configure authoritative maps. An editor, Vite development server, CI job, or administrative backend publishes them with the following server-only flow:
The endpoints have different responsibilities: Both endpoints accept the same administration token:
Set the Worker secret interactively:
createRpgServerWorker() fails closed for map and world updates when this secret is missing. A map room can be created by a trusted update before its first player connects. RPGJS awaits the Durable Object storage write before acknowledging either update. createStudioMapUpdatePayload() includes runtime-ready worldUpdates metadata. The RPGJS Vite publisher and the Studio seed command consume it automatically: they publish the selected map, then fan the topology out to every map room in the world. If you build a custom publisher or call the HTTP API directly, you must reproduce both steps. Sending only /map/update does not refresh the world manager of a player who is currently on another map.

Local Vite and Wrangler workflow

Configure the Vite plugin with a remote development server:
Vite proxies HTTP and WebSocket traffic under /parties to Wrangler. It uses a server-only publisher to send the configured maps on startup and after HMR. If the resolved payload contains worldUpdates, the same publisher also calls the world update endpoint for every referenced map room. See samples/cloudflare-mmorpg for the complete setup. For sources that need asynchronous preparation, use resolveMapPayload. This is particularly important for Studio because the publisher must fetch and normalize the complete v2 map before the room compiles its private authoritative state and public chunks:
The callback runs in Vite’s Node process, not in the browser. Do not use a VITE_-prefixed secret. The browser receives only nearby render and physics chunks; event rules, project data, the database, and the complete collision model remain in the Durable Object. Renderable media URLs remain public. The Studio playground contains a local Wrangler entry, integration test, fixture, and authenticated seed command:
RPG_TYPE=mmorpg alone keeps the normal Node.js room hosted by Vite and does not require RPGJS_MAP_UPDATE_TOKEN. The playground’s dev:cloudflare and build:cloudflare scripts additionally set RPGJS_SERVER_ADAPTER=cloudflare; only that adapter enables remote publication and requires the shared secret. It also documents the equivalent curl request and how to publish a real Studio project and map. Studio MMORPG streaming accepts v2 maps; v1 maps remain available to the standalone loader.

First production deployment

The following sequence deploys the browser client and Worker, configures the secret, publishes the starter map, and verifies the live game.
  1. Authenticate and confirm which Cloudflare account Wrangler uses:
  2. Build the MMORPG client and validate the Worker configuration:
  3. Deploy the Worker. Wrangler prints its public workers.dev URL:
  4. Create the administration secret through Wrangler’s interactive prompt. Do not put the value on the command line:
  5. Create an uncommitted .env.publisher file on your trusted development or CI machine. Use the URL printed by wrangler deploy and the same secret:
  6. Run the publisher created in the beginner guide:
    A Published map: simplemap message confirms that the map and its world updates were accepted. The Durable Object persists them before acknowledging the request.
  7. Inspect logs while opening the public URL in two independent browsers:
For later releases, build and deploy the Worker first, then run the publisher whenever map source or world topology changes. Never rewrite the deployed v1 migration tag; append a new migration if a future Durable Object class changes.

Direct HTTP publication

Prefer the Vite publisher, the Studio seed command, or a trusted backend because they perform the world fan-out. To inspect the HTTP contract manually, a complete publication uses the same secret for both requests:
prepared-world.json can be either { "id": "<world-id>", "maps": [...] } or the maps array itself. Repeat the second request for every map id contained in that world. A successful 2xx response means the target room updated its live world manager and persisted the topology.

Production checklist

These commands use the top-level Worker configuration shown above. If you add a named Wrangler environment, append the same --env <name> option to every secret, deploy, and tail command.
  1. Authenticate Wrangler with npx wrangler login or configure the appropriate CI API token.
  2. Build the MMORPG client and Worker entry.
  3. Verify the binding and the new_sqlite_classes migration in wrangler.jsonc.
  4. Deploy the Worker and note its public URL:
  5. Configure the secret for that Worker through the interactive prompt:
  6. Point the trusted publisher at the deployed Worker URL and publish the start map. For Studio worlds, confirm that it reports successful world publication for every map room.
  7. Open two clients, change maps in both directions, and inspect production logs with npx wrangler tail.
Never expose RPGJS_MAP_UPDATE_TOKEN in browser code, a VITE_ variable, a committed .env file, or a public build artifact.

Troubleshooting

Empty maps

When the final connection leaves a map, RPGJS stops its 60 Hz simulation loop. Persisted state remains in the Durable Object. On the next connection, RPGJS restores the state and rebuilds transient physics and event resources without simulating elapsed empty-room time.