開発効率化

Vercel Sandbox 24時間実行入門——PRごとの長時間検証を隔離する

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

2026年6月21日
Vercel SandboxGitHub ActionsNext.jsFirestoreCI/CD
Vercel Sandbox 24時間実行入門——PRごとの長時間検証を隔離する

はじめに

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へ保存する流れを作ります。

参考にした一次情報は次の通りです。

何をSandboxへ逃がすべきか

Vercel Sandboxは、隔離されたLinux microVMでコマンド、ビルド、テストを実行する仕組みです。通常のyarn buildまで全部移す必要はありません。StockBridgeでは、検証を次の3層に分けます。

実行場所目的
Fast CIGitHub-hosted runneryarn build、型チェック、軽い単体テスト
Long CIVercel SandboxFirestore Emulator込みの結合テスト、データ移行のドライラン
DeployVercel / 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_IDVERCEL_PROJECT_IDVERCEL_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へ送ります。

実務での注意点

長時間実行の価値は「止まらないこと」だけではありません。途中経過を見られること、あとで説明できること、失敗時に再実行しやすいことが重要です。

観点設計方針
SecretsPR由来コードに本番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時間使えるから長く走らせるのではなく、長くなりがちな検証を隔離し、レビューできるログとして残すことを目的に設計しましょう。