Build and deploy
Sites
How a build's web client is served from <project>.irt-serve.com and by irt dev, with caching, single-page app routes, cross-origin isolation and the reserved /api path.
A build can carry the project’s site: its web client’s static files.
The live build’s site is served at https://<slug>.irt-serve.com, and
irt dev serves it locally the same way, so a client always finds the
room code it was built against.
Adding a site
// irt.config.ts
import { defineConfig } from "@irt/cli/config";
export default defineConfig({
project: "space-race",
room: "src/room.ts",
site: { dir: "dist", build: "npm run build:web", crossOriginIsolated: false },
});
irt build, irt deploy and irt dev run site.build, if given, from
the project’s directory, then take every file under site.dir. Use any
bundler: Vite, esbuild, or none. A project without site deploys room
code only.
How files are served
/and any path ending in/serve that directory’sindex.html.- A path with no file extension that isn’t a file, such as
/rooms/forest-7, serves/index.html, so a single-page app’s client side routes work. A missing path with an extension is a 404. - Only
GETandHEAD; anything else is 405. - Content types come from the extension: HTML, JavaScript, CSS, JSON,
source maps, images, fonts, audio, video,
.wasm, and.glband.gltfmodels. Other files areapplication/octet-stream. Every response hasX-Content-Type-Options: nosniff. - HTML is served with
Cache-Control: no-cache, so browsers check for a new version on every load; everything else withpublic, max-age=300. Give bundles hashed file names, as most bundlers do, so a new build’s HTML never loads an old build’s scripts. - When a build goes live, every edge server serves it within 10 seconds.
irt-serve.com is the user content domain: tenant content is never
served from irt.io. Custom domains aren’t available yet.
Cookies and storage
Until irt-serve.com is on the Public Suffix List, browsers treat every
*.irt-serve.com project as one site for cookies: another project’s
page can set a cookie that yours receives. Name your own cookies with the
__Host- prefix, which a sibling subdomain can’t set. localStorage and
other storage are per origin, so they’re your project’s alone already.
Cross-origin isolation
site.crossOriginIsolated: true serves the site with
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
which make the page cross-origin isolated. That’s what
SharedArrayBuffer, Wasm threads and precise timers need, such as for a
physics engine running in workers. The cost: the page can then load a
cross-origin resource only if it opts in, with
Cross-Origin-Resource-Policy: cross-origin or CORS and a crossorigin
attribute. Fonts, images and scripts from a CDN that doesn’t send those
headers stop loading, and so do iframes and popups from other origins.
Serve such files from the site itself, or leave the flag off.
The flag is part of the build, so it changes when a build goes live and rolls back with it.
/api is reserved
/api and every path under it answer 404 with
{ "error": { "code": "not_found", "message": "No backend is deployed for this project" } }
whatever files the site has. The path is kept for backends hosted on the
site’s own origin, so don’t put site files under /api/; a file there is
never served.
irt dev
irt dev serves the site and the local room host on one origin,
http://127.0.0.1:7350 by default (--port changes it):
| Path | Serves |
|---|---|
/v1/ws |
The client protocol over WebSocket |
/v1/rooms, /v1/codes/... |
The client HTTP API that createRoom, joinByCode and listPublic use |
/api, /api/... |
404, as deployed |
| anything else | The site, as deployed: index.html fallback and COOP and COEP when the build asks for them |
Locally every response is Cache-Control: no-store, so a rebuild shows
on the next reload. irt dev reruns site.build and restarts the room
host when a project file changes; connected clients reconnect. Paths
under /v1/ belong to the room host, so keep site files out of a v1
directory.
Because both are on one origin, a client can pick its router by where it’s served from:
// router.ts
export const router = location.hostname.endsWith(".irt-serve.com") ? "wss://eu.irt.io" : location.origin;
irt deploy prints the router the project’s clients use.
Common mistakes
- Hard-coding
http://127.0.0.1:7350as the router. The deployed site then can’t connect. Choose bylocation, as above. - Turning on
crossOriginIsolatedwith assets on another origin. They stop loading unless they send CORP or CORS headers. - Bundles without hashed names. They’re cached for 5 minutes, so a
page can mix a new
index.htmlwith old scripts. - Files under
/api/. They’re never served. - Deploying the site separately from the room. Sites deploy only with their build, which is what keeps clients and rooms on one schema; see Builds and deploys.