# How to preview a Vite dev server running in a cloud sandbox Start `vite --port 5173 --strictPort` with spawn, allow `.runtimehost.com` as a host, and open the port's private HTTPS preview. **On Runtime the preview is private by default and hot reload works through it.** The address needs a token, WebSockets pass through, and every file the agent writes shows up in the browser without a reload. The sandbox runs Node.js 24.21.0, which Vite 8 supports; Vite 8.3.1 and create-vite 9.2.1 were current on 25 September 2026. ## Start Vite and share the port ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.create({ timeoutSeconds: 3600 }); // keeps running after this script ends await sbx.exec( "npm create vite@9.2.1 app -- --template react-ts --no-interactive && cd app && npm install --no-fund --no-audit", { check: true, timeoutMs: 600_000 }, ); await sbx.spawn("npx vite --port 5173 --strictPort", { cwd: "/workspace/app", env: { __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: ".runtimehost.com" }, }); await sbx.exec("npx wait-on@9.1.0 tcp:127.0.0.1:5173", { check: true, timeoutMs: 120_000 }); const preview = await sbx.previews.create(5173); console.log(preview.urlWithToken); // open once in your browser ``` ```python check from withruntime import Sandbox sbx = Sandbox.create(timeout_seconds=3600) sbx.exec("npm create vite@9.2.1 app -- --template react-ts --no-interactive && cd app && npm install --no-fund --no-audit", check=True, timeout_ms=600_000) sbx.spawn("npx vite --port 5173 --strictPort", cwd="/workspace/app", env={"__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS": ".runtimehost.com"}) sbx.exec("npx wait-on@9.1.0 tcp:127.0.0.1:5173", check=True, timeout_ms=120_000) preview = sbx.previews.create(5173) print(preview["urlWithToken"]) ``` `spawn` keeps Vite running after the call returns. A server started with `exec`, `nohup` included, ends when its command does. Vite listens on `localhost` by default, and that is enough: a preview reaches the port inside the sandbox. ## Why Vite needs the allowed host Vite answers only the hostnames in `server.allowedHosts`, plus `localhost` and IP addresses, to block DNS rebinding attacks. A preview's address is a name under `runtimehost.com`, the domain Runtime keeps for everything sandboxes serve, so Vite refuses it until you add it. | Way to allow it | Where | | --------------------------------------------------------- | ---------------------------------------------------------------------- | | `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.runtimehost.com` | The environment of `spawn`, as above; no file changes | | `server: { allowedHosts: [".runtimehost.com"] }` | `vite.config.ts`, for a project you keep | | `allowedHosts: true` | Not recommended: Vite's docs warn it opens the server to DNS rebinding | A leading dot allows the domain and every name under it. `vite preview` reads `preview.allowedHosts`, which defaults to `server.allowedHosts`. What the browser shows without it, from a test of Vite 8.3.1 on 25 September 2026 with a preview-shaped host name: ```text Blocked request. This host ("5173-abc.runtimehost.com") is not allowed. To allow this host, add "5173-abc.runtimehost.com" to `server.allowedHosts` in vite.config.js. ``` That was a 403 from both `vite` and `vite preview`. With `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.runtimehost.com` set, both answered 200. If an agent reports this page, the variable is missing from the process that runs Vite, often because it was started by hand instead of with the `env` above. ## Hot reload while an agent edits Vite watches the project's files. Anything written into the sandbox, by `files.write`, by `git pull` or by an agent's own editor, reaches the open browser tab over Vite's WebSocket: ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.connect(process.env.SANDBOX_ID ?? ""); await sbx.files.write( "/workspace/app/src/App.tsx", "export default function App() {\n return