Documents development environment setup, WSL2 patterns, and tooling configuration.
Institutional knowledge for local dev-server networking, learned from real
forge-ui debugging. All code here mirrors the live config in
forge-ui/vite.config.ts and forge-ui/src/services/api-client.ts (ports:
UI 5050 → proxies to API/WS 5051).
Severity: Critical (blocks all functionality from non-localhost devices).
API/WS calls fail only when accessed from a phone, tablet, or another PC:
Cross-Origin Request Blocked: localhost:5051NetworkError when attempting to fetch resourcelocalhost on the remote device points at that device, not the dev server.
Any URL that names localhost (hardcoded or via VITE_API_URL) is unreachable
off-machine. The fix is relative URLs proxied by Vite, so the browser only ever
talks same-origin to whatever host it loaded the page from.
// src/services/api-client.ts — env var wins, else relative /api in dev
const getApiBaseUrl = () => {
if (import.meta.env.VITE_API_URL) return import.meta.env.VITE_API_URL; // prod only
if (import.meta.env.DEV) return "/api"; // Vite proxies this
const host = typeof window !== "undefined" ? window.location.hostname : "localhost";
return `http://${host}:5051/api`; // prod fallback
};
// vite.config.ts — bind all interfaces + proxy /api, /ws, /terminal to 5051
server: {
host: "0.0.0.0", // accept connections from any device
port: 5050,
strictPort: true, // FAIL if taken — does NOT auto-increment
proxy: {
"/api": { target: "http://localhost:5051", changeOrigin: true },
"/ws": { target: "ws://localhost:5051", ws: true, changeOrigin: true },
"/terminal": { target: "ws://localhost:5051", ws: true },
},
}
# .env — never hardcode localhost. Leave VITE_API_URL UNSET in dev.
# VITE_API_URL=http://localhost:5051/api # WRONG — breaks every non-local device
Accessed via http://192.168.1.206:5050, a /api/... fetch resolves to
http://192.168.1.206:5050/api/... (relative to window.location.host). Vite's
dev server intercepts /api, /ws, /terminal and forwards to localhost:5051
server-side. The browser only sees same-origin requests, so there is no CORS.
grep -rn "VITE_API_URL\|localhost:5051" .env* src/ → find VITE_API_URL=http://localhost:5051/api in .env.local.import.meta.env.DEV → "/api" branch, so the bundle ships an absolute localhost URL. On the phone localhost = the phone → connection refused.Ctrl/Cmd+Shift+R) to drop cached JS./api/... (relative), not localhost.| Anti-pattern | Why it's wrong |
|--------------|----------------|
| VITE_API_URL=http://localhost:5051 in dev | Overrides the relative-URL branch; only works on the dev machine |
| fetch("http://localhost:5051/api") | Hardcoded host; breaks every other device |
| 127.0.0.1 instead of 0.0.0.0 for server bind | Accepts local connections only |
| Skipping the Vite proxy | Forces manual CORS config on the API server |
| Omitting changeOrigin: true on the proxy | Target may reject the mismatched Host header |
grep -rn "VITE_" .env* — any hardcoded localhost? Comment it out.vite.config.ts has host: "0.0.0.0"./api, /ws, /terminal)..env change (not just HMR).curl http://<dev-ip>:5050/api/health from another box to isolate app vs network.forge-ui/.env* — environment variables (build-time baked)forge-ui/vite.config.ts — bind + proxy configforge-ui/src/services/api-client.ts — getApiBaseUrl() URL constructionBind to 0.0.0.0 for any dev server that must be reachable off-machine:
server: { host: "0.0.0.0" } // Vite
app.listen(port, "0.0.0.0") // Express
server.listen(port, "0.0.0.0")// Node http
0.0.0.0 exposes the server on all interfaces — appropriate for local
development on a trusted network and required for WSL2 → Windows-host access.
For production use a specific interface or a reverse proxy.
Real, non-obvious failure modes for this stack:
getApiBaseUrl() checks
VITE_API_URL before import.meta.env.DEV. A stray value in .env,
.env.local, .env.development, or the shell environment overrides the
relative-URL logic — dev looks broken with no code change. .env.local is
gitignored, so it survives git status clean and is the classic "works on my
machine" culprit..env does
nothing until you restart the Vite process; HMR will not pick it up. A stale
dist-ui/ production build carries the URL baked at its build time.strictPort: true means no fallback port. If 5050 is already in use Vite
exits with an error instead of hopping to 5051/5052. Find the stale process
(lsof -i :5050 or ss -ltnp | grep 5050) and kill it — do not expect a new
port.192.168.x.x / 172.x.x.x) is reassigned each boot, so a bookmarked
http://<old-ip>:5050 goes dead. Use WSL2 mirrored networking (Windows 11
.wslconfig → networkingMode=mirrored) so localhost and the LAN IP work
from the Windows host, or re-check the IP with hostname -I after each reboot.0.0.0.0. Binding all
interfaces is necessary but not sufficient — a fresh Windows Firewall rule may
still drop inbound 5050 from other LAN devices. If 0.0.0.0 is set and the
Windows host still can't reach it, suspect the firewall before the app./ws and /terminal
are distinct proxy entries. A terminal/PTY feature can be dead while the main
WebSocket works (or vice versa) if only one proxy block is present or ws: true is missing on it.:5051. The non-dev branch builds
http://${host}:5051/api. Behind a reverse proxy that serves the API on the
same origin/port, that hardcoded :5051 is wrong — set VITE_API_URL
explicitly for that deploy.Status: Living document — update with new learnings.
npx skills add nxtg-ai/Dev Environment Patterns下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer