Goal: ship browser-mode ZCode (SPA + stateless /git-proxy) on Cloudflare without a stateful control plane.
Remote IDE (REH / zcode serve) is a separate self-host path — not required for Pages+Worker.
https://zcode.example.com/
├── / → Cloudflare Pages (apps/web/dist)
├── /git-proxy/* → Cloudflare Worker (deploy/cloudflare/git-proxy)
└── (optional later) → / + REH on a Node host (not Pages)
Invariants:
- Git proxy is stateless (no repo storage)
- No secrets in URLs (KD12)
- Same hostname for SPA + proxy (same-origin)
| Item | Notes |
|---|---|
| Cloudflare account | Pages + Workers enabled |
| Domain | Optional custom domain on Pages |
| Node 20+ / pnpm 11 | Local build |
| Wrangler | npx wrangler (login once) |
npx wrangler loginpnpm install
pnpm --filter @zcode/web build
# output: apps/web/dist (index.html, app.js, git-worker.js, …)
ls apps/web/dist/index.htmlOptional: stage VS Code Web only if you also host the workbench on the same Pages project (usually not — keep IDE on Node/zcode serve).
cd deploy/cloudflare/git-proxy
# Review wrangler.toml name + account
npx wrangler deployAttach a route on the same hostname as Pages:
| Route | Worker |
|---|---|
zcode.example.com/git-proxy/* |
zcode-git-proxy (or name from wrangler.toml) |
Dashboard: Workers → your worker → Triggers → Add route.
Alternatively use Workers for Platforms / zone routes so /* still hits Pages for non-proxy paths.
curl -sS https://zcode.example.com/git-proxy/healthz
# expect: {"ok":true,"service":"zcode-git-proxy",... "mode":"stateless"}# from monorepo root
pnpm --filter @zcode/web build
npx wrangler pages deploy apps/web/dist --project-name=zcode- Bind custom domain
zcode.example.comto the Pages project - Ensure Worker route takes precedence for
/git-proxy/*(Cloudflare: more specific Worker routes win)
If deep links 404, add Pages _redirects or public/_routes so non-file paths serve index.html. Current SPA is mostly / + assets; git-worker is a static file.
- Open
https://zcode.example.com/ - Test proxy → green /
ok:true - Clone a public HTTPS repo (e.g. a tiny public fixture)
- Edit → commit → (optional) push with PAT in sessionStorage only
Private clone requires a PAT in the SPA (never put tokens in the URL).
Browser SPA is simpler than full VS Code Web. If you add a transform rule CSP:
- Allow
connect-src 'self'for/git-proxy - Worker must not strip CORS headers it sets
Self-host IDE CSP is applied by @zcode/server on HTML (M2) — not by Pages for SPA-only.
| Layer | Rollback |
|---|---|
| Pages | Deploy previous deployment in Pages UI / wrangler pages deployment list |
| Worker | wrangler rollback or redeploy prior version |
-
pnpm --filter @zcode/web buildsucceeds - Worker
healthzreturnsmode:stateless - SPA Test proxy green on production hostname
- Public clone completes without server-side storage
- No
tkn/connectionTokenin browser location bar - Worker allowlist still blocks non-git hosts / private IPs
| Path | Where |
|---|---|
/ VS Code Web |
Self-host zcode serve or fat Docker (R5/R4) |
| REH / terminal | dist/server + cookie proxy (R2c/R3b/R6) |
| Multi-tenant SaaS | microVM orchestrator (P3) — not Docker multi-tenant |
Local validation without pushing to a live zone:
bash scripts/hosting-dry-run.sh
# after wrangler login (shared external action — ask before --deploy):
# bash scripts/hosting-dry-run.sh --deployThis builds the SPA, checks Worker sources / wrangler.toml, and attempts
wrangler deploy --dry-run when the CLI is available. Live Pages+Worker attach
still needs a Cloudflare account (see checklist above).
- hosting.md — local topology
- deploy/cloudflare/README.md — Worker details
- m2-diagnostics-csp.md — CSP on IDE host
- deploy/docker/README.md — H4 Docker self-host