ほとんどの本番アプリケーションには、リクエスト/レスポンスサイクルに収まらない作業が必要です:メールの送信、アップロードの処理、AI パイプラインの実行、サードパーティデータの同期、レポートの生成。従来の答えは、キュー(Redis、SQS、RabbitMQ)、ワーカーフリート、スケジューラー、そして毎回のデプロイで壊れる脆い接着コードの山です。
Trigger.dev は、そのスタックを単一の TypeScript SDK に折りたたみます。関数を書き、どこからでも呼び出すと、プラットフォームがキューイング、リトライ、可観測性、スケジューリング、永続実行を処理します。タスクは必要なだけ実行されます - 10 秒のサーバーレスタイムアウトはなく、再デプロイで作業が失われることもありません。
なぜ Trigger.dev なのか
2026 年のシフトは永続実行です。ワークフローは、再起動、クラッシュ、デプロイ、レート制限を生き延びる必要があります。また、進捗をリアルタイムで UI にストリーミングし、人間の入力のために一時停止する必要があります。Trigger.dev はバージョン 3 でこれらの要件を中心に再構築され、AI インフラストラクチャの表面を拡大し続けています。
モデルはシンプルです:タスクをエクスポートとして定義し、SDK がそれらを取得し、プラットフォームがそれらを隔離されたコンテナでスケジュールして実行し、Run 状態が永続化されるため、再開、リトライ、観察ができます。
はじめる
プロジェクトの初期化
npx trigger.dev@latest login npx trigger.dev@latest init
これにより、trigger.config.ts ファイルとサンプルタスクを含む trigger/ ディレクトリが作成されます。設定ファイルはプロジェクトの真実のソースです:どのディレクトリにタスクが含まれるか、ビルド設定、ライフサイクルフック、ランタイムオプション。
// trigger.config.ts import { defineConfig } from "@trigger.dev/sdk"; export default defineConfig({ project: "proj_abc123", runtime: "node", logLevel: "log", maxDuration: 3600, retries: { enabledInDev: true, default: { maxAttempts: 3, factor: 2, minTimeoutInMs: 1000, maxTimeoutInMs: 30_000, }, }, dirs: ["./trigger"], });
タスクをローカルで実行
npx trigger.dev@latest dev
dev サーバーはクラウドに接続し、タスクを登録し、Run をローカルコードを通じてストリーミングします。エディタにブレークポイントを設定し、実際のトリガーでヒットします - 通常の Node.js プロジェクトで使用するのと同じループです。
タスクの定義
タスクは、一意の id と run 関数を持つエクスポートされたオブジェクトです。SDK は dirs 全体のエクスポートを検査し、自動的に登録します。
// trigger/send-welcome-email.ts import { task } from "@trigger.dev/sdk"; import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); export const sendWelcomeEmail = task({ id: "send-welcome-email", retry: { maxAttempts: 5, factor: 1.8, minTimeoutInMs: 500, maxTimeoutInMs: 30_000, }, run: async (payload: { email: string; name: string }) => { const { data, error } = await resend.emails.send({ from: "hello@spinny.dev", to: payload.email, subject: `Welcome, ${payload.name}`, html: `<p>Glad you are here, ${payload.name}.</p>`, }); if (error) throw error; return { messageId: data?.id }; }, });
3 つの注目点:
- run ボディにタイムアウトはありません。 プラットフォームは、ランタイムではなく設定の
maxDurationを介して実行時間を管理します。 - Throw はリトライです。 SDK は例外をキャッチし、
retryポリシーに従って指数バックオフで再実行します。 - 戻り値は永続化されます。 他のタスクとフロントエンドはどこからでも
run.outputを読み取れます。
タスクのトリガー
バックエンド、API ルート、または別のタスクからタスクを呼び出します。
import { sendWelcomeEmail } from "@/trigger/send-welcome-email"; const handle = await sendWelcomeEmail.trigger( { email: "user@example.com", name: "Alex" }, { idempotencyKey: `welcome-${userId}`, concurrencyKey: `tenant-${tenantId}`, queue: { name: "emails", concurrencyLimit: 50 }, delay: "30s", ttl: "10m", } ); console.log(handle.id); // run_xyz - 進捗を追跡または表示するために使用
オプションは 1 回の呼び出しで多くの動作を解放します:
idempotencyKey- 同じキーの Run がすでに存在する場合、SDK は作業を複製する代わりに既存のハンドルを返します。concurrencyKey- キーを共有する Run をシリアライズし、テナントごとのレート制限を超えないようにします。queue.concurrencyLimit- すべてのキー全体でのキューのグローバル上限。delay- Run を将来の時間にスケジュールします。ttl- Run がそれまでに開始されていない場合、自動的に期限切れにします。
バッチトリガー
ファンアウトワークロードの場合、batchTrigger は呼び出しごとに最大 500 アイテムを受け取り、アイテムごとに 1 つの Run を作成します。
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
スケジュールされたタスク
Cron ジョブは第一級の宣言になります。スケジュール自体は別のオブジェクトで、タスクに何度も添付できます。
// trigger/daily-digest.ts import { schedules } from "@trigger.dev/sdk"; export const dailyDigest = schedules.task({ id: "daily-digest", cron: "0 9 * * *", run: async (payload) => { console.log("Scheduled at:", payload.timestamp); console.log("Last run:", payload.lastTimestamp); console.log("Timezone:", payload.timezone); console.log("Next 5 runs:", payload.upcoming); await sendDigestForDate(payload.timestamp); }, });
テナントごとのスケジュール(たとえば、顧客ごとに 1 つの cron)の場合、管理 API を介して動的に作成します。
import { schedules } from "@trigger.dev/sdk"; await schedules.create({ task: "daily-digest", cron: "0 9 * * *", timezone: "America/New_York", externalId: `customer_${customerId}`, deduplicationKey: `digest-${customerId}`, });
deduplicationKey は呼び出しを冪等にします:デプロイ時に同じコードを再実行しても、重複したスケジュールは積み重なりません。
キュー、並行性、冪等性
3 つのプリミティブが、ほとんどのレート制限と順序付けのニーズをカバーします。
一般的なパターン:ベンダーのレート制限を尊重するために、テナントごとに小さなキーごとの並行性を持つキューと、リトライを安全にするための冪等性キー。
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
待機と長時間実行作業
タスクは、接続を保持したり計算を消費したりせずに一時停止できます。プラットフォームは状態を永続化し、待機が完了したときに関数を再開します。
import { wait } from "@trigger.dev/sdk"; export const onboarding = task({ id: "onboarding", run: async (payload: { userId: string }) => { await sendWelcomeEmail.triggerAndWait({ userId: payload.userId }); await wait.for({ days: 1 }); await sendTipsEmail.trigger({ userId: payload.userId }); await wait.until({ date: oneWeekFromSignup(payload.userId) }); await sendUpgradeOffer.trigger({ userId: payload.userId }); }, });
triggerAndWait はキラー機能です:子タスクをトリガーし、子が完了するまで親を一時停止します。タスクを async 関数のように構成しますが、オーケストレーションは数日または数週間にわたって永続的に実行されます。
wait.forToken による Human-in-the-loop
承認フローと AI ゲートのために、wait.forToken は、アプリケーションが結果でコールバックするまで一時停止します。
import { task, wait } from "@trigger.dev/sdk"; export const publishPost = task({ id: "publish-post", run: async (payload: { draftId: string }) => { const draft = await generateAIContent(payload.draftId); const token = await wait.createToken({ timeout: "7d" }); await notifyEditor({ draftId: draft.id, token: token.id }); const decision = await wait.forToken<{ approved: boolean; notes?: string }>( token.id ); if (decision.approved) { return await publish(draft); } return await applyFeedback(draft, decision.notes); }, });
エディタは UI を開き、ドラフトをレビューし、承認をクリックすると、バックエンドがトークンを完了します。タスクは中断したところから再開します - 数時間または数日が経過していても。
ライフサイクルフック
init、onStart、onSuccess、onFailure をタスクに、またはグローバルに trigger.config.ts に添付できます。トレース、エラー報告、共有設定にこれらを使用します。
// trigger.config.ts export default defineConfig({ // ... init: async () => { Sentry.init({ dsn: process.env.SENTRY_DSN }); }, onFailure: async ({ error, ctx }) => { Sentry.captureException(error, { tags: { taskId: ctx.task.id, runId: ctx.run.id }, }); }, });
init はワーカーコンテナごとに起動時に 1 回実行され、Run ごとには実行されないため、クライアントとプールをセットアップする適切な場所です。
フロントエンドのリアルタイム
Trigger.dev は、Run 状態の変化(ステータス、メタデータ、出力)をストリーミング API で公開します。React フックはそのストリームをサブスクライブし、自動的に再レンダリングします。
// trigger/process-video.ts import { task, metadata } from "@trigger.dev/sdk"; export const processVideo = task({ id: "process-video", run: async (payload: { videoId: string }) => { metadata.set("stage", "transcoding"); await transcode(payload.videoId); metadata.set("stage", "thumbnails"); await generateThumbnails(payload.videoId); metadata.set("stage", "uploading"); const url = await uploadToCDN(payload.videoId); return { url }; }, });
// components/VideoStatus.tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; import type { processVideo } from "@/trigger/process-video"; export function VideoStatus({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun<typeof processVideo>(runId, { accessToken: publicAccessToken, }); if (error) return <p>Error: {error.message}</p>; if (!run) return <p>Loading...</p>; return ( <div> <p>Status: {run.status}</p> <p>Stage: {String(run.metadata?.stage ?? "queued")}</p> {run.output?.url && <video src={run.output.url} controls />} </div> ); }
サーバー側で特定の Run にスコープを設定したパブリックアクセストークンを生成し、クライアントに送信します。フックは認証、再接続、増分更新を処理します。
ワンショットでトリガーとサブスクライブの場合:
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks"; const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>( "process-video", { accessToken: publicAccessToken } ); <button onClick={() => submit({ videoId })} disabled={isLoading}> Process video </button>;
AI エージェントとストリーミング
Trigger.dev は AI エージェントの人気のあるランタイムになりました。同じプリミティブ(永続実行、リトライ、待機、リアルタイムメタデータ、human-in-the-loop)が、エージェントが必要とするものとまったく同じだからです。Run の進行中にモデルプロバイダーからのトークンを metadata にストリーミングし、フロントエンドはそれらをライブでレンダリングし、Run はサーバーレスタイムアウトを消費せずに長時間実行のツール呼び出しを生き延びます。
import { task, metadata } from "@trigger.dev/sdk"; import { streamText } from "ai"; import { anthropic } from "@ai-sdk/anthropic"; export const researchAgent = task({ id: "research-agent", maxDuration: 1800, run: async (payload: { question: string }) => { const result = streamText({ model: anthropic("claude-opus-4-7"), system: "You are a research assistant. Use the web.", prompt: payload.question, tools: { webSearch }, }); let fullText = ""; for await (const chunk of result.textStream) { fullText += chunk; metadata.set("partial", fullText); } return { answer: fullText, usage: await result.usage }; }, });
フロントエンドは useRealtimeRun を使用し、run.metadata.partial を読み取ってストリーミング応答をレンダリングします。チャット完了をレンダリングするのと同じ方法ですが、これはページのフルリロードを生き延びます。
デプロイ
デプロイはタスクをバージョン管理されたバンドルにコンパイルし、コンテナをビルドし、トラフィックを原子的に切り替えます。古い進行中の Run は以前のバージョンを使用し続けます。
npx trigger.dev@latest deploy --env prod
CI では通常、これをアプリを出荷するのと同じワークフローに配線します:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
プレビュー環境の場合、--env preview --branch ${{ github.head_ref }} を渡すと、Trigger.dev はブランチごとに分離された環境を作成し、Vercel がプレビューデプロイメントを処理する方法を反映します。
セルフホスティング vs クラウド
Trigger.dev は Apache 2.0 ライセンスの下でオープンソースです。任意のコンテナプラットフォーム(Docker Compose、Kubernetes、Fly.io)でセルフホスティングするか、trigger.dev でマネージドクラウドを使用できます。
| 側面 | クラウド | セルフホスト |
|---|---|---|
| セットアップ | サインアップ、init を実行 | docker-compose または Helm chart を実行 |
| スケーリング | 自動 | あなたの責任 |
| 価格 | Run ごと + 計算ごと | インフラコストのみ |
| コンプライアンス | SOC 2 | 環境が提供するもの |
| 最適 | ほとんどのチーム | 厳格なデータ常駐性、カスタムインフラ |
SDK と CLI はモード間で同一です - プロファイルフラグを変更し、自分のインスタンスにポイントします。
ベストプラクティス
1. ペイロードを小さくシリアライズ可能に保つ
完全なオブジェクトではなく、ID と参照を渡します。タスク内でデータを取得します。これにより、キューが小さく保たれ、ペイロードのログが安価になり、再トリガーせずにデータソースを変更できます。
2. すべての外部呼び出しに冪等性キー
タスクトリガーの idempotencyKey をベンダー API(Stripe、OpenAI など)の冪等性キーと組み合わせます。リトライはエンドツーエンドで安全になります。
3. オーケストレーションには triggerAndWait を使用し、トリガーの Promise.all ではない
triggerAndWait を呼び出す親は、子タスクを永続的に構成します。トリガーしてすぐに解決する親は、チェーンの可観測性を失います。
4. Run にタグを付ける
トリガーに tags を追加(tags: ["user:123", "feature:onboarding"])すると、ダッシュボードと管理 API をビジネスディメンションでフィルタリングできます。
5. init を冪等に保つ
コールドスタートのたびに実行されます。マイグレーションやワンショットの副作用をそこに入れないでください。
結論
Trigger.dev は、以前はジョブシステムをゼロから構築する必要があった作業のカテゴリを排除します。async TypeScript を書き、どこからでも呼び出すと、プラットフォームは永続実行、スケジューリング、キュー、リトライ、リアルタイム更新、human-in-the-loop パターンをすぐに提供します。
夜間 cron を駆動するのと同じ表面が、フロントエンドにストリーミングしてレビューのために一時停止する複数ステップの AI エージェントを駆動する表面です。その融合が、信頼性の高いバックグラウンド作業を必要とする SaaS を運営しているか、サーバーレスタイムアウトを超えて存続する AI 機能を出荷しているかにかかわらず、フレームワークが 2026 年に真剣に検討する価値があるものにします。
入門チェックリスト:
- trigger.dev でサインアップするか、セルフホスト Docker スタックを実行
- プロジェクトで
npx trigger.dev@latest inittask({ id, run })で最初のタスクを定義- API からトリガーし、ダッシュボードで Run を見る
- 本番安全のために
idempotencyKeyとconcurrencyKeyを追加useRealtimeRunをステータスコンポーネントに配線- CI から
trigger.dev deploy --env prodでデプロイ