Use Snapshots to Skip the Cold Start
The benchmark numbers from 5.1 told us where the time goes. Most of it lives inside pnpm install, which makes sense because cold Sandboxes start with no node_modules and have to fetch everything from scratch.
Snapshots are how we cheat. A snapshot is a pre-built Sandbox image that already has whatever you put in it (a global pnpm install, common system tools, a cached node_modules for a popular framework). Boot from a snapshot, and you skip all the work that went into building it.
Outcome
Update runSandboxLifecycle to create the Sandbox from a named snapshot, and fall back to default creation with a warning when the snapshot doesn't exist.
Fast Track
- Add a
SNAPSHOT_NAMEconstant (or read from env). - Try
Sandbox.create({ snapshot: SNAPSHOT_NAME })first. - Catch the missing-snapshot error and fall back to plain
Sandbox.create().
Hands-on exercise
Open src/sandbox-lifecycle.ts. We're going to extract Sandbox creation into a small helper:
import { Sandbox } from '@vercel/sandbox';
import { detectPackageManager } from './test-runner';
const INTERESTING_PATHS = [
'repo/package.json',
'repo/src/index.ts',
'repo/src/app.ts',
'repo/lib/auth.ts'
];
const SNAPSHOT_NAME = process.env.SANDBOX_SNAPSHOT ?? 'repo-review-base';
async function createSandbox(): Promise<{ sandbox: Sandbox; usedSnapshot: boolean }> {
try {
const sandbox = await Sandbox.create({ snapshot: SNAPSHOT_NAME });
return { sandbox, usedSnapshot: true };
} catch (error) {
console.warn(
`Snapshot "${SNAPSHOT_NAME}" unavailable; falling back to default creation.`,
error instanceof Error ? error.message : error
);
const sandbox = await Sandbox.create();
return { sandbox, usedSnapshot: false };
}
}
export type TestResult = {
exitCode: number;
stdout: string;
stderr: string;
packageManager: string;
};
export type LifecycleResult = {
sandboxId: string;
usedSnapshot: boolean;
cloneExitCode: number;
files: Array<{ path: string; content: string }>;
testResult: TestResult;
};
export async function runSandboxLifecycle(repoUrl: string): Promise<LifecycleResult> {
const { sandbox, usedSnapshot } = await createSandbox();
try {
const clone = await sandbox.runCommand(`git clone ${repoUrl} repo`);
if (clone.exitCode !== 0) {
throw new Error(`Clone failed: ${clone.stderr}`);
}
const files: Array<{ path: string; content: string }> = [];
for (const fullPath of INTERESTING_PATHS) {
try {
const content = await sandbox.readFile(fullPath);
files.push({
path: fullPath.replace(/^repo\//, ''),
content
});
} catch {
// file doesn't exist in this repo; skip it
}
}
const pm = await detectPackageManager(sandbox);
await sandbox.runCommand(pm.install);
const test = await sandbox.runCommand(pm.test);
return {
sandboxId: sandbox.sandboxId,
usedSnapshot,
cloneExitCode: clone.exitCode,
files,
testResult: {
exitCode: test.exitCode,
stdout: test.stdout,
stderr: test.stderr,
packageManager: pm.name
}
};
} finally {
await sandbox.stop();
}
}Three things to flag.
The snapshot name is configurable via env (SANDBOX_SNAPSHOT) with a sensible default. This lets you experiment with different snapshots without touching the code.
The fallback is loud, not silent. If a snapshot disappears or the name changes, you want to know about it. A warning in the logs makes the cause obvious next time you wonder why the pipeline got slow again.
The usedSnapshot flag flows back through the result so the CLI (or the reporter we build in 5.4) can show "snapshot accelerated" in the summary. Knowing whether the snapshot path was actually taken is the difference between "we have snapshots" and "snapshots actually help."
We're not covering how to create a snapshot in this course. That's a one-time setup step that depends on what you want pre-baked. Check the @vercel/sandbox docs for the snapshot creation API.
If the warning prints and the run still takes the full cold-start time, the fallback worked but the snapshot didn't help. That's expected the first time you run after the snapshot disappears. Recreate the snapshot to restore the speedup.
The first time you create a snapshot, you pay the full cost. The next 100 runs amortize that. If you only ever run the pipeline once, snapshots aren't worth it.
Try It
First run (or no snapshot exists):
pnpm review https://github.com/vercel/examplesExpected output:
Snapshot "repo-review-base" unavailable; falling back to default creation. ...
Reviewing https://github.com/vercel/examples...
⏱ sandbox lifecycle: 24180ms
⏱ ai analysis: 7240ms
Total: 31420msAfter you've set up the snapshot and the second run:
Reviewing https://github.com/vercel/examples...
⏱ sandbox lifecycle: 9420ms
⏱ ai analysis: 7180ms
Total: 16600msThe "snapshot unavailable" warning is gone, and the lifecycle stage dropped from ~24s to ~9s. Same review, ~half the time. The AI analysis stayed roughly the same because the snapshot doesn't help with model calls.
Commit
git add src/sandbox-lifecycle.ts
git commit -m "feat(sandbox): create from named snapshot with graceful fallback"Done-When
Sandbox.create({ snapshot })is attempted first- Missing-snapshot error is caught and logged as a warning
- Default creation runs as a fallback
usedSnapshotflag is returned inLifecycleResultSANDBOX_SNAPSHOTenv var overrides the default name
Solution
const SNAPSHOT_NAME = process.env.SANDBOX_SNAPSHOT ?? 'repo-review-base';
async function createSandbox(): Promise<{ sandbox: Sandbox; usedSnapshot: boolean }> {
try {
const sandbox = await Sandbox.create({ snapshot: SNAPSHOT_NAME });
return { sandbox, usedSnapshot: true };
} catch (error) {
console.warn(
`Snapshot "${SNAPSHOT_NAME}" unavailable; falling back to default creation.`,
error instanceof Error ? error.message : error
);
const sandbox = await Sandbox.create();
return { sandbox, usedSnapshot: false };
}
}Was this helpful?