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 reuse prepared filesystem state. A snapshot captures a running Sandbox's files and installed packages; it is distinct from the container image used to boot the Sandbox. Restoring one can skip toolchain setup or reuse a warmed package cache.
A snapshot containing one repository's node_modules will not generally accelerate an unrelated repository. Aim for reusable toolchains and package caches, or create a snapshot specifically for a stable repository.
Outcome
Update runSandboxLifecycle to create the Sandbox from a configured snapshot ID. If no ID is configured, create a default throwaway Sandbox. If a configured ID is invalid, surface the error instead of disguising every failure as a missing snapshot.
Fast Track
- Read a snapshot ID from
SANDBOX_SNAPSHOT_ID. - Restore with
source: { type: 'snapshot', snapshotId }. - Use default creation only when the env var is absent.
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_ID = process.env.SANDBOX_SNAPSHOT_ID;
async function createSandbox(): Promise<{ sandbox: Sandbox; usedSnapshot: boolean }> {
if (!SNAPSHOT_ID) {
console.warn('SANDBOX_SNAPSHOT_ID is not set; using a default Sandbox.');
const sandbox = await Sandbox.create({ persistent: false, timeout: 10 * 60 * 1000 });
return { sandbox, usedSnapshot: false };
}
const sandbox = await Sandbox.create({
source: { type: 'snapshot', snapshotId: SNAPSHOT_ID },
persistent: false,
timeout: 10 * 60 * 1000
});
return { sandbox, usedSnapshot: true };
}
export type TestResult = {
exitCode: number;
stdout: string;
stderr: string;
packageManager: string;
};
export type LifecycleResult = {
sandboxName: 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', '--depth', '1', repoUrl, 'repo']);
if (clone.exitCode !== 0) {
throw new Error(`Clone failed: ${await clone.stderr()}`);
}
const files: Array<{ path: string; content: string }> = [];
for (const fullPath of INTERESTING_PATHS) {
const content = await sandbox.readFileToBuffer({ path: fullPath });
if (content) {
files.push({
path: fullPath.replace(/^repo\//, ''),
content: content.toString('utf8')
});
}
}
const pm = await detectPackageManager(sandbox);
const install = await sandbox.runCommand(pm.install);
if (install.exitCode !== 0) {
throw new Error(`Install failed: ${await install.stderr()}`);
}
const test = await sandbox.runCommand(pm.test);
return {
sandboxName: sandbox.name,
usedSnapshot,
cloneExitCode: clone.exitCode,
files,
testResult: {
exitCode: test.exitCode,
stdout: await test.stdout(),
stderr: await test.stderr(),
packageManager: pm.name
}
};
} finally {
await sandbox.stop();
}
}Three things to flag.
The snapshot ID is configurable via SANDBOX_SNAPSHOT_ID. Snapshot IDs, rather than user-defined names, are what Sandbox.create({ source }) restores.
The fallback is only for an absent configuration. A configured but expired, deleted, or inaccessible snapshot should fail loudly so authentication and service errors are not misreported.
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."
To create one, prepare a Sandbox and call snapshot(). Snapshotting stops that Sandbox automatically:
const base = await Sandbox.create({ persistent: false });
await base.runCommand('corepack', ['enable']);
const snapshot = await base.snapshot({ expiration: 14 * 24 * 60 * 60 * 1000 });
console.log(snapshot.snapshotId);Snapshots expire after 30 days by default. Pass an explicit expiration when your retention needs differ.
Try It
Run without SANDBOX_SNAPSHOT_ID:
pnpm review https://github.com/vercel/examplesExpected output:
SANDBOX_SNAPSHOT_ID is not set; using a default Sandbox.
Reviewing https://github.com/vercel/examples...
⏱ sandbox lifecycle: 24180ms
⏱ ai analysis: 7240ms
Total: 31420msAfter exporting a valid snapshot ID and running again:
Reviewing https://github.com/vercel/examples...
⏱ sandbox lifecycle: 9420ms
⏱ ai analysis: 7180ms
Total: 16600msThe unconfigured warning is gone. Your measured improvement depends on what the snapshot contains and how closely the reviewed repo matches it. AI analysis time should remain roughly the same because snapshots do not accelerate 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({ source: { type: 'snapshot', snapshotId } })restores a configured snapshot- Default creation runs only when
SANDBOX_SNAPSHOT_IDis absent - Invalid configured snapshot errors remain visible
usedSnapshotflag is returned inLifecycleResultSANDBOX_SNAPSHOT_IDselects the snapshot to restore
Solution
const SNAPSHOT_ID = process.env.SANDBOX_SNAPSHOT_ID;
async function createSandbox(): Promise<{ sandbox: Sandbox; usedSnapshot: boolean }> {
if (!SNAPSHOT_ID) {
console.warn('SANDBOX_SNAPSHOT_ID is not set; using a default Sandbox.');
const sandbox = await Sandbox.create({ persistent: false, timeout: 10 * 60 * 1000 });
return { sandbox, usedSnapshot: false };
}
const sandbox = await Sandbox.create({
source: { type: 'snapshot', snapshotId: SNAPSHOT_ID },
persistent: false,
timeout: 10 * 60 * 1000
});
return { sandbox, usedSnapshot: true };
}Was this helpful?