vercel-sandbox

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Browser Automation with Vercel Sandbox

基于Vercel Sandbox的浏览器自动化

Run 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.).
在临时Vercel Sandbox微型虚拟机中运行agent-browser + 无头Chrome。Linux虚拟机按需启动,执行浏览器命令后关闭。适用于所有Vercel部署的框架(Next.js、SvelteKit、Nuxt、Remix、Astro等)。

When to Use Sandbox vs Serverless

Sandbox方案与无服务器方案对比

Vercel SandboxServerless (
@sparticuz/chromium
)
Binary size limitNone50MB compressed
Session persistenceYes, within a sandbox lifetimeNo, fresh browser per request
Multi-step workflowsYes, run sequences of commandsSingle request only
Startup time~30s cold, sub-second with snapshot~2-3s
Framework supportAny (Next.js, SvelteKit, Nuxt, etc.)Next.js (or any Node.js serverless)
Use Sandbox when you need full Chrome, multi-step workflows, or longer execution times. Use serverless when you need fast single-request screenshots/snapshots.
Vercel Sandbox无服务器方案(
@sparticuz/chromium
二进制文件大小限制无限制压缩后50MB
会话持久性是,在沙箱生命周期内有效否,每个请求启动全新浏览器
多步骤工作流是,可运行命令序列仅支持单个请求
启动时间冷启动约30秒,使用快照后低于1秒约2-3秒
框架支持所有框架(Next.js、SvelteKit、Nuxt等)Next.js(或任何Node.js无服务器环境)
当您需要完整Chrome功能、多步骤工作流或更长执行时间时,使用Sandbox方案。当您需要快速的单请求截图/快照时,使用无服务器方案。

Dependencies

依赖项

bash
pnpm add @vercel/sandbox
The sandbox VM installs agent-browser and Chrome on first run. Use snapshots (below) to skip this step.
bash
pnpm add @vercel/sandbox
沙箱虚拟机首次运行时会安装agent-browser和Chrome。使用下文介绍的快照功能可跳过此步骤。

Core Pattern

核心模式

ts
import { Sandbox } from "@vercel/sandbox";

async function withBrowser<T>(
  fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
): Promise<T> {
  const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;

  const sandbox = snapshotId
    ? await Sandbox.create({
        source: { type: "snapshot", snapshotId },
        timeout: 120_000,
      })
    : await Sandbox.create({ runtime: "node24", timeout: 120_000 });

  if (!snapshotId) {
    await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
    await sandbox.runCommand("npx", ["agent-browser", "install"]);
  }

  try {
    return await fn(sandbox);
  } finally {
    await sandbox.stop();
  }
}
ts
import { Sandbox } from "@vercel/sandbox";

async function withBrowser<T>(
  fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
): Promise<T> {
  const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;

  const sandbox = snapshotId
    ? await Sandbox.create({
        source: { type: "snapshot", snapshotId },
        timeout: 120_000,
      })
    : await Sandbox.create({ runtime: "node24", timeout: 120_000 });

  if (!snapshotId) {
    await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
    await sandbox.runCommand("npx", ["agent-browser", "install"]);
  }

  try {
    return await fn(sandbox);
  } finally {
    await sandbox.stop();
  }
}

Screenshot

截图功能

ts
export async function screenshotUrl(url: string) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const titleResult = await sandbox.runCommand("agent-browser", [
      "get", "title", "--json",
    ]);
    const title = JSON.parse(await titleResult.stdout())?.data?.title || url;

    const ssResult = await sandbox.runCommand("agent-browser", [
      "screenshot", "--json",
    ]);
    const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";

    await sandbox.runCommand("agent-browser", ["close"]);

    return { title, screenshot };
  });
}
ts
export async function screenshotUrl(url: string) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const titleResult = await sandbox.runCommand("agent-browser", [
      "get", "title", "--json",
    ]);
    const title = JSON.parse(await titleResult.stdout())?.data?.title || url;

    const ssResult = await sandbox.runCommand("agent-browser", [
      "screenshot", "--json",
    ]);
    const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";

    await sandbox.runCommand("agent-browser", ["close"]);

    return { title, screenshot };
  });
}

Accessibility Snapshot

无障碍快照

ts
export async function snapshotUrl(url: string) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const titleResult = await sandbox.runCommand("agent-browser", [
      "get", "title", "--json",
    ]);
    const title = JSON.parse(await titleResult.stdout())?.data?.title || url;

    const snapResult = await sandbox.runCommand("agent-browser", [
      "snapshot", "-i", "-c",
    ]);
    const snapshot = await snapResult.stdout();

    await sandbox.runCommand("agent-browser", ["close"]);

    return { title, snapshot };
  });
}
ts
export async function snapshotUrl(url: string) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const titleResult = await sandbox.runCommand("agent-browser", [
      "get", "title", "--json",
    ]);
    const title = JSON.parse(await titleResult.stdout())?.data?.title || url;

    const snapResult = await sandbox.runCommand("agent-browser", [
      "snapshot", "-i", "-c",
    ]);
    const snapshot = await snapResult.stdout();

    await sandbox.runCommand("agent-browser", ["close"]);

    return { title, snapshot };
  });
}

Multi-Step Workflows

多步骤工作流

The sandbox persists between commands, so you can run full automation sequences:
ts
export async function fillAndSubmitForm(url: string, data: Record<string, string>) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const snapResult = await sandbox.runCommand("agent-browser", [
      "snapshot", "-i",
    ]);
    const snapshot = await snapResult.stdout();
    // Parse snapshot to find element refs...

    for (const [ref, value] of Object.entries(data)) {
      await sandbox.runCommand("agent-browser", ["fill", ref, value]);
    }

    await sandbox.runCommand("agent-browser", ["click", "@e5"]);
    await sandbox.runCommand("agent-browser", ["wait", "--load", "networkidle"]);

    const ssResult = await sandbox.runCommand("agent-browser", [
      "screenshot", "--json",
    ]);
    const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";

    await sandbox.runCommand("agent-browser", ["close"]);

    return { screenshot };
  });
}
沙箱在命令之间保持运行状态,因此您可以运行完整的自动化序列:
ts
export async function fillAndSubmitForm(url: string, data: Record<string, string>) {
  return withBrowser(async (sandbox) => {
    await sandbox.runCommand("agent-browser", ["open", url]);

    const snapResult = await sandbox.runCommand("agent-browser", [
      "snapshot", "-i",
    ]);
    const snapshot = await snapResult.stdout();
    // 解析快照以查找元素引用...

    for (const [ref, value] of Object.entries(data)) {
      await sandbox.runCommand("agent-browser", ["fill", ref, value]);
    }

    await sandbox.runCommand("agent-browser", ["click", "@e5"]);
    await sandbox.runCommand("agent-browser", ["wait", "--load", "networkidle"]);

    const ssResult = await sandbox.runCommand("agent-browser", [
      "screenshot", "--json",
    ]);
    const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";

    await sandbox.runCommand("agent-browser", ["close"]);

    return { screenshot };
  });
}

Snapshots (Fast Startup)

快照功能(快速启动)

Without a snapshot, the first sandbox run installs agent-browser + Chromium (~30s). Create a snapshot to make startup sub-second:
ts
import { Sandbox } from "@vercel/sandbox";

async function createSnapshot(): Promise<string> {
  const sandbox = await Sandbox.create({
    runtime: "node24",
    timeout: 300_000,
  });

  await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
  await sandbox.runCommand("npx", ["agent-browser", "install"]);

  const snapshot = await sandbox.snapshot();
  return snapshot.snapshotId;
}
Run this once, then set the environment variable:
bash
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
A helper script is available in the demo app:
bash
npx tsx examples/demo/scripts/create-snapshot.ts
不使用快照时,首次启动沙箱需要安装agent-browser + Chromium(约30秒)。创建快照可将启动时间缩短至1秒以内:
ts
import { Sandbox } from "@vercel/sandbox";

async function createSnapshot(): Promise<string> {
  const sandbox = await Sandbox.create({
    runtime: "node24",
    timeout: 300_000,
  });

  await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
  await sandbox.runCommand("npx", ["agent-browser", "install"]);

  const snapshot = await sandbox.snapshot();
  return snapshot.snapshotId;
}
运行一次此代码,然后设置环境变量:
bash
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
演示应用中提供了辅助脚本:
bash
npx tsx examples/demo/scripts/create-snapshot.ts

Scheduled Workflows (Cron)

定时工作流(Cron任务)

Combine with Vercel Cron Jobs for recurring browser tasks:
ts
// app/api/cron/route.ts  (or equivalent in your framework)
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", "schedule": "0 9 * * *" }] }
结合Vercel Cron Jobs实现周期性浏览器任务:
ts
// app/api/cron/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();
  });

  // 处理结果、发送告警、存储数据...
  return Response.json({ ok: true, snapshot: result });
}
json
// vercel.json
{ "crons": [{ "path": "/api/cron", "schedule": "0 9 * * *" }] }

Environment Variables

环境变量

VariableRequiredDescription
AGENT_BROWSER_SNAPSHOT_ID
No (but recommended)Pre-built snapshot ID for sub-second startup
The Vercel Sandbox SDK handles OIDC authentication automatically when deployed on Vercel. For local development, run
vercel link
and
vercel env pull
to get the required tokens.
变量名是否必填描述
AGENT_BROWSER_SNAPSHOT_ID
否(但推荐使用)预构建快照ID,用于实现亚秒级启动
当在Vercel上部署时,Vercel Sandbox SDK会自动处理OIDC认证。本地开发时,运行
vercel link
vercel env pull
获取所需令牌。

Framework Examples

框架示例

The pattern works identically across frameworks. The only difference is where you put the server-side code:
FrameworkServer code location
Next.jsServer actions, API routes, route handlers
SvelteKit
+page.server.ts
,
+server.ts
Nuxt
server/api/
,
server/routes/
Remix
loader
,
action
functions
Astro
.astro
frontmatter, API routes
该模式在所有框架中的使用方式完全相同,唯一区别在于服务器端代码的存放位置:
框架服务器代码位置
Next.js服务器操作、API路由、路由处理器
SvelteKit
+page.server.ts
+server.ts
Nuxt
server/api/
server/routes/
Remix
loader
action
函数
Astro
.astro
前置代码、API路由

Example

示例应用

See
examples/demo/
in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a snapshot creation script and demo UI.
查看agent-browser仓库中的
examples/demo/
目录,获取包含Vercel Sandbox模式的可运行应用,其中包含快照创建脚本和演示UI。