Vercel Logo

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

  1. Add a SNAPSHOT_NAME constant (or read from env).
  2. Try Sandbox.create({ snapshot: SNAPSHOT_NAME }) first.
  3. 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.

Troubleshooting: fallback path is still slow

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.

Troubleshooting: snapshot creation isn't free either

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/examples

Expected output:

Snapshot "repo-review-base" unavailable; falling back to default creation. ...
Reviewing https://github.com/vercel/examples...
  ⏱  sandbox lifecycle: 24180ms
  ⏱  ai analysis: 7240ms

Total: 31420ms

After you've set up the snapshot and the second run:

Reviewing https://github.com/vercel/examples...
  ⏱  sandbox lifecycle: 9420ms
  ⏱  ai analysis: 7180ms

Total: 16600ms

The "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
  • usedSnapshot flag is returned in LifecycleResult
  • SANDBOX_SNAPSHOT env var overrides the default name

Solution

src/sandbox-lifecycle.ts (helper)
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?

supported.