# 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

Edited by the agent

;\n}\n", ); ``` `--strictPort` makes Vite exit when 5173 is taken, instead of moving to the next free port and leaving the preview pointing at nothing. The process list, `sbx.processes.list()`, shows whether Vite is still running. ## Build and serve the production bundle The dev server transforms files on request. To show what users would get, build once and serve `dist` with `vite preview`: ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.connect(process.env.SANDBOX_ID ?? ""); await sbx.exec("cd app && npx vite build", { check: true, timeoutMs: 300_000 }); await sbx.spawn("npx vite preview --port 4173 --strictPort", { cwd: "/workspace/app", env: { __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: ".runtimehost.com" }, }); const shared = await sbx.previews.create(4173, { visibility: "public" }); console.log(shared.url); // anyone with the address can open it await sbx.files.download("/workspace/app/dist", "./dist"); ``` A public preview needs no token, which suits a demo link. `previews.delete(port)` stops sharing, and `previews.rotate(port)` refuses every private token issued so far. ## Skip the install next time A new sandbox for each project spends most of its first minute in `npm install`. Build a [custom image](/docs/images) with the template already installed, and hand its folder to the sandbox user: ```python check from withruntime import Runtime runtime = Runtime() runtime.images.build( name="vite-react", recipe={ "env": {"__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS": ".runtimehost.com"}, "commands": [ "npm create vite@9.2.1 app -- --template react-ts --no-interactive", "cd app && npm install --no-fund --no-audit", "chown -R 1000:1000 /workspace/app", ], }, ) sbx = runtime.sandboxes.create(image="vite-react") sbx.spawn("npx vite --port 5173 --strictPort", cwd="/workspace/app") print(sbx.previews.create(5173)["urlWithToken"]) ``` The recipe's `env` becomes the image's environment, so every sandbox from it already allows the preview host. ## Ports and addresses | Port | What serves it | Default in Vite | | ---- | ----------------- | --------------- | | 5173 | `vite` dev server | `server.port` | | 4173 | `vite preview` | `preview.port` | Any port can take a preview. The trial reaches ports 80 and 443 on the internet, which covers the npm registry. ## Related - [Preview apps an AI agent builds](/use-cases/preview-agent-built-apps) - [Preview a Next.js app an agent built](/integrations/nextjs) - [Run JavaScript and Node.js code in a sandbox](/languages/javascript) - [Share a port](/docs/javascript#share-a-port) ## Sources Checked 25 September 2026. - [Vite server options](https://vite.dev/config/server-options): `server.host` (`localhost`), `server.port` (5173), `server.allowedHosts`, the leading-dot rule, the DNS rebinding warning and `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` - [Vite preview options](https://vite.dev/config/preview-options): `preview.port` (4173) and `preview.allowedHosts` - [Vite getting started](https://vite.dev/guide/): `npm create vite@latest -- --template` and `--no-interactive` - [vite on npm](https://www.npmjs.com/package/vite): version 8.3.1, Node `^20.19.0 || >=22.12.0`; [create-vite](https://www.npmjs.com/package/create-vite) 9.2.1 - A local test of Vite 8.3.1 and create-vite 9.2.1 on 25 September 2026: the `react-ts` template scaffolded with `--no-interactive`, and the host check answered 403 without the variable and 200 with it, for `vite` and `vite preview` Facts on this page were checked on 25 September 2026.