Skip to content
Skills Library
Programming · Analysis

Dev Environment Patterns

/Dev Environment Patterns

Debug and configure local dev-server networking for multi-device access — Vite proxy, env-var URL priority, 0.0.0.0 binding, and WSL2 quirks. Use when API/fetch or WebSocket calls fail from a phone/tablet/other PC ("localhost refused", CORS blocked, NetworkError, WS won't connect), when a server is only reachable from localhost, when a leftover VITE_API_URL breaks dev, or when setting up network access on WSL2.

Licence
MIT
Last repository change
15.10.2026

Assessment and security

90/ 100Class: good
Format15 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability25 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
Files in this package
1
Scripts
0
HTML comments
0
URLs in SKILL.md
11
Deprecation

No signals detected. Repository changed on 15.10.2026, without references to retired models or APIs.

Licence

MIT, open: use and modify while retaining author attribution.

What it does and when to use it

Debug and configure local dev-server networking for multi-device access — Vite proxy, env-var URL priority, 0.0.0.0 binding, and WSL2 quirks. Use when API/fetch or WebSocket calls fail from a phone/tablet/other PC ("localhost refused", CORS blocked, NetworkError, WS won't connect), when a server is only reachable from localhost, when a leftover VITE_API_URL breaks dev, or when setting up network access on WSL2.

Triggers from the description

localhost refused
Original author description

Debug and configure local dev-server networking for multi-device access — Vite proxy, env-var URL priority, 0.0.0.0 binding, and WSL2 quirks. Use when API/fetch or WebSocket calls fail from a phone/tablet/other PC ("localhost refused", CORS blocked, NetworkError, WS won't connect), when a server is only reachable from localhost, when a leftover VITE_API_URL breaks dev, or when setting up network access on WSL2.

How it works

  1. Pattern: Multi-Device Development Access
  2. Pattern: Server Binding for Network Access
  3. Gotchas
Original SKILL.md headings · 156 lines, 1,013 words
SKILL.md preview
---
name: Dev Environment Patterns
description: >
  Debug and configure local dev-server networking for multi-device access — Vite
  proxy, env-var URL priority, 0.0.0.0 binding, and WSL2 quirks. Use when API/fetch
  or WebSocket calls fail from a phone/tablet/other PC ("localhost refused", CORS
  blocked, NetworkError, WS won't connect), when a server is only reachable from
  localhost, when a leftover VITE_API_URL breaks dev, or when setting up network
  access on WSL2.
when_to_use: >
  Triggers: "works on my machine but not my phone", "CORS blocked localhost:5051",
  "NetworkError when attempting to fetch", "WebSocket connection failed", "can't
  reach dev server from another device", "vite proxy not working", "port 5050
  already in use", "how do I bind to 0.0.0.0", "WSL2 windows host can't reach
  vite", "VITE_API_URL keeps overriding".
allowed-tools: Read, Grep, Bash(lsof:*), Bash(ss:*), Bash(curl:*), Bash(cat:*)
---

# Development Environment Patterns

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).

---

## Pattern: Multi-Device Development Access

**Severity**: Critical (blocks all functionality from non-localhost devices).

### Symptom

API/WS calls fail only when accessed from a phone, tablet, or another PC:
- `Cross-Origin Request Blocked: localhost:5051`
- `NetworkError when attempting to fetch resource`
- WebSocket connection failures

### Root cause

`localhost` 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.

### Correct pattern (matches live source)

```ts
// 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
};
```

```ts
// 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 },
  },
}
```

```bash
# .env — never hardcode localhost. Leave VITE_API_URL UNSET in dev.
# VITE_API_URL=http://localhost:5051/api   # WRONG — breaks every non-local device
```

### Why it works

Accessed via `http://192.0.2.10:5050`, a `/api/...` fetch resolves to
`http://192.0.2.10: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.

### Worked example — "signup works on laptop, dead on phone"

1. `grep -rn "VITE_API_URL\|localhost:5051" .env* src/` → find `VITE_API_URL=http://localhost:5051/api` in `.env.local`.
2. That value wins over the `import.meta.env.DEV → "/api"` branch, so the bundle ships an absolute `localhost` URL. On the phone `localhost` = the phone → connection refused.
3. Comment it out, **restart Vite** (env is baked at startup, HMR won't pick it up), hard-refresh the phone (`Ctrl/Cmd+Shift+R`) to drop cached JS.
4. Confirm in the phone's console the request now hits `/api/...` (relative), not `localhost`.

### Anti-patterns

| 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 |

### Debugging checklist

1. [ ] `grep -rn "VITE_" .env*` — any hardcoded `localhost`? Comment it out.
2. [ ] `vite.config.ts` has `host: "0.0.0.0"`.
3. [ ] Proxy blocks present for every path the app calls (`/api`, `/ws`, `/terminal`).
4. [ ] **Restart Vite** after any `.env` change (not just HMR).
5. [ ] Hard-refresh the remote browser to clear cached JS.
6. [ ] Read the actual failing URL in the remote device's console — is it relative or absolute?
7. [ ] `curl http://<dev-ip>:5050/api/health` from another box to isolate app vs network.

### Related files

- `forge-ui/.env*` — environment variables (build-time baked)
- `forge-ui/vite.config.ts` — bind + proxy config
- `forge-ui/src/services/api-client.ts` — `getApiBaseUrl()` URL construction

---

## Pattern: Server Binding for Network Access

Bind to `0.0.0.0` for any dev server that must be reachable off-machine:

```ts
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.

---

## Gotchas

Real, non-obvious failure modes for this stack:

- **Env var beats the dev branch, silently.** `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.
- **Vite bakes env vars at startup, not per-request.** Editing `.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.
- **WSL2 VM IP changes on reboot.** Under legacy NAT networking the WSL2 IP
  (e.g. `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.
- **Windows Firewall blocks the LAN even with `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.
- **Three separate WS proxies, three separate failures.** `/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.
- **Prod fallback assumes API co-located on `: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.

Versions

The database has no other skills named Dev Environment Patterns.

Database snapshot: 13.11.2026 · package version dc7a24b649b3 (SHA-256 digest of the entire package)

Downloads in the last 30 days: 0 · ranking

Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.

Similar skills

  • 86vite
    Vite构建开发专家助手。当用户需要进行Vite构建配置、dev server开发调试、插件选型与开发、环境变量管理、依赖预构建或构建产物优化时调用。
  • 100local-dev
    Local development environment setup and commands. Use when helping with dev server, Docker, or local testing.
  • 100vite-proxy-folder-name-conflict
    Fix 404 errors for frontend JS files when using Vite proxy. Use when: (1) Browser shows "Failed to load resource: 404" for JS/JSX files that exist on disk, (2) Vite serves HTML instead of JavaScript for module imports, (3) You have a client folder named "api" (or similar) AND a…
  • 100fec-vite-project-standard
    用于创建、配置、审查或调试基于 Vite 的前端项目、vite.config.ts、环境变量、dev server proxy、HMR、插件顺序、library mode、依赖预打包、bundle splitting 或 Vite 构建性能。React/Vue 组件架构请使用框架专项 skill;中文触发词包括 Vite 配置、vite.config、Vite 构建、Vite 性能。
  • 100fec-vite-project-standard
    Use when creating, configuring, reviewing, or debugging Vite-based frontend projects, vite.config.ts, env variables, dev server proxy, HMR, plugin ordering, library mode, dependency pre-bundling, bundle splitting, or Vite build performance. Use framework-specific skills for…
  • 100dev-local
    One-command local dev stack for tools-registry. Use when asked to "start the dev server", "run treg locally", "test login locally", "bring the stack up", or before any manual/browser test against localhost.

Feedback and reports

Your message goes to Bartosz Sokoliński. An email address is needed only for a reply. See the privacy policy.