De flesta produktionsapplikationer behover arbete som inte passar i request/response-cykeln: skicka e-post, bearbeta uppladdningar, kora AI-pipelines, synkronisera tredjepartsdata, generera rapporter. Det traditionella svaret ar en ko (Redis, SQS, RabbitMQ), en worker-flotta, en schemalaggare och en bracklig hog med klisterkod som gar sonder vid varje deploy.
Trigger.dev komprimerar den stacken till ett enda TypeScript SDK. Du skriver funktioner, anropar dem var som helst och plattformen hanterar koer, retries, observability, schemalaggning och hallbar exekvering. Tasks korr sa lange som behovs - ingen 10-sekunders serverless-timeout, inget forlorat arbete vid omdistribueringar.
Varfor Trigger.dev
Skiftet 2026 ar hallbar exekvering. Arbetsfloden maste overleva omstarter, krascher, distributioner och rate limits. De maste ocksa stromma framsteg till UI:n i realtid och pausa for mansklig input. Trigger.dev byggdes om kring dessa krav med version 3 och fortsatter att utoka sin AI-infrastrukturyta.
Modellen ar enkel: du definierar tasks som exports, SDK:n plockar upp dem, plattformen schemalagger och kor dem i isolerade containrar och run-tillstandet bevaras sa att du kan aterupp ta, ompro va och observera.
Komma igang
Initiera ett projekt
npx trigger.dev@latest login npx trigger.dev@latest init
Detta skapar en trigger.config.ts-fil och en trigger/-katalog med exempeltasks. Konfigfilen ar sanningens kalla for ditt projekt: vilka kataloger som innehaller tasks, byggesinstallningar, lifecycle hooks och runtime-alternativ.
// 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"], });
Kor tasks lokalt
npx trigger.dev@latest dev
Dev-servern ansluter till molnet, registrerar dina tasks och stromar runs genom din lokala kod. Du satter brytpunkter i din editor och traffar dem pa riktiga triggers - samma loop du skulle anvanda i vilket vanligt Node.js-projekt som helst.
Definiera en Task
En task ar ett objekt som exporteras med ett unikt id och en run-funktion. SDK:n inspekterar exports over dirs och registrerar dem automatiskt.
// 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 }; }, });
Tre saker att notera:
- Ingen timeout i run-kroppen. Plattformen hanterar exekveringstiden via
maxDurationi konfigen, inte i runtime. - Throws ar retries. SDK:n fangar undantag och kor om med exponentiell backoff enligt
retry-policyn. - Returvardet bevaras. Andra tasks och din frontend kan lasa
run.outputvar som helst.
Trigga Tasks
Du anropar en task fran din backend, dina API-rutter eller en annan 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 - anvand for att spara eller visa framsteg
Alternativen lascker upp mycket beteende i ett enda anrop:
idempotencyKey- om en run med samma nyckel redan finns returnerar SDK:n det befintliga handtaget istallet for att duplicera arbete.concurrencyKey- serialiserar runs som delar nyckel sa att du inte overskrider en per-tenant rate limit.queue.concurrencyLimit- global cap for koen over alla nycklar.delay- schemalagger run for en framtida tidpunkt.ttl- om run inte har startats vid den tiden, lat den loop ut automatiskt.
Batch trigger
For fan-out-arbetsbelastningar accepterar batchTrigger upp till 500 objekt per anrop och skapar en run per objekt.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Schemalagda Tasks
Cron-jobb blir forstklassiga deklarationer. Schemat sjalv ar ett separat objekt som du kan kopla till en task flera ganger.
// 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); }, });
For per-tenant-scheman - sag, en cron per kund - skapar du dem dynamiskt via management-API:t.
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 gor anropet idempotent: att kora samma kod igen vid deploy-tid stackar inte duplicerade scheman.
Koer, Samtidighet och Idempotens
Tre primitiver tacker de flesta behov av rate-limiting och ordning.
Ett vanligt monster: en ko per tenant med liten per-key concurrency for att respektera en vendors rate limit, plus en idempotens-nyckel for att gora retries sakra.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Vantan och langvarigt arbete
Tasks kan pausa utan att halla en anslutning eller branna compute. Plattformen bevarar tillstandet och aterupptar funktionen nar vantan ar klar.
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 ar killer-funktionen: den triggar en barntask och suspenderar foraldern tills barnet ar klart. Du komponerar tasks som async-funktioner, men orkestreringen kor hallbart over dagar eller veckor.
Human-in-the-loop med wait.forToken
For godkannandeflaer och AI-grindar pausar wait.forToken tills din applikation atergivar med ett resultat.
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); }, });
Redaktoren oppnar ett UI, granskar utkastet, klickar pa Godkann och din backend slutfor token. Task fortsatter dar den slutade - aven om timmar eller dagar har gatt.
Lifecycle Hooks
Du kan kopla init, onStart, onSuccess och onFailure till en task eller globalt i trigger.config.ts. Anvand dem for tracing, error reporting och delad 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 kor en gang per worker-container vid boot, inte per run, sa det ar ratt plats att satta upp klienter och pooler.
Realtid i Frontenden
Trigger.dev publicerar run-tillstandsforandringar - status, metadata, output - over ett streaming-API. React-hooks prenumererar pa den strommen och re-renderar automatiskt.
// 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> ); }
Du genererar den publika atkomsttoken pa serversidan, scopad till en specifik run, och skickar den till klienten. Hooket hanterar auth, ateranslutning och inkrementella uppdateringar.
For trigger-and-subscribe i ett steg:
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-agenter och streaming
Trigger.dev har blivit en popular runtime for AI-agenter eftersom samma primitiver - hallbar exekvering, retries, vantan, realtidsmetadata, human-in-the-loop - ar exakt vad agenter behover. Du stromar tokens fran en modellprovider till metadata medan run pagar, frontenden renderar dem live och run overlever langvariga tool calls utan att branna en serverless-timeout.
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 }; }, });
Frontenden anvander useRealtimeRun och laser run.metadata.partial for att rendera streaming-svaret, pa samma satt som du skulle rendera en chat completion - forutom att den har overlever en fullstandig sidaomladdning.
Distribuera
Distribueringar kompilerar dina tasks till en versionerad bundle, bygger en container och vaxlar trafiken atomart. Gamla in-flight runs fortsatter att anvanda den foregaende versionen.
npx trigger.dev@latest deploy --env prod
I CI ansluter du detta vanligtvis till samma workflow som skickar din app:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
For preview-miljoer, skicka --env preview --branch ${{ github.head_ref }} och Trigger.dev skapar en isolerad miljo per branch, vilket speglar hur Vercel hanterar preview-distribueringar.
Self-Hosting vs Cloud
Trigger.dev ar oppen kallkod under Apache 2.0-licensen. Du kan self-hosta pa vilken containerplattform som helst (Docker Compose, Kubernetes, Fly.io) eller anvanda den hanterade molnet pa trigger.dev.
| Aspekt | Cloud | Self-hosted |
|---|---|---|
| Setup | Registrera, kor init | Kor docker-compose eller Helm chart |
| Skalning | Automatisk | Ditt ansvar |
| Pricing | Per run + per compute | Endast infra-kostnad |
| Compliance | SOC 2 | Vad din miljo tillhandahaller |
| Bast for | De flesta team | Strikt dataresidens, custom infra |
SDK:n och CLI:n ar identiska mellan lagen - du andrar en profilflagga och pekar pa din egen instans.
Best Practices
1. Hall payloads sma och serialiserbara
Skicka ID:n och referenser, inte fullstandiga objekt. Hamta data inuti task. Detta haller koen liten, payloads billiga att logga och later dig andra datakallan utan att trigga om.
2. Idempotens-nycklar pa varje extern call
Kombinera idempotencyKey pa task-triggern med idempotens-nycklar pa dina vendor-API:er (Stripe, OpenAI, etc.). Retries blir end-to-end sakra.
3. Anvand triggerAndWait for orkestrering, inte Promise.all av triggers
En forilder som anropar triggerAndWait komponerar hallbart barntasks. En forilder som triggar och resolvar omedelbart forlorar observabilitet av kedjan.
4. Tagga runs
Lagg till tags till triggers (tags: ["user:123", "feature:onboarding"]) sa att du kan filtrera dashboarden och management-API:t efter affarsdimensioner.
5. Hall init idempotent
Den kor vid varje cold start. Undvik migrationer eller engangs-sidoeffekter dar.
Slutsats
Trigger.dev tar bort kategorierna av arbete som tidigare kravde att bygga ett job-system fran grunden. Du skriver async TypeScript, du anropar det var som helst och plattformen ger dig hallbar exekvering, schemalaggning, koer, retries, realtidsuppdateringar och human-in-the-loop-monster ut ur lasten.
Samma yta som driver en nattlig cron ar ytan som driver en flerstegs AI-agent som stromar till frontenden och pausar for granskning. Den konvergensen ar vad som gor framework vart en seri os blick 2026, oavsett om du driver en SaaS som behover palitligt bakgrundsarbete eller skickar AI-funktioner som overlever en serverless-timeout.
Komma igang Checklist:
- Registrera dig pa trigger.dev eller kor self-hosted Docker-stacken
npx trigger.dev@latest initi ditt projekt- Definiera din forsta task med
task({ id, run })- Trigga den fran ditt API och se run i dashboarden
- Lagg till
idempotencyKeyochconcurrencyKeyfor produktionsakerhet- Koppla
useRealtimeRuntill en statuskomponent- Distribuera med
trigger.dev deploy --env prodfran CI