vercel-sandbox
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBrowser 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 Sandbox | Serverless ( | |
|---|---|---|
| Binary size limit | None | 50MB compressed |
| Session persistence | Yes, within a sandbox lifetime | No, fresh browser per request |
| Multi-step workflows | Yes, run sequences of commands | Single request only |
| Startup time | ~30s cold, sub-second with snapshot | ~2-3s |
| Framework support | Any (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 | 无服务器方案( | |
|---|---|---|
| 二进制文件大小限制 | 无限制 | 压缩后50MB |
| 会话持久性 | 是,在沙箱生命周期内有效 | 否,每个请求启动全新浏览器 |
| 多步骤工作流 | 是,可运行命令序列 | 仅支持单个请求 |
| 启动时间 | 冷启动约30秒,使用快照后低于1秒 | 约2-3秒 |
| 框架支持 | 所有框架(Next.js、SvelteKit、Nuxt等) | Next.js(或任何Node.js无服务器环境) |
当您需要完整Chrome功能、多步骤工作流或更长执行时间时,使用Sandbox方案。当您需要快速的单请求截图/快照时,使用无服务器方案。
Dependencies
依赖项
bash
pnpm add @vercel/sandboxThe 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_xxxxxxxxxxxxA 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.tsScheduled 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
环境变量
| Variable | Required | Description |
|---|---|---|
| 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 and to get the required tokens.
vercel linkvercel env pull| 变量名 | 是否必填 | 描述 |
|---|---|---|
| 否(但推荐使用) | 预构建快照ID,用于实现亚秒级启动 |
当在Vercel上部署时,Vercel Sandbox SDK会自动处理OIDC认证。本地开发时,运行和获取所需令牌。
vercel linkvercel env pullFramework Examples
框架示例
The pattern works identically across frameworks. The only difference is where you put the server-side code:
| Framework | Server code location |
|---|---|
| Next.js | Server actions, API routes, route handlers |
| SvelteKit | |
| Nuxt | |
| Remix | |
| Astro | |
该模式在所有框架中的使用方式完全相同,唯一区别在于服务器端代码的存放位置:
| 框架 | 服务器代码位置 |
|---|---|
| Next.js | 服务器操作、API路由、路由处理器 |
| SvelteKit | |
| Nuxt | |
| Remix | |
| Astro | |
Example
示例应用
See in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a snapshot creation script and demo UI.
examples/demo/查看agent-browser仓库中的目录,获取包含Vercel Sandbox模式的可运行应用,其中包含快照创建脚本和演示UI。
examples/demo/