Vercel Workflows(GA)入門——TypeScriptで「落ちても再開する」長期実行処理を書く
GAしたVercel Workflowsで耐久実行(durable execution)をTypeScriptで実装する方法を解説。use workflow / use stepディレクティブの使い方を、架空のSaaSのAI記事生成パイプラインを題材にハンズオンで構築します。

はじめに
長時間かかるバックエンド処理を書いていて、こんな悩みはありませんか?
- 外部API呼び出しの途中で関数がタイムアウトし、最初からやり直しになる
- 複数ステップのバッチ処理で、3ステップ目で失敗すると1・2もやり直す羽目になる
- 「数分待ってから次の処理」を実装したいが、サーバーレスでは関数を寝かせ続けられない
こうした「途中で落ちても続きから再開したい」処理を素直に書けるのが、GAした Vercel Workflows の耐久実行(durable execution)です。
本記事を読み終えると、次のことができるようになります。
- 耐久実行が解決する課題を理解する
use workflow/use stepディレクティブの基本を書ける- 架空のSaaSのAI記事生成パイプラインを、再開可能な形で組む
耐久実行(durable execution)とは
耐久実行とは、ワークフローの 各ステップの結果を永続化 し、途中で失敗しても完了済みステップをスキップして続きから再開する仕組みです。
通常のサーバーレス関数は「実行 → 終了」で状態が消えますが、耐久実行は ステップ単位で結果を覚えている ため、リトライが圧倒的に安全になります。
ハンズオン1: 最小のワークフローを書く
Vercel WorkflowsはTypeScriptでは "use workflow" ディレクティブで関数をワークフロー化します。架空のSaaS「ZaikoFlow」で、新商品の説明文をAIで生成して保存するワークフローを作ってみましょう。
まずはワークフロー本体です。
// src/workflows/generateDescription.ts
export async function generateDescription(input: { productId: string }) {
"use workflow";
const product = await fetchProduct(input.productId);
const draft = await generateDraft(product.name);
await saveDescription(input.productId, draft);
return { productId: input.productId, status: "done" };
}
各ステップは "use step" ディレクティブで分離します。これにより、ステップ単位で結果が永続化されます。
// 個別ステップ("use step" で永続化単位になる)
async function fetchProduct(productId: string) {
"use step";
return db.product.findUnique({ where: { id: productId } });
}
async function generateDraft(name: string) {
"use step";
// 外部のAI APIを呼び出して説明文を生成
return callAiApi(`商品「${name}」の説明文を生成`);
}
async function saveDescription(productId: string, text: string) {
"use step";
return db.product.update({
where: { id: productId },
data: { description: text },
});
}
ポイントは、generateDraft(AI呼び出し)が失敗してリトライされても、すでに完了している fetchProduct の結果は再利用される点です。重い処理や課金が発生する処理ほど、この恩恵が大きくなります。
ハンズオン2: ワークフローを起動する
ワークフローは start() で起動し、run IDを受け取ります。たとえば商品登録のServer Actionから起動するイメージです。
// src/app/actions/registerProduct.ts
import { start } from "workflow";
import { generateDescription } from "@/workflows/generateDescription";
export async function registerProduct(productId: string) {
// ワークフローを開始し、実行IDを取得
const run = await start(generateDescription, [{ productId }]);
return { runId: run.runId };
}
実行状況は、CLIで検査できます。
# 特定のワークフロー実行を検査する
npx workflow inspect runs <wrun_id>
<wrun_id> には start() が返した run ID を渡します。どのステップまで完了しているか、どこで失敗したかを追えるため、デバッグが格段にやりやすくなります。
ハンズオン3: sleepで「待ってから続ける」
耐久実行ならではの機能が sleep() です。分単位から月単位まで、ワークフローを一時停止して後で再開できます。サーバーレスで関数を寝かせ続ける必要がありません。
架空の例として、「商品登録の3日後にレビュー依頼通知を送る」フローを考えます。
// src/workflows/reviewReminder.ts
import { sleep } from "workflow";
export async function reviewReminder(input: { orderId: string }) {
"use workflow";
await markOrderShipped(input.orderId);
// 3日間スリープしてから再開(その間サーバーは何も保持しない)
await sleep("3 days");
await sendReviewRequest(input.orderId);
}
この sleep("3 days") の間、実行リソースを消費し続けることはありません。スリープが明けると、ワークフローは続きから再開します。
全体設計: どんな処理を耐久実行にすべきか
すべての処理をワークフロー化する必要はありません。耐久実行が向くのは次のような処理です。
| 向いている処理 | 理由 |
|---|---|
| 複数ステップの外部API連携 | 途中失敗のリトライコストが高い |
| AI生成パイプライン | 1ステップが高価・低速 |
| 「N日後に実行」系の処理 | sleepで素直に書ける |
| 決済・在庫など整合性が重要な処理 | ステップ単位の確実な進行が必要 |
逆に、単発の軽いCRUDは通常のServer Actionで十分です。「失敗したら最初からやり直すのが痛い処理」かどうか が判断基準になります。
Tips・注意点
- ステップは冪等に書く: リトライ前提なので、各ステップは何度実行しても結果が変わらない(冪等)ように設計するのが安全です。
- Python SDKはベータ: TypeScriptに加えてPython SDK(
from vercel.workflow import Workflows形式)も提供されていますが、ベータ版である点に注意してください。 - AI連携用のユーティリティ:
@workflow/ai系のパッケージにはAIエージェント向けの実装(DurableAgentなど)が含まれます。AIワークフローを組むなら公式ドキュメントを確認しましょう。 - 正確なAPIは公式で確認: 本記事の関数名・ディレクティブは公式ブログに基づいていますが、最新の仕様はVercel公式ブログとドキュメントで確認してください。
まとめ
Vercel Workflowsの要点を整理します。
- 耐久実行 は各ステップの結果を永続化し、失敗しても続きから再開できる
- TypeScriptでは
"use workflow"でワークフロー、"use step"でステップを宣言する start()で起動してrun IDを取得し、npx workflow inspect runsで実行を追えるsleep()でリソースを保持せず「N日後に再開」が書ける- 向くのは「失敗時のやり直しが痛い」マルチステップ処理
次のアクションとして、まずは手元のバッチ処理のうち「途中で落ちると痛い」ものを1つ選び、"use step" で分割してみてください。リトライの安心感が、耐久実行の価値をいちばん実感させてくれます。