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/sandboxServer 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 withBrowserT(
fn: (sandbox: InstanceType<typeof Sandbox>) => PromiseT,
): PromiseT {
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.tsThe 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_xxxxxxxxxxxxAdd 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:
| Variable | Description |
|---|---|
VERCEL_TOKEN | Vercel personal access token |
VERCEL_TEAM_ID | Vercel team ID |
VERCEL_PROJECT_ID | Vercel 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#
| Variable | Description |
|---|---|
AGENT_BROWSER_SNAPSHOT_ID | Sandbox snapshot ID for sub-second startup (see above) |
VERCEL_TOKEN | Vercel personal access token (for local dev; OIDC is automatic on Vercel) |
VERCEL_TEAM_ID | Vercel team ID (for local dev) |
VERCEL_PROJECT_ID | Vercel 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/.