# 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.5 and create-vite 9.3.1 were current on 25 October 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.3.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:localhost: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.3.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:localhost: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 accepts requests only for the hostnames in `server.allowedHosts`, plus `localhost` and IP addresses. This blocks 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.5 on 25 October 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 returned 200. If an agent reports this page, the variable is missing from the process that runs Vite. That often happens when the agent started Vite 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. It does not move to the next free port, which would leave 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); console.log(shared.urlWithToken); // the link carries its token await sbx.files.download("/workspace/app/dist", "./dist"); ``` On a paid sandbox, `{ visibility: "public" }` gives an address with no token, which suits a demo link. Previews on a sandbox without credit stay private. `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. Then 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.3.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 made 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. Without credit, a sandbox 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 October 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.5, Node `^20.19.0 || >=22.12.0`; [create-vite](https://www.npmjs.com/package/create-vite) 9.3.1 - A local test of Vite 8.3.5 and create-vite 9.3.1 on 25 October 2026: the `react-ts` template scaffolded with `--no-interactive`, and the host check returned 403 without the variable and 200 with it, for `vite` and `vite preview` Facts on this page were checked on 25 October 2026.