대부분의 프로덕션 애플리케이션은 요청/응답 사이클에 맞지 않는 작업이 필요합니다: 이메일 보내기, 업로드 처리, AI 파이프라인 실행, 타사 데이터 동기화, 보고서 생성. 전통적인 답은 큐(Redis, SQS, RabbitMQ), 작업자 함대, 스케줄러, 그리고 모든 배포에서 깨지는 글루 코드의 약한 더미입니다.
Trigger.dev는 그 스택을 단일 TypeScript SDK로 압축합니다. 함수를 작성하고, 어디서나 호출하면, 플랫폼이 큐잉, 재시도, 가시성, 일정 잡기 및 내구성 있는 실행을 처리합니다. 작업은 필요한 만큼 오래 실행됩니다 - 10초 서버리스 타임아웃 없음, 재배포 시 작업 손실 없음.
왜 Trigger.dev인가
2026년의 변화는 내구성 있는 실행입니다. 워크플로우는 재시작, 충돌, 배포 및 속도 제한에서 살아남아야 합니다. 또한 진행 상황을 실시간으로 UI에 스트리밍하고 사람의 입력을 위해 일시 중지해야 합니다. Trigger.dev는 버전 3에서 이러한 요구 사항을 중심으로 재구축되었으며 AI 인프라 표면을 계속 확장하고 있습니다.
모델은 단순합니다: 작업을 export로 정의하고, SDK가 이를 가져오고, 플랫폼이 격리된 컨테이너에서 일정을 잡고 실행하며, 실행 상태가 유지되어 재개, 재시도 및 관찰할 수 있습니다.
시작하기
프로젝트 초기화
npx trigger.dev@latest login npx trigger.dev@latest init
이는 trigger.config.ts 파일과 예제 작업이 있는 trigger/ 디렉토리를 생성합니다. config 파일은 프로젝트의 진실의 원천입니다: 어떤 디렉토리가 작업을 포함하는지, 빌드 설정, 라이프사이클 훅 및 런타임 옵션.
// 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"], });
작업을 로컬에서 실행
npx trigger.dev@latest dev
dev 서버는 클라우드에 연결하고, 작업을 등록하고, 로컬 코드를 통해 실행을 스트리밍합니다. 에디터에 중단점을 설정하고 실제 트리거에서 적중합니다 - 일반 Node.js 프로젝트에서 사용하는 것과 동일한 루프입니다.
작업 정의
작업은 고유한 id와 run 함수로 export된 객체입니다. SDK는 dirs 전체에서 export를 검사하고 자동으로 등록합니다.
// 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 }; }, });
세 가지 주의 사항:
- run 본문에 타임아웃 없음. 플랫폼은 런타임이 아닌 config의
maxDuration을 통해 실행 시간을 관리합니다. - Throw는 재시도입니다. SDK는 예외를 잡고
retry정책에 따라 지수 백오프로 다시 실행합니다. - 반환 값은 유지됩니다. 다른 작업과 프론트엔드는 어디서나
run.output을 읽을 수 있습니다.
작업 트리거
백엔드, API 라우트 또는 다른 작업에서 작업을 호출합니다.
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- 같은 키의 실행이 이미 존재하면, SDK는 작업을 복제하는 대신 기존 핸들을 반환합니다.concurrencyKey- 키를 공유하는 실행을 직렬화하여 테넌트당 속도 제한을 초과하지 않도록 합니다.queue.concurrencyLimit- 모든 키에서 큐의 글로벌 캡.delay- 실행을 미래의 시간으로 일정을 잡습니다.ttl- 그때까지 실행이 시작되지 않으면 자동으로 만료시킵니다.
Batch trigger
팬아웃 워크로드의 경우, batchTrigger는 호출당 최대 500개 항목을 받아들이고 항목당 하나의 실행을 생성합니다.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
일정이 잡힌 작업
Cron 작업은 일급 선언이 됩니다. 일정 자체는 작업에 여러 번 첨부할 수 있는 별도의 객체입니다.
// 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); }, });
테넌트별 일정 - 예를 들어 고객당 하나의 cron - 의 경우, 관리 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는 호출을 멱등하게 만듭니다: 배포 시 같은 코드를 다시 실행해도 중복된 일정을 쌓지 않습니다.
큐, 동시성 및 멱등성
세 가지 기본 요소가 대부분의 속도 제한 및 순서 지정 요구 사항을 다룹니다.
일반적인 패턴: 벤더의 속도 제한을 존중하기 위해 테넌트당 하나의 큐와 키별 작은 동시성, 재시도를 안전하게 만들기 위한 멱등성 키.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
대기 및 장기 실행 작업
작업은 연결을 유지하거나 컴퓨팅을 태우지 않고 일시 중지할 수 있습니다. 플랫폼은 상태를 유지하고 대기가 완료되면 함수를 재개합니다.
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는 킬러 기능입니다: 자식 작업을 트리거하고 자식이 완료될 때까지 부모를 일시 중지합니다. async 함수처럼 작업을 구성하지만, 오케스트레이션은 며칠 또는 몇 주에 걸쳐 내구성 있게 실행됩니다.
wait.forToken을 사용한 Human-in-the-loop
승인 흐름 및 AI 게이트의 경우, 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를 열고, 초안을 검토하고, 승인을 클릭하고, 백엔드는 토큰을 완료합니다. 작업은 중단된 곳에서 재개됩니다 - 몇 시간 또는 며칠이 지났더라도.
라이프사이클 훅
init, onStart, onSuccess 및 onFailure를 작업에 또는 trigger.config.ts에서 전역적으로 첨부할 수 있습니다. 추적, 오류 보고 및 공유 설정에 사용합니다.
// 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은 부팅 시 작업자 컨테이너당 한 번 실행되며, 실행당이 아니므로 클라이언트와 풀을 설정하기에 적합한 곳입니다.
프론트엔드의 실시간
Trigger.dev는 실행 상태 변경 - 상태, 메타데이터, 출력 - 을 스트리밍 API로 게시합니다. React 훅은 해당 스트림을 구독하고 자동으로 다시 렌더링합니다.
// 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> ); }
특정 실행으로 범위가 지정된 공개 액세스 토큰을 서버 측에서 생성하고 클라이언트에 전송합니다. 훅은 인증, 재연결 및 증분 업데이트를 처리합니다.
원샷 트리거 및 구독:
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 에이전트 및 스트리밍
Trigger.dev는 AI 에이전트를 위한 인기 있는 런타임이 되었습니다. 같은 기본 요소들 - 내구성 있는 실행, 재시도, 대기, 실시간 메타데이터, human-in-the-loop - 이 정확히 에이전트가 필요로 하는 것이기 때문입니다. 실행이 진행되는 동안 모델 제공자의 토큰을 metadata로 스트리밍하면, 프론트엔드가 이를 라이브로 렌더링하고, 실행은 서버리스 타임아웃을 태우지 않고도 장기 실행 도구 호출을 살아남습니다.
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을 읽어 스트리밍 응답을 렌더링합니다. 채팅 완료를 렌더링하는 것과 같은 방식이지만, 이것은 전체 페이지 새로고침에서도 살아남습니다.
배포
배포는 작업을 버전이 지정된 번들로 컴파일하고, 컨테이너를 빌드하고, 트래픽을 원자적으로 교체합니다. 이전의 진행 중인 실행은 이전 버전을 계속 사용합니다.
npx trigger.dev@latest deploy --env prod
CI에서는 일반적으로 이를 앱을 배포하는 동일한 워크플로우에 연결합니다:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
미리보기 환경의 경우, --env preview --branch ${{ github.head_ref }}를 전달하면 Trigger.dev가 분기당 격리된 환경을 생성하여 Vercel이 미리보기 배포를 처리하는 방식을 반영합니다.
셀프 호스팅 vs 클라우드
Trigger.dev는 Apache 2.0 라이선스 하에 오픈 소스입니다. 모든 컨테이너 플랫폼(Docker Compose, Kubernetes, Fly.io)에서 셀프 호스팅하거나 trigger.dev에서 관리되는 클라우드를 사용할 수 있습니다.
| 측면 | 클라우드 | 셀프 호스팅 |
|---|---|---|
| 설정 | 가입, init 실행 | docker-compose 또는 Helm chart 실행 |
| 확장 | 자동 | 당신의 책임 |
| 가격 | 실행당 + 컴퓨팅당 | 인프라 비용만 |
| 준수 | SOC 2 | 환경이 제공하는 것 |
| 최적 | 대부분의 팀 | 엄격한 데이터 거주, 사용자 정의 인프라 |
SDK와 CLI는 모드 간에 동일합니다 - 프로필 플래그를 변경하고 자신의 인스턴스를 가리킵니다.
모범 사례
1. 페이로드를 작고 직렬화 가능하게 유지
전체 객체가 아닌 ID와 참조를 전달하세요. 작업 내부에서 데이터를 가져오세요. 이렇게 하면 큐가 작게 유지되고, 페이로드가 로깅하기 저렴하며, 다시 트리거하지 않고도 데이터 소스를 변경할 수 있습니다.
2. 모든 외부 호출에 멱등성 키
작업 트리거의 idempotencyKey를 벤더 API(Stripe, OpenAI 등)의 멱등성 키와 결합하세요. 재시도는 종단 간 안전합니다.
3. 오케스트레이션에 triggerAndWait를 사용, 트리거의 Promise.all이 아님
triggerAndWait를 호출하는 부모는 자식 작업을 내구성 있게 구성합니다. 트리거하고 즉시 해결하는 부모는 체인의 가시성을 잃습니다.
4. 실행에 태그 지정
트리거에 tags를 추가하세요(tags: ["user:123", "feature:onboarding"]). 그러면 비즈니스 차원으로 대시보드와 관리 API를 필터링할 수 있습니다.
5. init을 멱등하게 유지
모든 콜드 스타트에서 실행됩니다. 마이그레이션이나 일회성 부작용을 거기에 두지 마세요.
결론
Trigger.dev는 이전에 처음부터 작업 시스템을 구축해야 했던 작업 카테고리를 제거합니다. async TypeScript를 작성하고, 어디서나 호출하면, 플랫폼은 내구성 있는 실행, 일정 잡기, 큐, 재시도, 실시간 업데이트 및 human-in-the-loop 패턴을 즉시 제공합니다.
야간 cron을 구동하는 동일한 표면이 프론트엔드로 스트리밍하고 검토를 위해 일시 중지되는 다단계 AI 에이전트를 구동하는 표면입니다. 이러한 융합이 신뢰할 수 있는 백그라운드 작업이 필요한 SaaS를 운영하든, 서버리스 타임아웃을 넘어 살아남는 AI 기능을 출시하든, 프레임워크를 2026년에 진지하게 살펴볼 가치가 있게 만드는 것입니다.
시작하기 체크리스트:
- trigger.dev에 가입하거나 셀프 호스팅 Docker 스택 실행
- 프로젝트에서
npx trigger.dev@latest inittask({ id, run })로 첫 번째 작업 정의- API에서 트리거하고 대시보드에서 실행 보기
- 프로덕션 안전을 위해
idempotencyKey및concurrencyKey추가useRealtimeRun을 상태 컴포넌트에 연결- CI에서
trigger.dev deploy --env prod로 배포