แอปพลิเคชัน production ส่วนใหญ่ต้องการงานที่ไม่เข้ากับวงจร request/response: การส่งอีเมล การประมวลผลการอัปโหลด การรัน AI pipelines การซิงค์ข้อมูลจากบุคคลที่สาม การสร้างรายงาน คำตอบดั้งเดิมคือคิว (Redis, SQS, RabbitMQ), worker fleet, scheduler และกองโค้ด glue ที่เปราะบางซึ่งจะแตกในทุกๆ การ deploy
Trigger.dev ยุบ stack นั้นให้เป็น TypeScript SDK เดียว คุณเขียน functions เรียกใช้จากที่ไหนก็ได้ และแพลตฟอร์มจัดการ queueing, retries, observability, scheduling และการประมวลผลที่ทนทาน Tasks ทำงานนานเท่าที่ต้องการ - ไม่มี timeout serverless 10 วินาที ไม่มีงานที่หายไปเมื่อ redeploy
ทำไมต้อง Trigger.dev
การเปลี่ยนแปลงในปี 2026 คือการประมวลผลที่ทนทาน Workflows ต้องอยู่รอดจากการรีสตาร์ท การ crash การ deploy และ rate limit พวกเขาต้อง stream ความคืบหน้าไปยัง UI แบบเรียลไทม์และหยุดชั่วคราวสำหรับการป้อนข้อมูลของมนุษย์ Trigger.dev ถูกสร้างขึ้นใหม่รอบๆ ข้อกำหนดเหล่านี้ในเวอร์ชัน 3 และยังคงขยายพื้นผิวโครงสร้างพื้นฐาน AI ของตน
โมเดลนั้นเรียบง่าย: คุณกำหนด tasks เป็น exports, SDK รับมันมา แพลตฟอร์มจัดตารางและรันใน containers ที่แยกออกจากกันและสถานะของ 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 ของคุณและ stream runs ผ่านโค้ดในเครื่องของคุณ คุณตั้งค่า breakpoints ใน editor ของคุณและตีถึงพวกเขาบน trigger จริง - วงรอบเดียวกันที่คุณจะใช้ในโปรเจค Node.js ปกติ
การกำหนด Task
Task คือ object ที่ส่งออกพร้อม 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 จับ exceptions และรันใหม่ด้วย exponential backoff ตามนโยบาย
retry - ค่าที่ส่งคืนถูกเก็บไว้ Tasks อื่นและ frontend ของคุณสามารถอ่าน
run.outputจากที่ไหนก็ได้
การ Trigger Tasks
คุณเรียกใช้ task จาก backend, route 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 ที่มี key เดียวกันมีอยู่แล้ว SDK จะส่งคืน handle ที่มีอยู่แทนการทำงานซ้ำconcurrencyKey- ทำการอนุกรม runs ที่แชร์ key ดังนั้นคุณจะไม่เกิน rate limit ต่อ tenantqueue.concurrencyLimit- ขอบเขตทั่วโลกสำหรับ queue ในทุก keysdelay- กำหนดเวลา run สำหรับเวลาในอนาคตttl- หาก run ยังไม่ได้เริ่มต้นภายในเวลานั้น ให้หมดอายุโดยอัตโนมัติ
Batch trigger
สำหรับ workload fan-out, batchTrigger ยอมรับ items สูงสุด 500 รายการต่อการเรียกและสร้าง run หนึ่งรายการต่อ item
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks ที่กำหนดเวลา
Cron jobs กลายเป็นการประกาศชั้นแรก Schedule เองเป็น object แยกที่คุณสามารถแนบกับ 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); }, });
สำหรับ schedule ต่อ 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 ซ้ำกัน
Queues, Concurrency และ Idempotency
primitives สามตัวครอบคลุมความต้องการ rate-limiting และ ordering ส่วนใหญ่
รูปแบบทั่วไป: queue หนึ่งต่อ tenant ที่มี concurrency ต่อ key เล็กเพื่อเคารพ rate limit ของ vendor บวกกับ idempotency key เพื่อให้ retries ปลอดภัย
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Waits และงานที่ทำงานนาน
Tasks สามารถหยุดชั่วคราวได้โดยไม่ต้องรักษาการเชื่อมต่อหรือเผา compute แพลตฟอร์มเก็บสถานะและดำเนินการฟังก์ชันต่อเมื่อ wait เสร็จสิ้น
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 คือฟีเจอร์เด็ดขาด: มัน trigger task ลูกและระงับ parent จนกว่า child จะเสร็จสมบูรณ์ คุณรวบรวม tasks เหมือน async functions แต่การประสานทำงานอย่างทนทานข้ามวันหรือสัปดาห์
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); }, });
editor เปิด UI ตรวจสอบร่าง คลิกอนุมัติและ backend ของคุณทำให้ token เสร็จสมบูรณ์ task ดำเนินการต่อจากที่ค้างไว้ - แม้ว่าจะผ่านไปหลายชั่วโมงหรือหลายวัน
Lifecycle Hooks
คุณสามารถแนบ init, onStart, onSuccess และ onFailure กับ task หรือทั่วโลกใน trigger.config.ts ใช้สำหรับ tracing, error reporting และ setup ที่ใช้ร่วมกัน
// 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 ใน Frontend
Trigger.dev เผยแพร่การเปลี่ยนแปลงสถานะ run - status, metadata, output - ผ่าน streaming API React hooks สมัครรับ stream นั้นและ 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 ฝั่งเซิร์ฟเวอร์ที่กำหนดขอบเขตให้กับ run เฉพาะและส่งให้กับ client 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, waits, real-time metadata, human-in-the-loop - คือสิ่งที่ agents ต้องการ คุณ stream tokens จาก model provider เข้าสู่ metadata ในขณะที่ run กำลังเกิดขึ้น frontend แสดงผลแบบสด และ 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 }; }, });
frontend ใช้ useRealtimeRun และอ่าน run.metadata.partial เพื่อแสดง response แบบ streaming ในวิธีเดียวกับที่คุณจะแสดง chat completion - ยกเว้นว่าอันนี้อยู่รอดจาก reload หน้าเต็ม
Deploying
Deploys คอมไพล์ tasks ของคุณเป็น bundle ที่มีเวอร์ชัน สร้าง container และสลับการรับส่งข้อมูลแบบ atomic Old in-flight 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
สำหรับ environment preview ส่ง --env preview --branch ${{ github.head_ref }} และ Trigger.dev สร้างสภาพแวดล้อมที่แยกต่อ branch สะท้อนวิธีที่ Vercel จัดการ preview deployments
Self-Hosting vs Cloud
Trigger.dev เป็นโอเพ่นซอร์สภายใต้ใบอนุญาต Apache 2.0 คุณสามารถ self-host บนแพลตฟอร์ม container ใดๆ (Docker Compose, Kubernetes, Fly.io) หรือใช้ cloud ที่จัดการที่ trigger.dev
| ด้าน | Cloud | Self-hosted |
|---|---|---|
| Setup | ลงทะเบียน รัน init | รัน docker-compose หรือ Helm chart |
| Scaling | อัตโนมัติ | ความรับผิดชอบของคุณ |
| Pricing | ต่อ run + ต่อ compute | ค่า infra เท่านั้น |
| Compliance | SOC 2 | สิ่งที่สภาพแวดล้อมของคุณให้ |
| เหมาะสำหรับ | ทีมส่วนใหญ่ | data residency เข้มงวด, infra ที่กำหนดเอง |
SDK และ CLI เหมือนกันระหว่างโหมด - คุณเปลี่ยน profile flag และชี้ไปที่ instance ของคุณเอง
Best Practices
1. รักษา payloads ให้เล็กและ serializable ได้
ส่ง IDs และการอ้างอิง ไม่ใช่ objects เต็ม ดึงข้อมูลภายใน task สิ่งนี้ช่วยให้ queue เล็ก payloads ถูกในการ log และให้คุณเปลี่ยนแหล่งข้อมูลโดยไม่ต้อง re-trigger
2. Idempotency keys ในทุกการเรียกภายนอก
รวม idempotencyKey บน trigger ของ task กับ idempotency keys บน vendor APIs (Stripe, OpenAI, ฯลฯ) Retries จะปลอดภัย end-to-end
3. ใช้ triggerAndWait สำหรับการประสาน ไม่ใช่ Promise.all ของ triggers
parent ที่เรียก triggerAndWait รวบรวม tasks ลูกอย่างทนทาน parent ที่ trigger และแก้ไขทันทีจะสูญเสียความสามารถในการสังเกตของ chain
4. Tag runs
เพิ่ม tags ให้กับ triggers (tags: ["user:123", "feature:onboarding"]) เพื่อให้คุณสามารถกรอง dashboard และ management API ตามมิติทางธุรกิจ
5. รักษา init ให้ idempotent
มันรันทุก cold start หลีกเลี่ยง migrations หรือ side effects แบบ one-shot ที่นั่น
บทสรุป
Trigger.dev ลบประเภทของงานที่เคยต้องสร้างระบบ job จากศูนย์ คุณเขียน async TypeScript เรียกใช้จากที่ไหนก็ได้และแพลตฟอร์มให้คุณการประมวลผลที่ทนทาน การกำหนดเวลา queues, retries, การอัปเดตแบบ real-time และรูปแบบ human-in-the-loop ออกจากกล่อง
พื้นผิวเดียวกันที่ขับเคลื่อน cron กลางคืนคือพื้นผิวที่ขับเคลื่อน AI agent หลายขั้นตอนที่ stream ไปยัง frontend และหยุดชั่วคราวสำหรับการตรวจสอบ การบรรจบกันนั้นคือสิ่งที่ทำให้ framework ควรค่าแก่การมองอย่างจริงจังในปี 2026 ไม่ว่าคุณจะกำลังจัดการ SaaS ที่ต้องการงาน background ที่เชื่อถือได้หรือจัดส่งฟีเจอร์ AI ที่อยู่รอดเหนือ timeout serverless
Checklist เริ่มต้น:
- ลงทะเบียนที่ trigger.dev หรือรัน stack Docker self-hosted
npx trigger.dev@latest initในโปรเจคของคุณ- กำหนด task แรกของคุณด้วย
task({ id, run })- Trigger จาก API ของคุณและดู run ใน dashboard
- เพิ่ม
idempotencyKeyและconcurrencyKeyสำหรับความปลอดภัยใน production- เชื่อม
useRealtimeRunเข้ากับ status component- Deploy ด้วย
trigger.dev deploy --env prodจาก CI