Οι περισσότερες εφαρμογές παραγωγής χρειάζονται εργασία που δεν χωράει στον κύκλο request/response: αποστολή email, επεξεργασία uploads, εκτέλεση pipelines AI, συγχρονισμός δεδομένων τρίτων, δημιουργία αναφορών. Η παραδοσιακή απάντηση είναι μια ουρά (Redis, SQS, RabbitMQ), ένας στόλος workers, ένας scheduler και ένας εύθραυστος σωρός κώδικα συγκόλλησης που σπάει σε κάθε deploy.
Trigger.dev συμπυκνώνει αυτό το stack σε ένα μόνο TypeScript SDK. Γράφετε συναρτήσεις, τις καλείτε από οπουδήποτε και η πλατφόρμα χειρίζεται queueing, retries, observability, scheduling και ανθεκτική εκτέλεση. Τα tasks τρέχουν όσο χρειάζεται - χωρίς timeout serverless 10 δευτερολέπτων, χωρίς χαμένη εργασία στα redeploys.
Γιατί Trigger.dev
Η αλλαγή το 2026 είναι η ανθεκτική εκτέλεση. Οι ροές εργασίας πρέπει να επιβιώνουν από επανεκκινήσεις, crashes, deploys και rate limits. Πρέπει επίσης να 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 server συνδέεται με το cloud, καταχωρεί τα tasks σας και κάνει stream τα runs μέσω του τοπικού σας κώδικα. Βάζετε breakpoints στον editor σας και τα χτυπάτε σε πραγματικά triggers - το ίδιο loop που θα χρησιμοποιούσατε σε οποιοδήποτε κανονικό έργο Node.js.
Ορισμός Task
Ένα task είναι ένα αντικείμενο που εξάγεται με ένα μοναδικό 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 πιάνει εξαιρέσεις και τρέχει ξανά με exponential backoff σύμφωνα με την πολιτική
retry. - Η τιμή επιστροφής διατηρείται. Άλλα tasks και το frontend σας μπορούν να διαβάζουν
run.outputαπό οπουδήποτε.
Triggering Tasks
Καλείτε ένα task από το backend σας, τις διαδρομές 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 με το ίδιο κλειδί υπάρχει ήδη, το SDK επιστρέφει το υπάρχον handle αντί να διπλασιάσει την εργασία.concurrencyKey- σειριοποιεί τα runs που μοιράζονται το κλειδί ώστε να μην ξεπερνάτε ένα per-tenant rate limit.queue.concurrencyLimit- παγκόσμιο cap για την ουρά σε όλα τα κλειδιά.delay- προγραμματίζει το run για μελλοντικό χρόνο.ttl- αν το run δεν έχει ξεκινήσει μέχρι τότε, λήγει αυτόματα.
Batch trigger
Για fan-out workloads, το batchTrigger δέχεται έως 500 items ανά κλήση και δημιουργεί ένα run ανά item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Προγραμματισμένα Tasks
Τα cron jobs γίνονται δηλώσεις πρώτης τάξης. Το ίδιο το schedule είναι ένα ξεχωριστό αντικείμενο που μπορείτε να επισυνάψετε σε ένα 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); }, });
Για schedules per-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.
Ουρές, ταυτόχρονη εκτέλεση και idempotency
Τρία primitives καλύπτουν τις περισσότερες ανάγκες rate-limiting και ταξινόμησης.
Ένα κοινό μοτίβο: μία ουρά ανά tenant με μικρή per-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}`, } );
Αναμονές και εργασία μεγάλης διάρκειας
Τα tasks μπορούν να σταματήσουν χωρίς να κρατούν σύνδεση ή να καίνε compute. Η πλατφόρμα διατηρεί την κατάσταση και επαναφέρει τη συνάρτηση όταν η αναμονή ολοκληρωθεί.
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 είναι το killer feature: ενεργοποιεί ένα child task και αναστέλλει το parent μέχρι να ολοκληρωθεί το child. Συνθέτετε tasks σαν async συναρτήσεις, αλλά η ενορχήστρωση τρέχει ανθεκτικά για ημέρες ή εβδομάδες.
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); }, });
Ο επεξεργαστής ανοίγει ένα UI, αναθεωρεί το πρόχειρο, κάνει κλικ στο Approve και το 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 κατά την εκκίνηση, όχι ανά 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 στην πλευρά του server, scoped σε ένα συγκεκριμένο 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, αναμονές, real-time metadata, human-in-the-loop - είναι ακριβώς αυτό που χρειάζονται οι agents. Streamάρετε tokens από έναν model provider στο metadata ενώ το run εκτελείται, το frontend τα κάνει render ζωντανά και το run επιβιώνει από tool calls μεγάλης διάρκειας χωρίς να καίει ένα 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 }; }, });
Το frontend χρησιμοποιεί το useRealtimeRun και διαβάζει το run.metadata.partial για να κάνει render την streaming απάντηση, με τον ίδιο τρόπο που θα κάνατε render μια chat completion - εκτός από το ότι αυτή επιβιώνει σε πλήρες reload σελίδας.
Deploying
Τα deploys μεταγλωττίζουν τα tasks σας σε ένα versioned bundle, χτίζουν ένα container και ανταλλάσσουν την κίνηση ατομικά. Τα παλιά 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
Για περιβάλλοντα 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 | Ό,τι παρέχει το περιβάλλον σας |
| Καλύτερο για | Τις περισσότερες ομάδες | Αυστηρή κατοικία δεδομένων, custom infra |
Το SDK και το CLI είναι πανομοιότυπα μεταξύ των τρόπων - αλλάζετε ένα profile flag και δείχνετε στο δικό σας instance.
Best Practices
1. Κρατήστε τα payloads μικρά και σειριοποιήσιμα
Περάστε IDs και αναφορές, όχι πλήρη αντικείμενα. Τραβήξτε τα δεδομένα μέσα στο task. Αυτό κρατάει την ουρά μικρή, τα payloads φθηνά να καταγραφούν και σας επιτρέπει να αλλάξετε την πηγή δεδομένων χωρίς re-trigger.
2. Idempotency keys σε κάθε εξωτερική κλήση
Συνδυάστε το idempotencyKey στο trigger του task με idempotency keys στα APIs των vendors σας (Stripe, OpenAI, κλπ). Τα retries θα είναι ασφαλή end-to-end.
3. Χρησιμοποιήστε το triggerAndWait για ενορχήστρωση, όχι Promise.all από triggers
Ένας parent που καλεί triggerAndWait συνθέτει ανθεκτικά child tasks. Ένας parent που τρενάρει και επιλύει αμέσως χάνει την παρατηρησιμότητα της αλυσίδας.
4. Tagάρετε τα runs
Προσθέστε tags στα triggers (tags: ["user:123", "feature:onboarding"]) ώστε να μπορείτε να φιλτράρετε το dashboard και το management API ανά επιχειρηματικές διαστάσεις.
5. Κρατήστε το init idempotent
Τρέχει σε κάθε cold start. Αποφύγετε migrations ή one-shot παρενέργειες εκεί.
Συμπέρασμα
Το Trigger.dev αφαιρεί τις κατηγορίες εργασίας που παλιότερα απαιτούσαν να χτίσετε ένα job system από την αρχή. Γράφετε async TypeScript, το καλείτε από οπουδήποτε και η πλατφόρμα σας δίνει ανθεκτική εκτέλεση, scheduling, ουρές, retries, real-time ενημερώσεις και πρότυπα human-in-the-loop out of the box.
Η ίδια επιφάνεια που τροφοδοτεί ένα νυχτερινό cron είναι η επιφάνεια που τροφοδοτεί έναν multi-step AI agent που streamάρει στο frontend και σταματάει για review. Αυτή η σύγκλιση είναι αυτό που κάνει το framework άξιο σοβαρής ματιάς το 2026, είτε διαχειρίζεστε ένα SaaS που χρειάζεται αξιόπιστη εργασία παρασκηνίου είτε αποστέλλετε χαρακτηριστικά AI που επιβιώνουν από ένα serverless timeout.
Checklist Εκκίνησης:
- Εγγραφείτε στο trigger.dev ή τρέξτε το self-hosted Docker stack
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