Vercel Sandbox 24時間実行入門——PRごとの長時間検証を隔離する
Vercel Sandboxの24時間実行化を題材に、Next.js + Firestore + GitHub Actionsで長時間検証ジョブを安全に分離し、監査ログへ残す実践手順を解説します。

はじめに
PRの検証で、こんな経験はありませんか?
| 状況 | 困りごと |
|---|---|
| Firestore Emulatorを使った結合テストが長い | 通常CIの制限時間やrunner占有が気になる |
| AIエージェントが大きめの修正PRを作る | 生成コードを手元や本番に近い環境でいきなり動かしたくない |
| データ移行スクリプトをレビューしたい | 実行ログと成果物を後から追える場所に残したい |
2026年6月16日、Vercelは Vercel Sandboxが最大24時間の連続実行に対応した と発表しました。従来の5時間から伸びたことで、大規模データ処理、E2Eテスト、長時間のagentic workflowを途中で切らずに扱いやすくなっています。公式Changelogでは、Sandbox.create({ timeout: 24 * 60 * 60 * 1000 }) のようにミリ秒でtimeoutを指定する例も示されています。
本記事では、架空の在庫管理SaaS「StockBridge」を題材に、Next.js + Firestore + GitHub Actions + TypeScriptの現場で、PRごとの長時間検証をVercel Sandboxへ逃がし、結果をFirestoreへ保存する流れを作ります。
参考にした一次情報は次の通りです。
- Vercel Sandbox can now run for up to 24 hours
- Vercel Sandbox Quickstart
- Sandbox SDK Reference
- Sandbox Authentication
何をSandboxへ逃がすべきか
Vercel Sandboxは、隔離されたLinux microVMでコマンド、ビルド、テストを実行する仕組みです。通常のyarn buildまで全部移す必要はありません。StockBridgeでは、検証を次の3層に分けます。
| 層 | 実行場所 | 目的 |
|---|---|---|
| Fast CI | GitHub-hosted runner | yarn build、型チェック、軽い単体テスト |
| Long CI | Vercel Sandbox | Firestore Emulator込みの結合テスト、データ移行のドライラン |
| Deploy | Vercel / Firebase Hosting | レビュー後のPreviewまたはProduction反映 |
ポイントは、PRごとの「重い検証」を本番環境や固定runnerから切り離すことです。24時間まで伸びたからといって、すべてのCIを長くするのではなく、時間が読みにくい処理だけを明示的にSandboxへ寄せます。
事前準備
公式Quickstartでは、TypeScript SDKとして @vercel/sandbox を使います。ローカル開発ではVercelプロジェクトに紐づけ、OIDC tokenを取得する流れが推奨されています。
yarn add @vercel/sandbox dotenv tsx typescript @types/node firebase-admin
vercel link
vercel env pull
vercel env pull で作られる .env.local には VERCEL_OIDC_TOKEN が入り、ローカルではこのトークンをSDKが使います。GitHub Actionsのような外部CIではOIDC tokenが自動では使えないため、公式ドキュメントに従って VERCEL_TEAM_ID、VERCEL_PROJECT_ID、VERCEL_TOKEN をSecretsとして渡す設計にします。
ハンズオン1: 24時間Sandboxを作る
まずは、PR番号と対象コミットを受け取ってSandboxを作成するスクリプトを用意します。ここではSandboxの実行結果をFirestoreへ記録するため、作成時点で監査ドキュメントも作ります。
// scripts/run-long-ci-sandbox.ts
import { Sandbox } from "@vercel/sandbox";
import { applicationDefault, getApps, initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
if (getApps().length === 0) {
initializeApp({ credential: applicationDefault() });
}
const db = getFirestore();
const pullRequestNumber = process.env.PR_NUMBER;
const headSha = process.env.HEAD_SHA;
if (!pullRequestNumber || !headSha) {
throw new Error("PR_NUMBER and HEAD_SHA are required");
}
const sandbox = await Sandbox.create({
teamId: process.env.VERCEL_TEAM_ID,
projectId: process.env.VERCEL_PROJECT_ID,
token: process.env.VERCEL_TOKEN,
runtime: "node22",
timeout: 24 * 60 * 60 * 1000,
});
const auditRef = db.collection("longCiRuns").doc(`${pullRequestNumber}-${headSha}`);
await auditRef.set({
pullRequestNumber: Number(pullRequestNumber),
headSha,
sandboxId: sandbox.sandboxId,
status: "running",
createdAt: FieldValue.serverTimestamp(),
});
runtime: "node22" はSDK Referenceに記載されているruntime指定の一例です。StockBridgeのNext.js 16系プロジェクトでは、通常CIと同じNode.jsメジャーに寄せると差分調査が楽になります。
ハンズオン2: コマンドを実行してログを保存する
次に、Sandbox内で検証コマンドを実行します。公式Quickstartでは sandbox.runCommand("echo", ["Hello from Vercel Sandbox!"]) の形が示されています。複数コマンドを扱う場合も、終了コードと標準出力を必ず保存します。
const commands: Array<{ cmd: string; args: string[] }> = [
{ cmd: "node", args: ["--version"] },
{ cmd: "yarn", args: ["--version"] },
{ cmd: "yarn", args: ["build"] },
{ cmd: "yarn", args: ["test:integration"] },
];
const results = [];
for (const command of commands) {
const startedAt = Date.now();
const result = await sandbox.runCommand(command.cmd, command.args);
const stdout = await result.stdout();
const stderr = await result.stderr();
results.push({
...command,
exitCode: result.exitCode,
durationMs: Date.now() - startedAt,
stdout: stdout.slice(-4000),
stderr: stderr.slice(-4000),
});
if (result.exitCode !== 0) {
break;
}
}
const failed = results.find((result) => result.exitCode !== 0);
await auditRef.update({
status: failed ? "failed" : "passed",
results,
finishedAt: FieldValue.serverTimestamp(),
});
実際のプロジェクトでは、リポジトリのコピー方法を先に決めます。公開リポジトリならSandbox内でcloneできますが、非公開リポジトリではGitHub tokenの扱いが論点になります。未レビューのPRコードへ強いSecretsを渡さないため、最初はGitHub Actions側で必要な検証対象だけをアーカイブし、Sandbox CLIの sandbox cp でコピーする方が設計を読みやすくできます。
ハンズオン3: GitHub Actionsから起動する
GitHub Actionsでは、通常CIを通した後に長時間検証を手動またはラベル付きで動かします。すべてのPRで24時間枠を使うとコストも待ち時間も読みにくくなるため、long-ci ラベルを条件にします。
name: Long CI in Vercel Sandbox
on:
pull_request:
types: [labeled, synchronize]
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
permissions:
contents: read
pull-requests: read
jobs:
sandbox:
if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'long-ci')
runs-on: ubuntu-latest
timeout-minutes: 1440
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- run: yarn install --frozen-lockfile
- name: Prepare Google credentials
run: |
printf '%s' "$GOOGLE_APPLICATION_CREDENTIALS_JSON" > "$RUNNER_TEMP/google-credentials.json"
echo "GOOGLE_APPLICATION_CREDENTIALS=$RUNNER_TEMP/google-credentials.json" >> "$GITHUB_ENV"
env:
GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
- name: Run long CI in Sandbox
run: yarn tsx scripts/run-long-ci-sandbox.ts
env:
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
このworkflow自体の timeout-minutes も長めにしていますが、実務では24時間いっぱい待つより、検証単位を3〜4本に分けて早く失敗を見つける方が有効です。たとえば「Firestore Rules」「在庫引当ロジック」「月次CSV取込」「AI補正候補」のように分け、必要なものだけSandboxへ送ります。
実務での注意点
長時間実行の価値は「止まらないこと」だけではありません。途中経過を見られること、あとで説明できること、失敗時に再実行しやすいことが重要です。
| 観点 | 設計方針 |
|---|---|
| Secrets | PR由来コードに本番Firestoreや本番外部APIの権限を渡さない |
| ログ | stdout/stderrをFirestoreへ保存する。ただし個人情報や巨大ログは末尾だけ残す |
| コスト | long-ci ラベルや手動実行に限定し、通常PRの既定にはしない |
| 再現性 | Node.js、Yarn、環境変数、対象SHAを監査ドキュメントに残す |
| 分割 | 24時間枠に甘えず、失敗しやすい検証から順に実行する |
Vercel Sandbox SDKには sandbox.snapshot() や sandbox.extendTimeout() も用意されています。依存関係のインストールに時間がかかる場合は、セットアップ済み状態をsnapshot化して次回以降に使う設計も検討できます。ただしsnapshotは状態を持つため、PR間で混ぜてよいものと、必ず作り直すべきものを分ける必要があります。
まとめ
Vercel Sandboxの24時間実行化は、単に「長いCIが動く」ニュースではありません。AIエージェントが作る大きめのPR、Firestore Emulator込みの結合テスト、データ移行のドライランを、通常CIや本番環境から切り離す選択肢が増えたという意味があります。
Next.js + Firestoreの業務アプリでは、Fast CIで素早く落とし、必要なPRだけLong CIとしてSandboxへ送り、結果をFirestoreへ残す形が扱いやすいです。24時間使えるから長く走らせるのではなく、長くなりがちな検証を隔離し、レビューできるログとして残すことを目的に設計しましょう。