ServerVite Plugin

Vite Plugin

The Colyseus Vite plugin runs your game server inside Vite’s dev server using Vite’s Environment API. A single vite process serves your frontend and hosts the Colyseus server. Hot Module Replacement (HMR) covers both client components and server-side room definitions, with no process restart between edits.

It also drives the production build: vite build emits your static client and a standalone server bundle.

Requirements

  • A Vite project (vite >= 6.0.0).
  • The colyseus package: the plugin ships with it under the colyseus/vite subpath.

Setup

Add the colyseus() plugin to your Vite config, alongside any frontend plugins (such as @vitejs/plugin-react):

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { colyseus } from 'colyseus/vite';
 
export default defineConfig({
  plugins: [
    react(),
    colyseus({
      serverEntry: '/src/server/index.ts',
    }),
  ],
});

The serverEntry file must export your server configuration as server (from defineServer()) or a rooms map. A default export of { server } / { rooms } also works.

src/server/index.ts
import { defineServer, defineRoom, createRouter, createEndpoint } from "colyseus";
import { MyRoom } from "./MyRoom.ts";
 
export const server = defineServer({
  rooms: {
    my_room: defineRoom(MyRoom),
  },
 
  // Type-safe HTTP routes, hot-swapped on reload
  routes: createRouter({
    hello: createEndpoint("/hello", { method: "GET" }, async () => {
      return { message: "Hello world!" };
    }),
  }),
 
  // Express middleware: set up once, persists across reloads
  express: (app) => {
    app.get('/express-hello', (_req, res) => {
      res.json({ message: 'Hello from Express!' });
    });
  },
});

Start everything with the usual Vite command:

npm run dev

Editing MyRoom.ts reloads the server in place; editing your frontend reloads the browser, both without restarting the process. The Colyseus server attaches to Vite’s own HTTP server, so your client and server share a single origin and port (no dev proxy required).

Options

OptionTypeDefaultDescription
serverEntrystring(required)Path to the module exporting server (or rooms).
portnumber2567Port the production server listens on. Dev mode reuses Vite’s port.
serveClientbooleanfalseIn the production build, serve the built client via express.fullStateOnly() with an SPA fallback to index.html. No effect in dev.
quietbooleanfalseSuppress the plugin’s reload/room logs.
httpServerhttp.ServerRequired only when running Vite in middleware mode.
loadWsTransport() => Promise<…>Provide a custom WebSocket transport loader. Defaults to @colyseus/ws-transport.

How server HMR works

The plugin enables Colyseus Development Mode internally. When you save a server file:

  1. The server module is re-imported, picking up your new code.
  2. HTTP routes and room definitions are swapped for the new ones.
  3. Active rooms are cached, disposed, and restored: state and seat reservations are preserved, and connected clients reconnect automatically.

Because this reuses the dev-mode machinery, the same hooks and caveats apply:

  • Implement onCacheRoom() / onRestoreRoom() to preserve data held outside the room state.
  • On the frontend, the onAdd schema callbacks fire again after a reload. Be ready to ignore duplicate calls during development.
⚠️

Like devMode, this is for local development only. The production build (below) runs a normal Colyseus server with HMR disabled.

Production build

vite build --app

This produces:

  • dist/client/: your static frontend assets.
  • dist/server/server.mjs: a standalone server that imports your entry and calls server.listen().

Run it with Node:

node dist/server/server.mjs

To have the server also serve the built client (single deployable), enable serveClient:

vite.config.ts
colyseus({
  serverEntry: '/src/server/index.ts',
  port: 2567,
  serveClient: true,
})

With serveClient, the production server mounts dist/client/ via express.fullStateOnly() and adds an SPA fallback that returns index.html for unmatched GET requests.

Middleware mode

In standalone dev mode the plugin attaches the WebSocket transport to Vite’s own HTTP server. When you run Vite in middleware mode (embedding it inside your own Express/HTTP server), that server owns the socket. You must pass it explicitly:

colyseus({
  serverEntry: '/src/server/index.ts',
  httpServer: myHttpServer,
})