Skip to content

feat(build): add a standalone mode for deploys with no backend - #166

Merged
mcdope merged 1 commit into
masterfrom
feat/standalone-build-mode
Aug 27, 2026
Merged

mcdope merged 1 commit into
masterfrom
feat/standalone-build-mode

Conversation

@mcdope

@mcdope mcdope commented Aug 26, 2026

Copy link
Copy Markdown
Owner

npx vite build --mode standalone produces a build with multiplayer switched off, for any deploy served as plain static files that cannot reach the signaling server.

The problem

The gate already existed:

// main.ts
const MULTIPLAYER_SERVER_CONFIGURED = Boolean(import.meta.env.VITE_MULTIPLAYER_SERVER_URL);

with updateMultiplayerTabEnabled hiding the tab when it is unset. What was missing was a way to ask for it — a default vite build runs in production mode and loads .env.production.local, so the URL is always present and the tab always appears.

On a host the signaling server does not accept, that advertises a mode which cannot connect. Found by uploading to itch.io and clicking it.

Named for what it is

Nothing here is itch-specific — it applies to any subpath host, any fork without a server of its own, any static mirror. An earlier draft called the mode itch, which was simply the wrong noun.

The honest limitation

.env.standalone sets the variable to empty explicitly, rather than letting the mode work by having no env file at all, so the intent is greppable in one place.

That file is documentation, not a guarantee — and this was measured rather than assumed. Vite's precedence is:

.env  <  .env.local  <  .env.[mode]  <  .env.[mode].local

Dropping a .env.standalone.local in place brings the Multiplayer tab straight back. I tested it, and it behaves identically whether the tracked file exists or not — so the explicit file buys discoverability, not safety. My first instinct was that explicit-empty would be more robust than absence; it isn't, and the test is what said so.

The only check that holds is on the built artifact:

[...document.querySelectorAll("#launch-tabs .tab-btn")]
  .filter(b => b.offsetParent !== null).map(b => b.textContent.trim())

Verification — both directions

The risk here is symmetric: the mode has to work, and it must not leak into the real deploy.

Build Tabs Multiplayer
--mode standalone Local / Repo / Demos / ⚙ hidden ✅
default vite build Local / Repo / Demos / Multiplayer / ⚙ still present ✅

Plus the usual gate: npm run build clean with bundle hygiene, vitest --dir src 3,682 passing across 135 files, docPins 72 passing.

Not included

No README entry yet — the mode is documented in .env.standalone's own comment and is greppable from there. Say if you would rather it were listed under Development Scripts too.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ncJfux8GDacSTeLDcqrhr

`npx vite build --mode standalone` produces a build with multiplayer switched
off, for any deploy served as plain static files that cannot reach the
signaling server.

The gate already existed — `main.ts`'s
`MULTIPLAYER_SERVER_CONFIGURED = Boolean(import.meta.env.VITE_MULTIPLAYER_SERVER_URL)`,
with `updateMultiplayerTabEnabled` hiding the tab when it is unset. What was
missing was a way to ask for it: a default `vite build` runs in production mode
and loads `.env.production.local`, so the URL is always present and the tab
always appears. On a host the signaling server does not accept, that advertises
a mode which cannot connect — found by uploading to itch.io and clicking it.

**Named for what it is, not where it was first needed.** Nothing here is
itch-specific: it applies to any subpath host, any fork without a server of its
own, any static mirror. An earlier draft called the mode `itch` and that was
simply the wrong noun.

`.env.standalone` sets the variable to empty explicitly rather than letting the
mode work by having no env file at all, so the intent is greppable in one place.

**It is documentation, not a guarantee, and this was measured rather than
assumed.** Vite's precedence is `.env` < `.env.local` < `.env.[mode]` <
`.env.[mode].local`. Dropping a `.env.standalone.local` in place brings the
Multiplayer tab straight back — verified, and it does so identically whether the
tracked file exists or not. So the explicit file buys discoverability, not
safety; the only check that holds is on the built artifact.

Verified in both directions, since the risk here is symmetric:

- `--mode standalone` → tabs are Local / Repo / Demos / gear, no Multiplayer
- default `vite build` → Multiplayer still present, so the real deploy is
  untouched by this file existing

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mcdope
mcdope merged commit 8b4ef93 into master Aug 27, 2026
21 checks passed
@mcdope
mcdope deleted the feat/standalone-build-mode branch August 27, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant