Бiльшостi виробничих застосункiв потрiбна робота, яка не вписується в цикл запит/вiдповiдь: вiдправлення листiв, обробка завантажень, запуск AI-пайплайнiв, синхронiзацiя сторонiх даних, генерацiя звiтiв. Традицiйна вiдповiдь - черга (Redis, SQS, RabbitMQ), флот воркерiв, планувальник та крихка купа склеювального коду, який ламається при кожному деплої.
Trigger.dev згортає цей стек у єдиний TypeScript SDK. Ви пишете функцii, викликаєте ix звiдки завгодно, а платформа займається чергами, повторами, спостережнiстю, плануванням i довговiчним виконанням. Tasks працюють стiльки, скiльки потрiбно - жодних 10-секундних serverless-таймаутiв, жодноi втраченоi роботи при редеплоi.
Чому Trigger.dev
Зрушення 2026 року - довговiчне виконання. Воркфлоу повиннi виживати пiсля перезапускiв, крашiв, деплоiв i rate limits. Вони також повиннi стрiмити прогрес у UI у режимi реального часу та зупинятися для введення людини. Trigger.dev був перебудований навколо цих вимог у версii 3 i продовжує розширювати свою AI-iнфраструктурну поверхню.
Модель проста: ви визначаєте tasks як експорти, SDK iх пiдхоплює, платформа планує та запускає iх в iзольованих контейнерах, а стан run зберiгається, щоб ви могли вiдновити, повторити та спостерiгати.
Початок роботи
Iнiцiалiзацiя проекту
npx trigger.dev@latest login npx trigger.dev@latest init
Це створює файл trigger.config.ts i каталог trigger/ з прикладами tasks. Файл config - джерело iстини для вашого проекту: якi каталоги мiстять tasks, налаштування зборки, lifecycle hooks та опцii рантайму.
// 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-сервер пiдключається до хмари, реєструє вашi tasks та стрiмить runs через ваш локальний код. Ви ставите breakpoints у редакторi та потрапляєте у них на справжнiх тригерах - той самий цикл, який ви б використовували в будь-якому звичайному Node.js-проектi.
Визначення Task
Task - це об'єкт, який експортується з унiкальним id та функцiєю run. SDK iнспектує експорти у dirs та реєструє iх автоматично.
// 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 }; }, });
Три речi, на якi варто звернути увагу:
- У тiлi run немає таймауту. Платформа керує часом виконання через
maxDurationу config, а не у рантаймi. - Throws - це повтори. SDK ловить виключення та перезапускає з експоненцiйним backoff згiдно з полiтикою
retry. - Значення, що повертається, зберiгається. Iншi tasks та ваш фронтенд можуть читати
run.outputзвiдки завгодно.
Тригер Tasks
Ви викликаєте task з бекенду, API-роутiв або iншого 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 - використовуйте для вiдстеження або вiдображення прогресу
Опцii розблоковують багато поведiнки в одному виклику:
idempotencyKey- якщо run з тим самим ключем уже iснує, SDK повертає iснуючий handle замiсть дублювання роботи.concurrencyKey- серiалiзує runs, що подiляють ключ, щоб ви не перевищили per-tenant rate limit.queue.concurrencyLimit- глобальний cap для черги по всiх ключах.delay- планує run на майбутнiй час.ttl- якщо run не запустився до того часу, автоматично закiнчується.
Batch trigger
Для fan-out навантажень batchTrigger приймає до 500 елементiв на виклик i створює один run на елемент.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Заплановнi Tasks
Cron-задачi стають декларацiями першого класу. Сам розклад - це окремий об'єкт, який можна прикрiпити до task кiлька разiв.
// 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); }, });
Для розкладiв на кожного tenant - скажiмо, один cron на клiєнта - ви створюєте iх динамiчно через 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 робить виклик iдемпотентним: повторний запуск того самого коду пiд час деплою не накопичує дубльованi розклади.
Черги, конкурентнiсть та iдемпотентнiсть
Три примiтиви покривають бiльшiсть потреб у rate-limiting i впорядкуваннi.
Поширений патерн: одна черга на tenant з малою per-key конкурентнiстю, щоб дотримуватися rate limit вендора, плюс ключ iдемпотентностi, щоб зробити повтори безпечними.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Очiкування та довгострокова робота
Tasks можуть зупинятися, не утримуючи з'єднання чи спалюючи compute. Платформа зберiгає стан i вiдновлює функцiю, коли очiкування завершується.
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 - вбивча фiча: вона тригерить дочiрнiй task i призупиняє батькiвський, доки дочiрнiй не завершиться. Ви компонуєте tasks як async-функцii, але оркестрацiя працює довговiчно через днi або тижнi.
Human-in-the-loop з wait.forToken
Для approval-флоу та AI-гейтiв wait.forToken зупиняється, доки ваш застосунок не викличе callback з результатом.
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); }, });
Редактор вiдкриває UI, перевiряє чернетку, натискає Approve, i ваш бекенд завершує токен. Task продовжує з мiсця, де зупинився - навiть якщо пройшли години чи днi.
Lifecycle hooks
Ви можете прикрiпити init, onStart, onSuccess та onFailure до task або глобально у trigger.config.ts. Використовуйте iх для tracing, error reporting i спiльноi настройки.
// 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-контейнер при завантаженнi, а не на кожен run, тому це правильне мiсце для налаштування клiєнтiв i пулiв.
Реалтайм у фронтендi
Trigger.dev публiкує змiни стану run - status, metadata, output - через streaming API. React-хуки пiдписуються на цей потiк i автоматично перерендерюють.
// 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> ); }
Ви генеруєте публiчний access token на сторонi сервера, scoped до конкретного run, i вiдправляєте його клiєнту. Хук обробляє auth, перепiдключення та iнкрементальнi оновлення.
Для 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-агенти та стрiмiнг
Trigger.dev став популярним runtime для AI-агентiв, бо тi самi примiтиви - довговiчне виконання, повтори, очiкування, реалтайм-метаданi, human-in-the-loop - це саме те, що потрiбно агентам. Ви стрiмите токени вiд провайдера моделi у metadata, поки run вiдбувається, фронтенд рендерить iх наживо, i run переживає довгi tool calls без спалювання 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 i читає run.metadata.partial, щоб рендерити streaming-вiдповiдь, так само, як ви рендерили б chat completion - окрiм того, що цей переживе повне перезавантаження сторiнки.
Деплой
Деплоi компiлюють вашi tasks у версiйований bundle, будують контейнер i атомарно перемикають трафiк. Старi in-flight runs продовжують використовувати попередню версiю.
npx trigger.dev@latest deploy --env prod
У CI ви зазвичай пiдключаєте це до того ж 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 }} i Trigger.dev створює iзольоване середовище на кожну гiлку, вiдображаючи те, як Vercel обробляє preview deployments.
Self-Hosting vs Cloud
Trigger.dev вiдкритий пiд лiцензiєю Apache 2.0. Ви можете self-host на будь-якiй контейнернiй платформi (Docker Compose, Kubernetes, Fly.io) або використовувати кероване хмару на trigger.dev.
| Аспект | Cloud | Self-hosted |
|---|---|---|
| Setup | Реєстрацiя, запуск init | Запуск docker-compose або Helm chart |
| Scaling | Автоматичне | Ваша вiдповiдальнiсть |
| Pricing | За run + за compute | Тiльки витрати на iнфраструктуру |
| Compliance | SOC 2 | Те, що надає ваше середовище |
| Iдеально для | Бiльшостi команд | Сувороi data residency, кастомноi iнфри |
SDK i CLI iдентичнi мiж режимами - ви змiнюєте прапорець профiлю i вказуєте на свiй власний екземпляр.
Best Practices
1. Тримайте payloads маленькими та серiалiзованими
Передавайте ID i посилання, а не повнi об'єкти. Завантажуйте данi всерединi task. Це тримає чергу маленькою, payloads дешеви для логування i дозволяє змiнювати джерело даних без перетригерiв.
2. Idempotency keys на кожному зовнiшньому виклику
Поєднуйте idempotencyKey на task trigger з idempotency keys на API ваших вендорiв (Stripe, OpenAI тощо). Повтори будуть безпечними end-to-end.
3. Використовуйте triggerAndWait для оркестрацii, а не Promise.all тригерiв
Батько, який викликає triggerAndWait, довговiчно компонує дочiрнi tasks. Батько, який тригерить i зразу резолвиться, втрачає спостережнiсть ланцюга.
4. Тегуйте runs
Додайте tags до тригерiв (tags: ["user:123", "feature:onboarding"]), щоб ви могли фiльтрувати дашборд i management API за бiзнес-вимiрами.
5. Тримайте init iдемпотентним
Вiн запускається на кожному cold start. Уникайте мiграцiй або одноразових побiчних ефектiв там.
Висновок
Trigger.dev прибирає категорii роботи, для яких ранiше потрiбно було будувати job-систему з нуля. Ви пишете async TypeScript, викликаєте його звiдки завгодно, i платформа дає вам довговiчне виконання, планування, черги, повтори, реалтайм-оновлення i патерни human-in-the-loop з коробки.
Та сама поверхня, яка живить нiчний cron, - це поверхня, яка живить багатоступеневого AI-агента, що стрiмить у фронтенд i зупиняється для перегляду. Ця конвергенцiя робить фреймворк гiдним серйозного погляду у 2026 роцi, незалежно вiд того, керуєте ви SaaS, якому потрiбна надiйна фонова робота, чи поставляєте AI-фiчi, якi переживають serverless-таймаут.
Чек-лист для початку:
- Зареєструйтесь на trigger.dev або запустiть self-hosted Docker-стек
npx trigger.dev@latest initу вашому проектi- Визначте свiй перший task з
task({ id, run })- Тригерте його з вашого API i подивiться run у дашбордi
- Додайте
idempotencyKeyiconcurrencyKeyдля production-безпеки- Пiдключiть
useRealtimeRunдо status-компонента- Деплойте з
trigger.dev deploy --env prodз CI