Kebanyakan aplikasi pengeluaran memerlukan kerja yang tidak sesuai dengan kitaran request/response: menghantar e-mel, memproses muat naik, menjalankan pipeline AI, menyegerakkan data pihak ketiga, menjana laporan. Jawapan tradisional ialah satu queue (Redis, SQS, RabbitMQ), satu armada worker, satu scheduler, dan satu timbunan kod glue rapuh yang pecah pada setiap deploy.
Trigger.dev memampatkan stack itu menjadi satu TypeScript SDK. Anda menulis fungsi, memanggilnya dari mana-mana, dan platform mengendalikan queueing, retries, observability, scheduling, dan pelaksanaan tahan lasak. Tasks berjalan selama yang diperlukan - tiada timeout serverless 10 saat, tiada kerja hilang pada redeploys.
Mengapa Trigger.dev
Peralihan pada 2026 ialah pelaksanaan tahan lasak. Workflow mesti bertahan daripada restart, crash, deploy, dan rate limit. Mereka juga mesti melakukan streaming kemajuan ke UI dalam masa nyata dan jeda untuk input manusia. Trigger.dev dibina semula sekitar keperluan ini dengan versi 3 dan terus mengembangkan permukaan infrastruktur AInya.
Modelnya mudah: anda mendefinisikan tasks sebagai exports, SDK mengambilnya, platform menjadualkan dan menjalankannya dalam container terpencil, dan keadaan run dikekalkan supaya anda boleh menyambung semula, mencuba semula, dan memerhati.
Bermula
Mulakan projek
npx trigger.dev@latest login npx trigger.dev@latest init
Ini mencipta fail trigger.config.ts dan direktori trigger/ dengan tasks contoh. Fail config adalah sumber kebenaran untuk projek anda: direktori mana mengandungi tasks, tetapan build, lifecycle hooks, dan opsyen 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"], });
Jalankan tasks secara setempat
npx trigger.dev@latest dev
Pelayan dev menyambung ke awan, mendaftarkan tasks anda, dan melakukan streaming runs melalui kod tempatan anda. Anda menetapkan breakpoints dalam editor anda dan memukul mereka pada trigger sebenar - gelung yang sama yang anda akan gunakan dalam mana-mana projek Node.js biasa.
Mendefinisikan Task
Task ialah objek yang dieksport dengan id unik dan fungsi run. SDK memeriksa exports merentasi dirs dan mendaftarkannya secara automatik.
// 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 }; }, });
Tiga perkara yang perlu diperhatikan:
- Tiada timeout dalam badan run. Platform menguruskan masa pelaksanaan melalui
maxDurationdalam config, bukan dalam runtime. - Throws ialah retries. SDK menangkap pengecualian dan menjalankan semula dengan exponential backoff mengikut polisi
retry. - Nilai pulangan dikekalkan. Tasks lain dan frontend anda boleh membaca
run.outputdari mana-mana.
Triggering Tasks
Anda memanggil task dari backend anda, route API anda, atau task lain.
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 - gunakan untuk menjejaki atau memaparkan kemajuan
Pilihan membuka banyak tingkah laku dalam satu panggilan:
idempotencyKey- jika run dengan kekunci yang sama sudah wujud, SDK mengembalikan handle sedia ada bukannya menduplikasi kerja.concurrencyKey- mengsiri runs yang berkongsi kekunci supaya anda tidak melebihi rate limit per-tenant.queue.concurrencyLimit- cap global untuk queue merentasi semua kekunci.delay- menjadualkan run untuk masa akan datang.ttl- jika run belum bermula menjelang masa itu, ia tamat tempoh secara automatik.
Batch trigger
Untuk beban kerja fan-out, batchTrigger menerima sehingga 500 item setiap panggilan dan mencipta satu run setiap item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks Berjadual
Cron jobs menjadi pengisytiharan kelas pertama. Schedule itu sendiri ialah objek berasingan yang anda boleh lampirkan kepada task beberapa kali.
// 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); }, });
Untuk schedules per-tenant - katakan, satu cron setiap pelanggan - anda menciptanya secara dinamik melalui 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 menjadikan panggilan idempotent: menjalankan semula kod yang sama pada masa deploy tidak menyusun schedules pendua.
Queues, Concurrency, dan Idempotency
Tiga primitif meliputi kebanyakan keperluan rate-limiting dan susunan.
Corak biasa: satu queue per tenant dengan concurrency per-key kecil untuk menghormati rate limit vendor, ditambah dengan idempotency key untuk membuat retries selamat.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Waits dan Kerja Berjangka Panjang
Tasks boleh dihentikan tanpa memegang sambungan atau membakar compute. Platform mengekalkan keadaan dan menyambung semula fungsi apabila wait selesai.
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 adalah ciri pembunuh: ia mencetuskan child task dan menggantung parent sehingga child selesai. Anda menggubah tasks seperti fungsi async, tetapi orchestration berjalan tahan lasak selama beberapa hari atau minggu.
Human-in-the-loop dengan wait.forToken
Untuk aliran kelulusan dan AI gates, wait.forToken jeda sehingga aplikasi anda memanggil semula dengan hasil.
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); }, });
Editor membuka UI, menyemak draft, klik Lulus, dan backend anda melengkapkan token. Task menyambung dari tempat ia berhenti - walaupun jam atau hari telah berlalu.
Lifecycle Hooks
Anda boleh melampirkan init, onStart, onSuccess, dan onFailure ke task atau secara global dalam trigger.config.ts. Gunakannya untuk tracing, error reporting, dan setup dikongsi.
// 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 berjalan sekali per worker container pada boot, bukan per run, jadi ia adalah tempat yang betul untuk menyediakan clients dan pools.
Realtime dalam Frontend
Trigger.dev menerbitkan perubahan keadaan run - status, metadata, output - melalui streaming API. React hooks melanggan stream itu dan re-render secara automatik.
// 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> ); }
Anda menjana public access token di sisi pelayan, scoped ke run tertentu, dan menghantarnya ke client. Hook mengendalikan auth, sambungan semula, dan kemas kini tambahan.
Untuk trigger-and-subscribe dalam satu langkah:
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 dan Streaming
Trigger.dev telah menjadi runtime popular untuk AI agents kerana primitif yang sama - pelaksanaan tahan lasak, retries, waits, real-time metadata, human-in-the-loop - adalah tepat apa yang diperlukan agents. Anda streaming token dari penyedia model ke metadata semasa run berlaku, frontend merendernya secara langsung, dan run terselamat melalui tool calls berjangka panjang tanpa membakar 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 }; }, });
Frontend menggunakan useRealtimeRun dan membaca run.metadata.partial untuk merender tindak balas streaming, dengan cara yang sama anda akan merender chat completion - kecuali yang ini bertahan dari muat semula halaman penuh.
Deploying
Deploys mengkompilasi tasks anda kepada bundle berversi, membina container, dan menukar trafik secara atomik. runs lama dalam penerbangan terus menggunakan versi sebelumnya.
npx trigger.dev@latest deploy --env prod
Dalam CI anda biasanya menyambungkan ini ke workflow yang sama yang menghantar aplikasi anda:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
Untuk persekitaran preview, hantar --env preview --branch ${{ github.head_ref }} dan Trigger.dev mencipta persekitaran terpencil per branch, mencerminkan cara Vercel mengendalikan preview deployments.
Self-Hosting vs Cloud
Trigger.dev adalah sumber terbuka di bawah lesen Apache 2.0. Anda boleh self-host pada mana-mana platform container (Docker Compose, Kubernetes, Fly.io) atau menggunakan cloud terurus di trigger.dev.
| Aspek | Cloud | Self-hosted |
|---|---|---|
| Setup | Daftar, jalankan init | Jalankan docker-compose atau Helm chart |
| Scaling | Automatik | Tanggungjawab anda |
| Pricing | Per run + per compute | Hanya kos infra |
| Compliance | SOC 2 | Apa sahaja yang persekitaran anda sediakan |
| Terbaik untuk | Kebanyakan pasukan | Kediaman data ketat, infra tersuai |
SDK dan CLI adalah sama antara mod - anda menukar bendera profil dan menunjuk ke instance anda sendiri.
Best Practices
1. Pastikan payloads kecil dan boleh diserialisasi
Hantar IDs dan rujukan, bukan objek penuh. Tarik data di dalam task. Ini menjadikan queue kecil, payloads murah untuk dilog, dan membolehkan anda menukar sumber data tanpa mencetus semula.
2. Idempotency keys pada setiap panggilan luar
Gabungkan idempotencyKey pada trigger task dengan idempotency keys pada API vendor anda (Stripe, OpenAI, dll.). retries akan selamat dari hujung ke hujung.
3. Gunakan triggerAndWait untuk orchestration, bukan Promise.all daripada triggers
Parent yang memanggil triggerAndWait secara tahan lasak menggubah child tasks. Parent yang trigger dan menyelesaikan dengan segera kehilangan observability rantaian.
4. Tag runs
Tambahkan tags ke triggers (tags: ["user:123", "feature:onboarding"]) supaya anda boleh menapis dashboard dan management API mengikut dimensi perniagaan.
5. Pastikan init idempotent
Ia berjalan pada setiap cold start. Elakkan migrations atau kesan sampingan one-shot di sana.
Kesimpulan
Trigger.dev menghapuskan kategori kerja yang dahulunya memerlukan membina sistem job dari awal. Anda menulis async TypeScript, anda memanggilnya dari mana-mana, dan platform memberi anda pelaksanaan tahan lasak, scheduling, queues, retries, kemas kini real-time, dan corak human-in-the-loop di luar kotak.
Permukaan yang sama yang menggerakkan cron malam adalah permukaan yang menggerakkan multi-step AI agent yang streaming ke frontend dan jeda untuk semakan. Konvergensi itulah yang menjadikan framework berbaloi untuk pandangan serius pada 2026, sama ada anda menjalankan SaaS yang memerlukan kerja background yang boleh dipercayai atau menghantar ciri AI yang bertahan melepasi serverless timeout.
Senarai Semak Memulakan:
- Daftar di trigger.dev atau jalankan stack Docker self-hosted
npx trigger.dev@latest initdalam projek anda- Tentukan task pertama anda dengan
task({ id, run })- Trigger ia dari API anda dan tonton run dalam dashboard
- Tambah
idempotencyKeydanconcurrencyKeyuntuk keselamatan production- Sambungkan
useRealtimeRunke komponen status- Deploy dengan
trigger.dev deploy --env proddari CI