Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Run agent-browser + headless Chrome inside ephemeral Vercel Sandbox microVMs for browser automation in any Vercel-deployed app.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: vercel-sandbox3description: Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include "Vercel Sandbox browser", "microVM Chrome", "agent-browser in sandbox", "browser automation on Vercel", or any task requiring Chrome in a Vercel Sandbox.4---56# Browser Automation with Vercel Sandbox78Run agent-browser + headless Chrome inside ephemeral Vercel Sandbox microVMs. A Linux VM spins up on demand, executes browser commands, and shuts down. Works with any Vercel-deployed framework (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.).910## Dependencies1112```bash13pnpm add @vercel/sandbox14```1516The sandbox VM needs system dependencies for Chromium plus agent-browser itself. Use sandbox snapshots (below) to pre-install everything for sub-second startup.1718## Core Pattern1920```ts21import { Sandbox } from "@vercel/sandbox";2223// System libraries required by Chromium on the sandbox VM (Amazon Linux / dnf)24const CHROMIUM_SYSTEM_DEPS = [25"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",26"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",27"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",28"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",29"gtk3", "dbus-libs",30];3132function getSandboxCredentials() {33if (34process.env.VERCEL_TOKEN &&35process.env.VERCEL_TEAM_ID &&36process.env.VERCEL_PROJECT_ID37) {38return {39token: process.env.VERCEL_TOKEN,40teamId: process.env.VERCEL_TEAM_ID,41projectId: process.env.VERCEL_PROJECT_ID,42};43}44return {};45}4647async function withBrowser<T>(48fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,49): Promise<T> {50const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;51const credentials = getSandboxCredentials();5253const sandbox = snapshotId54? await Sandbox.create({55...credentials,56source: { type: "snapshot", snapshotId },57timeout: 120_000,58})59: await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 });6061if (!snapshotId) {62await sandbox.runCommand("sh", [63"-c",64`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,65]);66await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);67await sandbox.runCommand("npx", ["agent-browser", "install"]);68}6970try {71return await fn(sandbox);72} finally {73await sandbox.stop();74}75}76```7778## Screenshot7980The `screenshot --json` command saves to a file and returns the path. Read the file back as base64:8182```ts83export async function screenshotUrl(url: string) {84return withBrowser(async (sandbox) => {85await sandbox.runCommand("agent-browser", ["open", url]);8687const titleResult = await sandbox.runCommand("agent-browser", [88"get", "title", "--json",89]);90const title = JSON.parse(await titleResult.stdout())?.data?.title || url;9192const ssResult = await sandbox.runCommand("agent-browser", [93"screenshot", "--json",94]);95const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;96const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);97const screenshot = (await b64Result.stdout()).trim();9899await sandbox.runCommand("agent-browser", ["close"]);100101return { title, screenshot };102});103}104```105106## Accessibility Snapshot107108```ts109export async function snapshotUrl(url: string) {110return withBrowser(async (sandbox) => {111await sandbox.runCommand("agent-browser", ["open", url]);112113const titleResult = await sandbox.runCommand("agent-browser", [114"get", "title", "--json",115]);116const title = JSON.parse(await titleResult.stdout())?.data?.title || url;117118const snapResult = await sandbox.runCommand("agent-browser", [119"snapshot", "-i", "-c",120]);121const snapshot = await snapResult.stdout();122123await sandbox.runCommand("agent-browser", ["close"]);124125return { title, snapshot };126});127}128```129130## Multi-Step Workflows131132The sandbox persists between commands, so you can run full automation sequences:133134```ts135export async function fillAndSubmitForm(url: string, data: Record<string, string>) {136return withBrowser(async (sandbox) => {137await sandbox.runCommand("agent-browser", ["open", url]);138139const snapResult = await sandbox.runCommand("agent-browser", [140"snapshot", "-i",141]);142const snapshot = await snapResult.stdout();143// Parse snapshot to find element refs...144145for (const [ref, value] of Object.entries(data)) {146await sandbox.runCommand("agent-browser", ["fill", ref, value]);147}148149await sandbox.runCommand("agent-browser", ["click", "@e5"]);150await sandbox.runCommand("agent-browser", ["wait", "--load", "networkidle"]);151152const ssResult = await sandbox.runCommand("agent-browser", [153"screenshot", "--json",154]);155const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;156const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);157const screenshot = (await b64Result.stdout()).trim();158159await sandbox.runCommand("agent-browser", ["close"]);160161return { screenshot };162});163}164```165166## Sandbox Snapshots (Fast Startup)167168A **sandbox snapshot** is a saved VM image of a Vercel Sandbox with system dependencies + agent-browser + Chromium already installed. Think of it like a Docker image -- instead of installing dependencies from scratch every time, the sandbox boots from the pre-built image.169170This is unrelated to agent-browser's *accessibility snapshot* feature (`agent-browser snapshot`), which dumps a page's accessibility tree. A sandbox snapshot is a Vercel infrastructure concept for fast VM startup.171172Without a sandbox snapshot, each run installs system deps + agent-browser + Chromium (~30s). With one, startup is sub-second.173174### Creating a sandbox snapshot175176The snapshot must include system dependencies (via `dnf`), agent-browser, and Chromium:177178```ts179import { Sandbox } from "@vercel/sandbox";180181const CHROMIUM_SYSTEM_DEPS = [182"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",183"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",184"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",185"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",186"gtk3", "dbus-libs",187];188189async function createSnapshot(): Promise<string> {190const sandbox = await Sandbox.create({191runtime: "node24",192timeout: 300_000,193});194195await sandbox.runCommand("sh", [196"-c",197`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,198]);199await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);200await sandbox.runCommand("npx", ["agent-browser", "install"]);201202const snapshot = await sandbox.snapshot();203return snapshot.snapshotId;204}205```206207Run this once, then set the environment variable:208209```bash210AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx211```212213A helper script is available in the demo app:214215```bash216npx tsx examples/environments/scripts/create-snapshot.ts217```218219Recommended for any production deployment using the Sandbox pattern.220221## Authentication222223On Vercel deployments, the Sandbox SDK authenticates automatically via OIDC. For local development or explicit control, set:224225```bash226VERCEL_TOKEN=<personal-access-token>227VERCEL_TEAM_ID=<team-id>228VERCEL_PROJECT_ID=<project-id>229```230231These are spread into `Sandbox.create()` calls. When absent, the SDK falls back to `VERCEL_OIDC_TOKEN` (automatic on Vercel).232233## Scheduled Workflows (Cron)234235Combine with Vercel Cron Jobs for recurring browser tasks:236237```ts238// app/api/cron/route.ts (or equivalent in your framework)239export async function GET() {240const result = await withBrowser(async (sandbox) => {241await sandbox.runCommand("agent-browser", ["open", "https://example.com/pricing"]);242const snap = await sandbox.runCommand("agent-browser", ["snapshot", "-i", "-c"]);243await sandbox.runCommand("agent-browser", ["close"]);244return await snap.stdout();245});246247// Process results, send alerts, store data...248return Response.json({ ok: true, snapshot: result });249}250```251252```json253// vercel.json254{ "crons": [{ "path": "/api/cron", "schedule": "0 9 * * *" }] }255```256257## Environment Variables258259| Variable | Required | Description |260|---|---|---|261| `AGENT_BROWSER_SNAPSHOT_ID` | No (but recommended) | Pre-built sandbox snapshot ID for sub-second startup (see above) |262| `VERCEL_TOKEN` | No | Vercel personal access token (for local dev; OIDC is automatic on Vercel) |263| `VERCEL_TEAM_ID` | No | Vercel team ID (for local dev) |264| `VERCEL_PROJECT_ID` | No | Vercel project ID (for local dev) |265266## Framework Examples267268The pattern works identically across frameworks. The only difference is where you put the server-side code:269270| Framework | Server code location |271|---|---|272| Next.js | Server actions, API routes, route handlers |273| SvelteKit | `+page.server.ts`, `+server.ts` |274| Nuxt | `server/api/`, `server/routes/` |275| Remix | `loader`, `action` functions |276| Astro | `.astro` frontmatter, API routes |277278## Example279280See `examples/environments/` in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a sandbox snapshot creation script, streaming progress UI, and rate limiting.281