# How to preview a Next.js app an AI agent built Run `next dev` in the agent's sandbox with spawn, allow `*.runtimehost.com` in `allowedDevOrigins`, and share port 3000 as a preview. **On Runtime, each agent's app gets its own microVM and a private HTTPS address in one call.** The app never runs on your servers. The address needs a token unless you make it public. Hot reload works because previews support WebSockets. The sandbox has Node.js 24.21.0. Next.js 16.4.3, the current version on 25 October 2026, needs Node.js 20.9 or later. ## Run the agent's app Upload what the agent wrote, or scaffold a fresh project. Then start the dev server and share it: ```ts check import { Sandbox } from "withruntime"; const nextConfig = `import type { NextConfig } from "next"; const nextConfig: NextConfig = { allowedDevOrigins: ["*.runtimehost.com"] }; export default nextConfig; `; const sbx = await Sandbox.create({ timeoutSeconds: 3600 }); // keeps running after this script ends await sbx.exec("npx create-next-app@16.4.3 site --yes --use-npm --disable-git", { check: true, timeoutMs: 600_000, env: { NEXT_TELEMETRY_DISABLED: "1" }, }); await sbx.files.write("/workspace/site/next.config.ts", nextConfig); await sbx.spawn("npx next dev -p 3000", { cwd: "/workspace/site", env: { NEXT_TELEMETRY_DISABLED: "1" }, }); await sbx.exec("npx wait-on@9.1.0 http://127.0.0.1:3000", { check: true, timeoutMs: 180_000 }); const preview = await sbx.previews.create(3000); console.log(preview.urlWithToken); // a link that carries its token ``` ```python check from withruntime import Sandbox NEXT_CONFIG = """import type { NextConfig } from "next"; const nextConfig: NextConfig = { allowedDevOrigins: ["*.runtimehost.com"] }; export default nextConfig; """ sbx = Sandbox.create(timeout_seconds=3600) sbx.exec("npx create-next-app@16.4.3 site --yes --use-npm --disable-git", check=True, timeout_ms=600_000, env={"NEXT_TELEMETRY_DISABLED": "1"}) sbx.files.write("/workspace/site/next.config.ts", NEXT_CONFIG) sbx.spawn("npx next dev -p 3000", cwd="/workspace/site", env={"NEXT_TELEMETRY_DISABLED": "1"}) sbx.exec("npx wait-on@9.1.0 http://127.0.0.1:3000", check=True, timeout_ms=180_000) print(sbx.previews.create(3000)["urlWithToken"]) ``` For a project the agent already wrote, replace the scaffold with `sbx.files.upload("./site", "/workspace/site")` and `npm ci`. Upload the source without its `node_modules`. Packages installed on a Mac carry native binaries built for the wrong system. ## Why `allowedDevOrigins` matters In development, Next.js blocks requests for dev-only assets and endpoints. It blocks them when they come from an origin other than `localhost`, its subdomains, or the hostname the server started with. A preview's hostname is a name under `runtimehost.com`. Without an entry, the page loads, but the dev assets it requests are refused with 403. The dev server logs "Blocked cross-origin request to Next.js dev resource". Hot reload then never connects. | Setting | Effect | | ------------------------------------------ | ------------------------------------------------------------------------------ | | No entry | Next.js blocks the preview's requests for dev assets | | `allowedDevOrigins: ["*.runtimehost.com"]` | Every preview address works; `*` covers one label, which is all a preview uses | | `next start` (production) | No dev endpoints, so the setting does not apply | Next.js matches only the hostname of the request's `Origin`. Write the entry without `https://` and without a port. ## Dev server or production build | Question | `next dev` | `next build` then `next start` | | ----------------------------------- | ---------------------------------- | ------------------------------ | | Does an edit show immediately? | Yes, over hot reload | No; rebuild first | | Does it match what users would get? | Close, but compiled on request | Yes | | Who should open it? | The person watching the agent work | A reviewer, a client, a demo | | Preview visibility | Private, with its token link | Private, or public when paid | To share the finished app: ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.connect(process.env.SANDBOX_ID ?? ""); const build = await sbx.exec("cd site && npx next build", { timeoutMs: 900_000, onStdout: (text) => process.stdout.write(text), }); if (build.exitCode !== 0) throw new Error(build.stdout + build.stderr); await sbx.spawn("npx next start -p 3001", { cwd: "/workspace/site" }); await sbx.exec("npx wait-on@9.1.0 http://127.0.0.1:3001", { check: true, timeoutMs: 120_000 }); const demo = await sbx.previews.create(3001); console.log(demo.urlWithToken); // the link carries its token ``` On a paid sandbox, `sbx.previews.create(3001, { visibility: "public" })` gives an address that anyone can open, with no token. A sandbox without credit keeps its previews private. A failed build is useful to the agent too. `next build` checks types. Send its output back as the next message, and the agent sees each error with its file and line. ## How much disk a Next.js app takes A fresh `create-next-app@16.4.3` project, measured on a Linux x86-64 machine on 25 October 2026: | Part | Size | | ------------------------------ | ------ | | `node_modules` | 476 MB | | `.next` after `next build` | 30 MB | | npm's download cache, `~/.npm` | 533 MB | That fits the default 4 GiB disk, which has about 2.5 GiB free, with room to spare. An agent that adds many packages, or a monorepo, needs more. Ask for `diskMiB: 8192` when you create the sandbox. Without credit, a sandbox has up to 10 GiB. ## Start from an image with the dependencies installed Build the project's dependencies into a [custom image](/docs/images) with a Dockerfile. The lockfile comes first, so a rebuild after a code change reuses the installed packages: ```dockerfile no-run FROM runtime ENV NEXT_TELEMETRY_DISABLED=1 WORKDIR /workspace/site COPY package.json package-lock.json ./ RUN npm ci --no-fund --no-audit COPY . . RUN chown -R 1000:1000 /workspace/site ``` ```python check from withruntime import Runtime runtime = Runtime() with open("site/Dockerfile") as handle: runtime.images.build(name="site", dockerfile=handle.read(), context_dir="site", on_log=lambda line: print(line["text"])) sbx = runtime.sandboxes.create(image="site") sbx.spawn("npx next dev -p 3000", cwd="/workspace/site") print(sbx.previews.create(3000)["urlWithToken"]) ``` Keep `node_modules` and `.next` in `.dockerignore`, so the build context holds only the source. ## Related - [Preview apps an AI agent builds](/use-cases/preview-agent-built-apps) - [Preview a Vite dev server](/integrations/vite) - [Test the app with Playwright](/integrations/playwright) - [Per-user dev environments](/use-cases/per-user-dev-environments) - [Custom domains for a finished app](/docs/networking) ## Sources Checked 25 October 2026. - [allowedDevOrigins](https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins): what Next.js blocks in development, the `localhost` default, wildcard rules and hostname-only matching - [create-next-app](https://nextjs.org/docs/app/api-reference/cli/create-next-app): `--yes`, `--use-npm`, `--disable-git` - [next CLI](https://nextjs.org/docs/app/api-reference/cli/next): `-p`, default port 3000, Turbopack by default - [Next.js installation](https://nextjs.org/docs/app/getting-started/installation): Node.js 20.9 or later - [Next.js telemetry](https://nextjs.org/telemetry): `NEXT_TELEMETRY_DISABLED=1` - [next on npm](https://www.npmjs.com/package/next): version 16.4.3 Facts on this page were checked on 25 October 2026.