spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12De 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.34[Trigger.dev](https://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.56## Varfor Trigger.dev78Skiftet 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.910```mermaid11graph LR12 App[Din App] -->|trigger| API[Trigger.dev API]13 API --> Queue[Hallbar Ko]14 Queue --> Worker[Worker Container]15 Worker -->|run task| Task[Din Task-kod]16 Task -->|metadata| Realtime[Realtidsstrom]17 Realtime --> UI[React UI]18 Worker --> Storage[Run-tillstandslager]19```2021Modellen 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.2223## Komma igang2425### Initiera ett projekt2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132Detta 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.3334```typescript35// trigger.config.ts36import { defineConfig } from "@trigger.dev/sdk";3738export default defineConfig({39 project: "proj_abc123",40 runtime: "node",41 logLevel: "log",42 maxDuration: 3600,43 retries: {44 enabledInDev: true,45 default: {46 maxAttempts: 3,47 factor: 2,48 minTimeoutInMs: 1000,49 maxTimeoutInMs: 30_000,50 },51 },52 dirs: ["./trigger"],53});54```5556### Kor tasks lokalt5758```bash59npx trigger.dev@latest dev60```6162Dev-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.6364## Definiera en Task6566En task ar ett objekt som exporteras med ett unikt `id` och en `run`-funktion. SDK:n inspekterar exports over `dirs` och registrerar dem automatiskt.6768```typescript69// trigger/send-welcome-email.ts70import { task } from "@trigger.dev/sdk";71import { Resend } from "resend";7273const resend = new Resend(process.env.RESEND_API_KEY);7475export const sendWelcomeEmail = task({76 id: "send-welcome-email",77 retry: {78 maxAttempts: 5,79 factor: 1.8,80 minTimeoutInMs: 500,81 maxTimeoutInMs: 30_000,82 },83 run: async (payload: { email: string; name: string }) => {84 const { data, error } = await resend.emails.send({85 from: "hello@spinny.dev",86 to: payload.email,87 subject: `Welcome, ${payload.name}`,88 html: `<p>Glad you are here, ${payload.name}.</p>`,89 });9091 if (error) throw error;92 return { messageId: data?.id };93 },94});95```9697Tre saker att notera:98991. **Ingen timeout i run-kroppen.** Plattformen hanterar exekveringstiden via `maxDuration` i konfigen, inte i runtime.1002. **Throws ar retries.** SDK:n fangar undantag och kor om med exponentiell backoff enligt `retry`-policyn.1013. **Returvardet bevaras.** Andra tasks och din frontend kan lasa `run.output` var som helst.102103## Trigga Tasks104105Du anropar en task fran din backend, dina API-rutter eller en annan task.106107```typescript108import { sendWelcomeEmail } from "@/trigger/send-welcome-email";109110const handle = await sendWelcomeEmail.trigger(111 { email: "user@example.com", name: "Alex" },112 {113 idempotencyKey: `welcome-${userId}`,114 concurrencyKey: `tenant-${tenantId}`,115 queue: { name: "emails", concurrencyLimit: 50 },116 delay: "30s",117 ttl: "10m",118 }119);120121console.log(handle.id); // run_xyz - anvand for att spara eller visa framsteg122```123124Alternativen lascker upp mycket beteende i ett enda anrop:125126- **`idempotencyKey`** - om en run med samma nyckel redan finns returnerar SDK:n det befintliga handtaget istallet for att duplicera arbete.127- **`concurrencyKey`** - serialiserar runs som delar nyckel sa att du inte overskrider en per-tenant rate limit.128- **`queue.concurrencyLimit`** - global cap for koen over alla nycklar.129- **`delay`** - schemalagger run for en framtida tidpunkt.130- **`ttl`** - om run inte har startats vid den tiden, lat den loop ut automatiskt.131132### Batch trigger133134For fan-out-arbetsbelastningar accepterar `batchTrigger` upp till 500 objekt per anrop och skapar en run per objekt.135136```typescript137await sendWelcomeEmail.batchTrigger(138 newUsers.map((u) => ({139 payload: { email: u.email, name: u.name },140 options: { idempotencyKey: `welcome-${u.id}` },141 }))142);143```144145## Schemalagda Tasks146147Cron-jobb blir forstklassiga deklarationer. Schemat sjalv ar ett separat objekt som du kan kopla till en task flera ganger.148149```typescript150// trigger/daily-digest.ts151import { schedules } from "@trigger.dev/sdk";152153export const dailyDigest = schedules.task({154 id: "daily-digest",155 cron: "0 9 * * *",156 run: async (payload) => {157 console.log("Scheduled at:", payload.timestamp);158 console.log("Last run:", payload.lastTimestamp);159 console.log("Timezone:", payload.timezone);160 console.log("Next 5 runs:", payload.upcoming);161162 await sendDigestForDate(payload.timestamp);163 },164});165```166167For per-tenant-scheman - sag, en cron per kund - skapar du dem dynamiskt via management-API:t.168169```typescript170import { schedules } from "@trigger.dev/sdk";171172await schedules.create({173 task: "daily-digest",174 cron: "0 9 * * *",175 timezone: "America/New_York",176 externalId: `customer_${customerId}`,177 deduplicationKey: `digest-${customerId}`,178});179```180181`deduplicationKey` gor anropet idempotent: att kora samma kod igen vid deploy-tid stackar inte duplicerade scheman.182183## Koer, Samtidighet och Idempotens184185Tre primitiver tacker de flesta behov av rate-limiting och ordning.186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>sedd?}190 IK -->|ja| Reuse[Returnera befintlig run]191 IK -->|nej| CK[concurrencyKey-bucket]192 CK --> Q[Ko med<br/>concurrencyLimit]193 Q -->|slot tillganglig| Run[Kor task]194 Q -->|slots fulla| Wait[Vanta i kon]195```196197Ett 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.198199```typescript200await syncShopifyOrders.trigger(201 { shopId },202 {203 queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 },204 concurrencyKey: shopId,205 idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`,206 }207);208```209210## Vantan och langvarigt arbete211212Tasks kan pausa utan att halla en anslutning eller branna compute. Plattformen bevarar tillstandet och aterupptar funktionen nar vantan ar klar.213214```typescript215import { wait } from "@trigger.dev/sdk";216217export const onboarding = task({218 id: "onboarding",219 run: async (payload: { userId: string }) => {220 await sendWelcomeEmail.triggerAndWait({ userId: payload.userId });221 await wait.for({ days: 1 });222 await sendTipsEmail.trigger({ userId: payload.userId });223 await wait.until({ date: oneWeekFromSignup(payload.userId) });224 await sendUpgradeOffer.trigger({ userId: payload.userId });225 },226});227```228229`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.230231### Human-in-the-loop med `wait.forToken`232233For godkannandeflaer och AI-grindar pausar `wait.forToken` tills din applikation atergivar med ett resultat.234235```typescript236import { task, wait } from "@trigger.dev/sdk";237238export const publishPost = task({239 id: "publish-post",240 run: async (payload: { draftId: string }) => {241 const draft = await generateAIContent(payload.draftId);242243 const token = await wait.createToken({ timeout: "7d" });244 await notifyEditor({ draftId: draft.id, token: token.id });245246 const decision = await wait.forToken<{ approved: boolean; notes?: string }>(247 token.id248 );249250 if (decision.approved) {251 return await publish(draft);252 }253 return await applyFeedback(draft, decision.notes);254 },255});256```257258Redaktoren 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.259260## Lifecycle Hooks261262Du 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.263264```typescript265// trigger.config.ts266export default defineConfig({267 // ...268 init: async () => {269 Sentry.init({ dsn: process.env.SENTRY_DSN });270 },271 onFailure: async ({ error, ctx }) => {272 Sentry.captureException(error, {273 tags: { taskId: ctx.task.id, runId: ctx.run.id },274 });275 },276});277```278279`init` kor en gang per worker-container vid boot, inte per run, sa det ar ratt plats att satta upp klienter och pooler.280281## Realtid i Frontenden282283Trigger.dev publicerar run-tillstandsforandringar - status, metadata, output - over ett streaming-API. React-hooks prenumererar pa den strommen och re-renderar automatiskt.284285```typescript286// trigger/process-video.ts287import { task, metadata } from "@trigger.dev/sdk";288289export const processVideo = task({290 id: "process-video",291 run: async (payload: { videoId: string }) => {292 metadata.set("stage", "transcoding");293 await transcode(payload.videoId);294295 metadata.set("stage", "thumbnails");296 await generateThumbnails(payload.videoId);297298 metadata.set("stage", "uploading");299 const url = await uploadToCDN(payload.videoId);300301 return { url };302 },303});304```305306```tsx307// components/VideoStatus.tsx308"use client";309import { useRealtimeRun } from "@trigger.dev/react-hooks";310import type { processVideo } from "@/trigger/process-video";311312export function VideoStatus({313 runId,314 publicAccessToken,315}: {316 runId: string;317 publicAccessToken: string;318}) {319 const { run, error } = useRealtimeRun<typeof processVideo>(runId, {320 accessToken: publicAccessToken,321 });322323 if (error) return <p>Error: {error.message}</p>;324 if (!run) return <p>Loading...</p>;325326 return (327 <div>328 <p>Status: {run.status}</p>329 <p>Stage: {String(run.metadata?.stage ?? "queued")}</p>330 {run.output?.url && <video src={run.output.url} controls />}331 </div>332 );333}334```335336Du genererar den publika atkomsttoken pa serversidan, scopad till en specifik run, och skickar den till klienten. Hooket hanterar auth, ateranslutning och inkrementella uppdateringar.337338For trigger-and-subscribe i ett steg:339340```tsx341import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";342343const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>(344 "process-video",345 { accessToken: publicAccessToken }346);347348<button onClick={() => submit({ videoId })} disabled={isLoading}>349 Process video350</button>;351```352353## AI-agenter och streaming354355Trigger.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.356357```typescript358import { task, metadata } from "@trigger.dev/sdk";359import { streamText } from "ai";360import { anthropic } from "@ai-sdk/anthropic";361362export const researchAgent = task({363 id: "research-agent",364 maxDuration: 1800,365 run: async (payload: { question: string }) => {366 const result = streamText({367 model: anthropic("claude-opus-4-7"),368 system: "You are a research assistant. Use the web.",369 prompt: payload.question,370 tools: { webSearch },371 });372373 let fullText = "";374 for await (const chunk of result.textStream) {375 fullText += chunk;376 metadata.set("partial", fullText);377 }378379 return { answer: fullText, usage: await result.usage };380 },381});382```383384Frontenden 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.385386## Distribuera387388Distribueringar kompilerar dina tasks till en versionerad bundle, bygger en container och vaxlar trafiken atomart. Gamla in-flight runs fortsatter att anvanda den foregaende versionen.389390```bash391npx trigger.dev@latest deploy --env prod392```393394I CI ansluter du detta vanligtvis till samma workflow som skickar din app:395396```yaml397# .github/workflows/deploy.yml398- name: Deploy Trigger.dev399 env:400 TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}401 run: npx trigger.dev@latest deploy --env prod402```403404For 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.405406## Self-Hosting vs Cloud407408Trigger.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.409410| Aspekt | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | Registrera, kor `init` | Kor docker-compose eller Helm chart |413| **Skalning** | Automatisk | Ditt ansvar |414| **Pricing** | Per run + per compute | Endast infra-kostnad |415| **Compliance** | SOC 2 | Vad din miljo tillhandahaller |416| **Bast for** | De flesta team | Strikt dataresidens, custom infra |417418SDK:n och CLI:n ar identiska mellan lagen - du andrar en profilflagga och pekar pa din egen instans.419420## Best Practices421422### 1. Hall payloads sma och serialiserbara423424Skicka 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.425426### 2. Idempotens-nycklar pa varje extern call427428Kombinera `idempotencyKey` pa task-triggern med idempotens-nycklar pa dina vendor-API:er (Stripe, OpenAI, etc.). Retries blir end-to-end sakra.429430### 3. Anvand `triggerAndWait` for orkestrering, inte `Promise.all` av triggers431432En forilder som anropar `triggerAndWait` komponerar hallbart barntasks. En forilder som triggar och resolvar omedelbart forlorar observabilitet av kedjan.433434### 4. Tagga runs435436Lagg till `tags` till triggers (`tags: ["user:123", "feature:onboarding"]`) sa att du kan filtrera dashboarden och management-API:t efter affarsdimensioner.437438### 5. Hall `init` idempotent439440Den kor vid varje cold start. Undvik migrationer eller engangs-sidoeffekter dar.441442## Slutsats443444Trigger.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.445446Samma 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.447448> **Komma igang Checklist:**449>450> - [x] Registrera dig pa trigger.dev eller kor self-hosted Docker-stacken451> - [x] `npx trigger.dev@latest init` i ditt projekt452> - [x] Definiera din forsta task med `task({ id, run })`453> - [x] Trigga den fran ditt API och se run i dashboarden454> - [x] Lagg till `idempotencyKey` och `concurrencyKey` for produktionsakerhet455> - [x] Koppla `useRealtimeRun` till en statuskomponent456> - [x] Distribuera med `trigger.dev deploy --env prod` fran CI457
:Trigger.dev: Hallbara bakgrundsjobb och AI-arbetsfloden i TypeScriptlines 1-457 (END) — press q to close