# Next.js + Vercel Run agent-browser from a Next.js app on Vercel using Vercel Sandbox. A Linux microVM spins up on demand, runs agent-browser + Chrome, and shuts down. No binary size limits, no Chromium bundling complexity. ## Setup ```bash pnpm add @vercel/sandbox ``` ## Server action The Vercel Sandbox runs Amazon Linux. Chromium requires system libraries that are not installed by default, so fresh sandboxes need a `dnf install` step before agent-browser can launch Chrome. Use a sandbox snapshot (below) to skip this entirely in production. ```ts "use server"; import { Sandbox } from "@vercel/sandbox"; const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID; const CHROMIUM_SYSTEM_DEPS = [ "nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core", "libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor", "libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm", "mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo", "gtk3", "dbus-libs", ]; function getSandboxCredentials() { if ( process.env.VERCEL_TOKEN && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID ) { return { token: process.env.VERCEL_TOKEN, teamId: process.env.VERCEL_TEAM_ID, projectId: process.env.VERCEL_PROJECT_ID, }; } return {}; } async function withBrowser( fn: (sandbox: InstanceType) => Promise, ): Promise { const credentials = getSandboxCredentials(); const sandbox = snapshotId ? await Sandbox.create({ ...credentials, source: { type: "snapshot", snapshotId }, timeout: 120_000, }) : await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 }); if (!snapshotId) { await sandbox.runCommand("sh", [ "-c", `sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`, ]); await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]); await sandbox.runCommand("npx", ["agent-browser", "install"]); } try { return await fn(sandbox); } finally { await sandbox.stop(); } } export async function screenshotUrl(url: string) { return withBrowser(async (sandbox) => { await sandbox.runCommand("agent-browser", ["open", url]); const ssResult = await sandbox.runCommand("agent-browser", [ "screenshot", "--json", ]); const ssPath = JSON.parse(await ssResult.stdout())?.data?.path; const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]); const screenshot = (await b64Result.stdout()).trim(); await sandbox.runCommand("agent-browser", ["close"]); return { ok: true, screenshot }; }); } export async function snapshotUrl(url: string) { return withBrowser(async (sandbox) => { await sandbox.runCommand("agent-browser", ["open", url]); const result = await sandbox.runCommand("agent-browser", [ "snapshot", "-i", "-c", ]); const snapshot = await result.stdout(); await sandbox.runCommand("agent-browser", ["close"]); return { ok: true, snapshot }; }); } ``` ## Sandbox snapshots Without optimization, each Sandbox run installs system dependencies + agent-browser + Chromium from scratch (~30 seconds). A **sandbox snapshot** is a saved VM image with everything pre-installed -- like a Docker image for Vercel Sandbox. When `AGENT_BROWSER_SNAPSHOT_ID` is set, the sandbox boots from that image instead of installing, bringing startup down to sub-second. This is different from an agent-browser *accessibility snapshot* (which dumps a page's accessibility tree). A sandbox snapshot is a Vercel infrastructure concept. Create a sandbox snapshot by running the helper script once: ```bash npx tsx scripts/create-snapshot.ts ``` The script spins up a fresh sandbox, installs system dependencies + agent-browser + Chromium, saves the VM state, and prints the snapshot ID: ``` AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx ``` Add this to your Vercel project environment variables (or `.env.local` for local development). Recommended for any production deployment. ## Authentication On Vercel deployments, the Sandbox SDK authenticates automatically via OIDC. For local development, provide explicit credentials:
VariableDescription
VERCEL_TOKENVercel personal access token
VERCEL_TEAM_IDVercel team ID
VERCEL_PROJECT_IDVercel project ID
When all three are set, they are passed to `Sandbox.create()`. When absent, the SDK falls back to `VERCEL_OIDC_TOKEN` (automatic on Vercel). ## Scheduled workflows (cron) For recurring tasks like daily monitoring, use Vercel Cron Jobs: ```ts // app/api/cron/monitor/route.ts export async function GET() { const result = await withBrowser(async (sandbox) => { await sandbox.runCommand("agent-browser", [ "open", "https://example.com/pricing", ]); const snap = await sandbox.runCommand("agent-browser", [ "snapshot", "-i", "-c", ]); await sandbox.runCommand("agent-browser", ["close"]); return await snap.stdout(); }); // Process results, send alerts, store data... return Response.json({ ok: true, snapshot: result }); } ``` ```json // vercel.json { "crons": [ { "path": "/api/cron/monitor", "schedule": "0 9 * * *" } ] } ``` ## Environment variables
VariableDescription
AGENT_BROWSER_SNAPSHOT_IDSandbox snapshot ID for sub-second startup (see above)
VERCEL_TOKENVercel personal access token (for local dev; OIDC is automatic on Vercel)
VERCEL_TEAM_IDVercel team ID (for local dev)
VERCEL_PROJECT_IDVercel project ID (for local dev)
## Demo app A working demo with streaming progress UI, rate limiting, and a deploy-to-Vercel button is at [`examples/environments/`](https://github.com/vercel-labs/agent-browser/tree/main/examples/environments).