fix environments demo (#696)

* fix

* fixes

* fixes

* update docs

* fixes

* fixes

* sandbox tokens

* better logging
This commit is contained in:
Chris Tate
2026-03-09 17:00:30 -05:00
committed by GitHub
parent c0a525c9e4
commit 5bf9fedd58
42 changed files with 1366 additions and 1850 deletions
-189
View File
@@ -1,189 +0,0 @@
---
name: next
description: Run headless Chrome in Next.js serverless functions using @sparticuz/chromium + puppeteer-core. Use when the user needs browser automation from a Next.js app, wants to take screenshots or snapshots from server actions or API routes, or is building a Next.js app that needs headless Chrome. Triggers include "screenshot from Next.js", "headless Chrome in serverless", "browser automation in Next.js", "puppeteer on Vercel", or any task requiring Chrome in a Next.js server context.
---
# Browser Automation in Next.js Serverless Functions
Run headless Chrome directly inside Next.js server actions and API routes using `@sparticuz/chromium` + `puppeteer-core`. No external server needed -- Chrome runs in the same serverless function.
## Dependencies
```bash
pnpm add @sparticuz/chromium puppeteer-core
```
## Core Pattern
```ts
import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium";
import fs from "node:fs";
const CHROME_PATHS = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
];
function findLocalChrome(): string {
for (const p of CHROME_PATHS) {
if (fs.existsSync(p)) return p;
}
throw new Error(
`Chrome not found. Set CHROMIUM_PATH to your Chrome/Chromium binary.`,
);
}
async function launchBrowser() {
const isLambda =
!!process.env.VERCEL || !!process.env.AWS_LAMBDA_FUNCTION_NAME;
const executablePath = isLambda
? await chromium.executablePath()
: process.env.CHROMIUM_PATH || findLocalChrome();
const args = isLambda
? chromium.args
: ["--no-sandbox", "--disable-setuid-sandbox"];
return puppeteer.launch({
args,
executablePath,
headless: true,
defaultViewport: { width: 1280, height: 720 },
});
}
```
On Vercel, `@sparticuz/chromium` bundles a compatible Chromium binary automatically. Locally, the launcher falls back to the system Chrome installation or `CHROMIUM_PATH`.
## Server Actions
### Screenshot
```ts
"use server";
export async function takeScreenshot(url: string) {
const browser = await launchBrowser();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle2", timeout: 30_000 });
const title = await page.title();
const screenshot = await page.screenshot({
fullPage: true,
encoding: "base64",
});
return { ok: true, title, screenshot: screenshot as string };
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : String(err) };
} finally {
await browser.close();
}
}
```
### Accessibility Snapshot
```ts
"use server";
export async function takeSnapshot(url: string) {
const browser = await launchBrowser();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle2", timeout: 30_000 });
const title = await page.title();
const snapshot = await page.accessibility.snapshot();
return { ok: true, title, snapshot: JSON.stringify(snapshot, null, 2) };
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : String(err) };
} finally {
await browser.close();
}
}
```
## API Routes
```ts
// app/api/browse/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { url, action } = await req.json();
if (!url) {
return NextResponse.json({ error: "Provide a 'url'" }, { status: 400 });
}
const browser = await launchBrowser();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle2", timeout: 30_000 });
if (action === "screenshot") {
const screenshot = await page.screenshot({ encoding: "base64" });
return NextResponse.json({ screenshot });
}
if (action === "snapshot") {
const snapshot = await page.accessibility.snapshot();
return NextResponse.json({ snapshot });
}
return NextResponse.json(
{ error: "action must be 'screenshot' or 'snapshot'" },
{ status: 400 },
);
} finally {
await browser.close();
}
}
```
## Environment Variables
| Variable | Required | Description |
|---|---|---|
| `CHROMIUM_PATH` | Local dev only | Path to Chrome/Chromium binary. Not needed on Vercel. |
On Vercel, `@sparticuz/chromium` auto-detects the bundled binary. Locally, if Chrome is not in a standard location, set `CHROMIUM_PATH`.
## Vercel Configuration
The `@sparticuz/chromium` binary is large (~50MB). Increase the serverless function's memory and timeout if needed:
```ts
// next.config.ts
const nextConfig = {
serverExternalPackages: ["@sparticuz/chromium"],
};
export default nextConfig;
```
If the project lives in a monorepo subdirectory, set `outputFileTracingRoot` so the Chromium binary is included in the deployment:
```ts
import path from "node:path";
const nextConfig = {
outputFileTracingRoot: path.join(import.meta.dirname, "../../"),
serverExternalPackages: ["@sparticuz/chromium"],
};
export default nextConfig;
```
## Limitations
- Vercel serverless functions have a 50MB compressed size limit. `@sparticuz/chromium` fits within this but leaves limited room for other large dependencies.
- Function execution timeout is 10s on Hobby, 300s on Pro. Complex page loads may need the Pro plan.
- Each invocation launches a fresh browser. There is no session persistence between requests.
- For workflows that need persistent sessions, longer timeouts, or full Chrome (no size limits), use the Vercel Sandbox pattern instead (see the `vercel-sandbox` skill).
## Example
See `examples/demo/` in the agent-browser repo for a working app with both serverless and sandbox patterns, and a deploy-to-Vercel button.
+14 -18
View File
@@ -7,25 +7,13 @@ description: Run agent-browser + Chrome inside Vercel Sandbox microVMs for brows
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.).
## When to Use Sandbox vs Serverless
| | Vercel Sandbox | Serverless (`@sparticuz/chromium`) |
|---|---|---|
| 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.
## Dependencies
```bash
pnpm add @vercel/sandbox
```
The sandbox VM installs agent-browser and Chrome on first run. Use snapshots (below) to skip this step.
The sandbox VM installs agent-browser and Chrome on first run. Use sandbox snapshots (below) to skip this step.
## Core Pattern
@@ -139,9 +127,15 @@ export async function fillAndSubmitForm(url: string, data: Record<string, string
}
```
## Snapshots (Fast Startup)
## Sandbox Snapshots (Fast Startup)
Without a snapshot, the first sandbox run installs agent-browser + Chromium (~30s). Create a snapshot to make startup sub-second:
A **sandbox snapshot** is a saved VM image of a Vercel Sandbox with 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.
This 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.
Without a sandbox snapshot, each run installs agent-browser + Chromium (~30s). With one, startup is sub-second.
### Creating a sandbox snapshot
```ts
import { Sandbox } from "@vercel/sandbox";
@@ -169,9 +163,11 @@ AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
A helper script is available in the demo app:
```bash
npx tsx examples/demo/scripts/create-snapshot.ts
npx tsx examples/environments/scripts/create-snapshot.ts
```
Recommended for any production deployment using the Sandbox pattern.
## Scheduled Workflows (Cron)
Combine with Vercel Cron Jobs for recurring browser tasks:
@@ -200,7 +196,7 @@ export async function GET() {
| Variable | Required | Description |
|---|---|---|
| `AGENT_BROWSER_SNAPSHOT_ID` | No (but recommended) | Pre-built snapshot ID for sub-second startup |
| `AGENT_BROWSER_SNAPSHOT_ID` | No (but recommended) | Pre-built sandbox snapshot ID for sub-second startup (see above) |
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.
@@ -218,4 +214,4 @@ The pattern works identically across frameworks. The only difference is where yo
## 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.
See `examples/environments/` in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a sandbox snapshot creation script and demo UI.