רוב יישומי הייצור צריכים עבודה שלא מתאימה למחזור request/response: שליחת מיילים, עיבוד העלאות, הרצת pipelines של AI, סנכרון נתוני צד שלישי, יצירת דוחות. התשובה המסורתית היא תור (Redis, SQS, RabbitMQ), צי workers, scheduler וערימה שברירית של קוד glue שנשבר בכל deploy.
Trigger.dev מקפל את הסטאק הזה ל-SDK יחיד של TypeScript. אתה כותב פונקציות, קורא להן מכל מקום, והפלטפורמה מטפלת ב-queueing, retries, observability, scheduling וביצוע עמיד. Tasks רצים כל עוד נדרש - אין timeout serverless של 10 שניות, אין עבודה אבודה ב-redeploys.
למה Trigger.dev
השינוי ב-2026 הוא ביצוע עמיד. Workflows חייבים לשרוד restarts, crashes, deploys ו-rate limits. הם גם חייבים לשדר התקדמות ל-UI בזמן אמת ולהשהות עבור קלט אנושי. Trigger.dev נבנה מחדש סביב הדרישות האלה עם גרסה 3 וממשיך להרחיב את שטח התשתית של AI שלו.
המודל פשוט: אתה מגדיר tasks כ-exports, ה-SDK אוסף אותם, הפלטפורמה מתזמנת ומריצה אותם במיכלים מבודדים, ומצב run נשמר כדי שתוכל לחדש, לנסות שוב ולצפות.
התחלה
אתחל פרויקט
npx trigger.dev@latest login npx trigger.dev@latest init
זה יוצר קובץ trigger.config.ts וספריית trigger/ עם tasks לדוגמה. קובץ ה-config הוא מקור האמת לפרויקט שלך: אילו ספריות מכילות tasks, הגדרות build, lifecycle hooks ואפשרויות runtime.
// 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"], });
הרץ tasks מקומית
npx trigger.dev@latest dev
שרת ה-dev מתחבר לענן, רושם את ה-tasks שלך ומשדר runs דרך הקוד המקומי שלך. אתה מגדיר breakpoints בעורך שלך ופוגע בהם ב-triggers אמיתיים - אותו לולאה שתשתמש בה בכל פרויקט Node.js רגיל.
הגדרת Task
Task הוא אובייקט שמיוצא עם id ייחודי ופונקציית run. ה-SDK בודק את ה-exports ב-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 }; }, });
שלושה דברים שכדאי לציין:
- אין timeout בגוף ה-run. הפלטפורמה מנהלת את זמן הביצוע דרך
maxDurationב-config, לא ב-runtime. - Throws הם retries. ה-SDK תופס חריגים ומריץ מחדש עם exponential backoff בהתאם למדיניות
retry. - ערך ההחזרה נשמר. Tasks אחרים והפרונטאנד שלך יכולים לקרוא את
run.outputמכל מקום.
הפעלת Tasks
אתה קורא ל-task מהבקאנד שלך, ממסלולי ה-API או מ-task אחר.
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 - השתמש לעקוב או להציג את ההתקדמות
האפשרויות פותחות הרבה התנהגות בקריאה אחת:
idempotencyKey- אם run עם אותו מפתח כבר קיים, ה-SDK מחזיר את ה-handle הקיים במקום לשכפל עבודה.concurrencyKey- מסדר runs ששוטפים את המפתח כך שלא תחרוג מ-rate limit לפי tenant.queue.concurrencyLimit- מקסימום גלובלי לתור על פני כל המפתחות.delay- מתזמן את ה-run לזמן עתידי.ttl- אם ה-run לא התחיל עד אז, פוקע אוטומטית.
Batch trigger
עבור עומסי fan-out, batchTrigger מקבל עד 500 פריטים לקריאה ויוצר run אחד לפריט.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks מתוזמנים
Cron jobs הופכים להצהרות מהמעלה הראשונה. ה-schedule עצמו הוא אובייקט נפרד שאתה יכול לצרף ל-task מספר פעמים.
// 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); }, });
עבור schedules לפי tenant - נניח, cron אחד ללקוח - אתה יוצר אותם דינמית דרך management 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 הופך את הקריאה ל-idempotent: הרצה חוזרת של אותו קוד בזמן deploy לא ערימה schedules כפולים.
תורים, Concurrency ו-Idempotency
שלושה primitives מכסים את רוב הצרכים של rate-limiting וסידור.
תבנית נפוצה: תור אחד ל-tenant עם concurrency קטן לפי מפתח כדי לכבד את rate limit של ספק, בתוספת מפתח idempotency כדי להפוך את ה-retries לבטוחים.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
המתנות ועבודה לטווח ארוך
Tasks יכולים להשהות בלי להחזיק חיבור או לשרוף compute. הפלטפורמה שומרת את המצב ומחדשת את הפונקציה כשההמתנה מסתיימת.
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 היא תכונת ה-killer: היא מפעילה task ילד ומשהה את ההורה עד שהילד מסתיים. אתה מרכיב tasks כפונקציות async, אבל ה-orchestration רץ עמיד על פני ימים או שבועות.
Human-in-the-loop עם wait.forToken
עבור זרימות אישור ו-AI gates, 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, סוקר את הטיוטה, לוחץ על אישור והבקאנד שלך משלים את ה-token. ה-task ממשיך מהמקום שבו הוא הפסיק - גם אם עברו שעות או ימים.
Lifecycle Hooks
אתה יכול לצרף init, onStart, onSuccess ו-onFailure ל-task או באופן גלובלי ב-trigger.config.ts. השתמש בהם ל-tracing, error reporting והגדרה משותפת.
// 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 רץ פעם אחת ל-worker container ב-boot, לא לכל run, אז זה המקום הנכון להגדיר clients ו-pools.
Realtime בפרונטאנד
Trigger.dev מפרסם שינויי מצב run - status, metadata, output - דרך streaming API. ה-React hooks נרשמים לזרם הזה ועושים re-render אוטומטית.
// 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> ); }
אתה יוצר את ה-public access token בצד השרת, scoped ל-run ספציפי, ושולח אותו ללקוח. ה-hook מטפל ב-auth, התחברות מחדש ועדכונים מצטברים.
ל-trigger-and-subscribe בצעד אחד:
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 Agents ו-Streaming
Trigger.dev הפך ל-runtime פופולרי עבור AI agents כי אותם primitives - ביצוע עמיד, retries, המתנות, real-time metadata, human-in-the-loop - הם בדיוק מה שסוכנים צריכים. אתה משדר tokens מספק מודל ל-metadata בזמן שה-run מתרחש, הפרונטאנד מציג אותם בזמן אמת, וה-run שורד tool calls ארוכי טווח בלי לשרוף timeout serverless.
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 כדי להציג את התגובה ה-streaming, באותה דרך שהיית מציג chat completion - מלבד שזה שורד reload מלא של דף.
פריסה
הפריסות מהדרות את ה-tasks שלך ל-bundle עם גרסה, בונות מיכל ומחליפות תעבורה אטומית. runs ישנים בטיסה ממשיכים להשתמש בגרסה הקודמת.
npx trigger.dev@latest deploy --env prod
ב-CI אתה בדרך כלל מחבר את זה ל-workflow שמשלח את האפליקציה שלך:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
עבור סביבות preview, העבר --env preview --branch ${{ github.head_ref }} ו-Trigger.dev יוצר סביבה מבודדת לכל branch, משקף את הדרך שבה Vercel מטפל בפריסות preview.
Self-Hosting לעומת Cloud
Trigger.dev הוא קוד פתוח תחת רישיון Apache 2.0. אתה יכול ל-self-host על כל פלטפורמת מיכלים (Docker Compose, Kubernetes, Fly.io) או להשתמש בענן המנוהל ב-trigger.dev.
| היבט | Cloud | Self-hosted |
|---|---|---|
| Setup | הרשמה, הרצת init | הרצת docker-compose או Helm chart |
| Scaling | אוטומטי | האחריות שלך |
| Pricing | לכל run + לכל compute | רק עלות תשתית |
| Compliance | SOC 2 | מה שהסביבה שלך מספקת |
| הכי טוב ל | רוב הצוותים | data residency מחמיר, תשתית מותאמת |
ה-SDK וה-CLI זהים בין מצבים - אתה משנה דגל profile ומכוון ל-instance שלך.
Best Practices
1. שמור על payloads קטנים וניתנים לסריאליזציה
העבר IDs והפניות, לא אובייקטים מלאים. משוך נתונים בתוך ה-task. זה שומר על התור קטן, payloads זולים ל-log ומאפשר לך לשנות מקור נתונים בלי לעורר מחדש.
2. מפתחות Idempotency על כל קריאה חיצונית
שלב את idempotencyKey על trigger ה-task עם מפתחות idempotency על APIs של ספקים (Stripe, OpenAI וכו'). Retries יהיו בטוחים end-to-end.
3. השתמש ב-triggerAndWait ל-orchestration, לא ב-Promise.all של triggers
הורה שקורא ל-triggerAndWait מרכיב באופן עמיד tasks ילד. הורה ש-trigger ופותר מיד מאבד את היכולת להתבונן בשרשרת.
4. תייג runs
הוסף tags ל-triggers (tags: ["user:123", "feature:onboarding"]) כדי שתוכל לסנן את ה-dashboard ואת ה-management API לפי מימדים עסקיים.
5. שמור על init כ-idempotent
הוא רץ בכל cold start. הימנע ממיגרציות או תופעות לוואי one-shot שם.
סיכום
Trigger.dev מסיר את הקטגוריות של עבודה שדרשו פעם בניית מערכת job מאפס. אתה כותב async TypeScript, קורא לו מכל מקום, והפלטפורמה נותנת לך ביצוע עמיד, scheduling, תורים, retries, עדכונים בזמן אמת ודפוסי human-in-the-loop מחוץ לקופסה.
אותו משטח שמפעיל cron לילי הוא המשטח שמפעיל AI agent רב-שלבי שמשדר ל-frontend ומשהה לסקירה. ההתכנסות הזו היא מה שהופך את ה-framework ראוי למבט רציני ב-2026, בין אם אתה מנהל SaaS שצריך עבודת רקע אמינה או שולח תכונות AI ששורדות timeout serverless.
רשימת בדיקה להתחלה:
- הירשם ב-trigger.dev או הרץ stack Docker self-hosted
npx trigger.dev@latest initבפרויקט שלך- הגדר את ה-task הראשון שלך עם
task({ id, run })- הפעל אותו מה-API שלך וצפה ב-run ב-dashboard
- הוסף
idempotencyKeyו-concurrencyKeyלבטיחות בייצור- חבר את
useRealtimeRunלרכיב סטטוס- פרוס עם
trigger.dev deploy --env prodמ-CI