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#

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.

"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​​T​​(
  fn: (sandbox: InstanceType<typeof Sandbox>) => Promise​​T​​,
): Promise​​T​​ {
  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:

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:

// 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 });
}
// 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/.