Sebagian besar aplikasi production memerlukan pekerjaan yang tidak cocok dengan siklus request/response: mengirim email, memproses unggahan, menjalankan pipeline AI, sinkronisasi data pihak ketiga, menghasilkan laporan. Jawaban tradisional adalah queue (Redis, SQS, RabbitMQ), armada worker, scheduler, dan tumpukan kode glue rapuh yang rusak setiap deploy.
Trigger.dev merampingkan stack tersebut menjadi satu SDK TypeScript. Anda menulis fungsi, memanggilnya dari mana saja, dan platform menangani queueing, retries, observability, scheduling, dan eksekusi yang tahan lama. Tasks berjalan selama yang dibutuhkan - tanpa timeout serverless 10 detik, tidak ada pekerjaan yang hilang saat redeploy.
Mengapa Trigger.dev
Pergeseran di 2026 adalah eksekusi yang tahan lama. Workflow harus bertahan terhadap restart, crash, deploy, dan rate limit. Mereka juga harus melakukan streaming progress ke UI secara real time dan jeda untuk input manusia. Trigger.dev dibangun ulang seputar persyaratan ini dengan versi 3 dan terus memperluas permukaan infrastruktur AI-nya.
Modelnya sederhana: Anda mendefinisikan tasks sebagai exports, SDK mengambilnya, platform menjadwalkan dan menjalankannya di container terisolasi, dan status run dipertahankan sehingga Anda dapat melanjutkan, mencoba lagi, dan mengamati.
Memulai
Inisialisasi proyek
npx trigger.dev@latest login npx trigger.dev@latest init
Ini membuat file trigger.config.ts dan direktori trigger/ dengan tasks contoh. File config adalah sumber kebenaran untuk proyek Anda: direktori mana yang berisi tasks, pengaturan build, lifecycle hooks, dan opsi 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 lokal
npx trigger.dev@latest dev
Server dev terhubung ke cloud, mendaftarkan tasks Anda, dan melakukan streaming runs melalui kode lokal Anda. Anda menetapkan breakpoint di editor Anda dan mencapainya pada trigger nyata - loop yang sama yang akan Anda gunakan dalam proyek Node.js normal.
Mendefinisikan Task
Task adalah objek yang diekspor dengan id unik dan fungsi run. SDK memeriksa exports di seluruh dirs dan mendaftarkannya secara otomatis.
// 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 hal yang perlu diperhatikan:
- Tidak ada timeout di body run. Platform mengelola waktu eksekusi melalui
maxDurationdi config, bukan di runtime. - Throws adalah retries. SDK menangkap exception dan menjalankan kembali dengan exponential backoff sesuai kebijakan
retry. - Nilai pengembalian dipertahankan. Tasks lain dan frontend Anda dapat membaca
run.outputdari mana saja.
Triggering Tasks
Anda memanggil task dari backend, route API, 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 melacak atau menampilkan progress
Opsi membuka banyak perilaku dalam satu panggilan:
idempotencyKey- jika run dengan key yang sama sudah ada, SDK mengembalikan handle yang ada alih-alih duplikat pekerjaan.concurrencyKey- menserialkan runs yang berbagi key sehingga Anda tidak melebihi rate limit per-tenant.queue.concurrencyLimit- cap global untuk queue di seluruh keys.delay- menjadwalkan run untuk waktu di masa depan.ttl- jika run belum dimulai pada saat itu, kadaluarsa secara otomatis.
Batch trigger
Untuk workload fan-out, batchTrigger menerima hingga 500 item per panggilan dan membuat satu run per item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks Terjadwal
Cron jobs menjadi deklarasi kelas pertama. Schedule itu sendiri adalah objek terpisah yang dapat Anda lampirkan ke 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 - katakanlah, satu cron per pelanggan - Anda membuatnya secara dinamis 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 membuat panggilan idempotent: menjalankan kembali kode yang sama saat deploy tidak menumpuk schedules duplikat.
Queues, Concurrency, dan Idempotency
Tiga primitif mencakup sebagian besar kebutuhan rate-limiting dan ordering.
Pola umum: satu queue per tenant dengan concurrency per-key kecil untuk menghormati rate limit vendor, ditambah idempotency key untuk membuat retries aman.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Waits dan Pekerjaan Berjalan Lama
Tasks dapat dijeda tanpa mempertahankan koneksi atau membakar compute. Platform mempertahankan status dan melanjutkan fungsi ketika 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 fitur pembunuh: ini memicu task anak dan menangguhkan parent sampai anak selesai. Anda menyusun tasks seperti fungsi async, tetapi orkestrasi berjalan tahan lama selama berhari-hari atau berminggu-minggu.
Human-in-the-loop dengan wait.forToken
Untuk approval flow dan AI gates, wait.forToken jeda sampai aplikasi Anda callback 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, meninjau draft, klik Approve, dan backend Anda menyelesaikan token. Task berlanjut dari tempat ia berhenti - bahkan jika sudah berlalu berjam-jam atau berhari-hari.
Lifecycle Hooks
Anda dapat melampirkan init, onStart, onSuccess, dan onFailure ke task atau secara global di trigger.config.ts. Gunakan untuk tracing, error reporting, dan setup bersama.
// 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 saat boot, bukan per run, jadi ini adalah tempat yang tepat untuk mengatur clients dan pools.
Realtime di Frontend
Trigger.dev mempublikasikan perubahan status run - status, metadata, output - melalui API streaming. React hooks subscribe ke stream itu dan re-render secara otomatis.
// 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 menghasilkan public access token sisi server, dengan scope ke run tertentu, dan mengirimkannya ke client. Hook menangani auth, reconnection, dan pembaruan inkremental.
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 populer untuk agent AI karena primitif yang sama - eksekusi tahan lama, retries, waits, metadata real-time, human-in-the-loop - persis yang dibutuhkan agent. Anda streaming token dari penyedia model ke metadata selama run berlangsung, frontend merendernya secara live, dan run bertahan dalam tool calls yang berjalan lama 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 respons streaming, dengan cara yang sama Anda akan merender chat completion - kecuali yang ini bertahan dari reload halaman penuh.
Deploying
Deploys mengompilasi tasks Anda menjadi bundle berversi, membangun container, dan menukar traffic secara atomik. Runs lama yang sedang berlangsung terus menggunakan versi sebelumnya.
npx trigger.dev@latest deploy --env prod
Di CI Anda biasanya menghubungkan ini ke workflow yang sama yang mengirim 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 environment preview, lewatkan --env preview --branch ${{ github.head_ref }} dan Trigger.dev membuat environment terisolasi per branch, mencerminkan cara Vercel menangani preview deployment.
Self-Hosting vs Cloud
Trigger.dev open source di bawah lisensi Apache 2.0. Anda dapat self-host di platform container apa pun (Docker Compose, Kubernetes, Fly.io) atau menggunakan cloud terkelola di trigger.dev.
| Aspek | Cloud | Self-hosted |
|---|---|---|
| Setup | Daftar, jalankan init | Jalankan docker-compose atau Helm chart |
| Scaling | Otomatis | Tanggung jawab Anda |
| Pricing | Per run + per compute | Hanya biaya infra |
| Compliance | SOC 2 | Apa pun yang disediakan environment Anda |
| Terbaik untuk | Sebagian besar tim | Data residency ketat, infra custom |
SDK dan CLI identik di antara mode - Anda mengubah flag profile dan menunjuk ke instance Anda sendiri.
Best Practices
1. Jaga payloads tetap kecil dan dapat diserialisasi
Lewatkan ID dan referensi, bukan objek lengkap. Tarik data di dalam task. Ini menjaga queue tetap kecil, payloads murah untuk dilog, dan memungkinkan Anda mengubah sumber data tanpa re-trigger.
2. Idempotency key di setiap panggilan eksternal
Gabungkan idempotencyKey di trigger task dengan idempotency keys di API vendor Anda (Stripe, OpenAI, dll.). Retries akan aman end-to-end.
3. Gunakan triggerAndWait untuk orkestrasi, bukan Promise.all dari triggers
Parent yang memanggil triggerAndWait secara tahan lama menyusun tasks anak. Parent yang trigger dan resolve segera kehilangan observability rantai.
4. Tag runs
Tambahkan tags ke trigger (tags: ["user:123", "feature:onboarding"]) sehingga Anda dapat memfilter dashboard dan management API berdasarkan dimensi bisnis.
5. Jaga init idempotent
Itu berjalan setiap cold start. Hindari migrations atau efek samping one-shot di sana.
Kesimpulan
Trigger.dev menghapus kategori pekerjaan yang dulu memerlukan membangun sistem job dari awal. Anda menulis async TypeScript, memanggilnya dari mana saja, dan platform memberi Anda eksekusi tahan lama, scheduling, queues, retries, pembaruan real-time, dan pola human-in-the-loop out of the box.
Permukaan yang sama yang menggerakkan cron malam adalah permukaan yang menggerakkan AI agent multi-langkah yang streaming ke frontend dan jeda untuk review. Konvergensi itu yang membuat framework layak dilihat secara serius di 2026, baik Anda menjalankan SaaS yang membutuhkan pekerjaan background yang andal atau mengirim fitur AI yang bertahan setelah timeout serverless.
Checklist Memulai:
- Daftar di trigger.dev atau jalankan stack Docker self-hosted
npx trigger.dev@latest initdi proyek Anda- Definisikan task pertama Anda dengan
task({ id, run })- Trigger dari API Anda dan lihat run di dashboard
- Tambahkan
idempotencyKeydanconcurrencyKeyuntuk keamanan production- Hubungkan
useRealtimeRunke komponen status- Deploy dengan
trigger.dev deploy --env proddari CI