trigger-realtime

作者: triggerdotdev

Trigger.dev client/frontend surface: subscribe to runs in realtime (runs.subscribeToRun and the @trigger.dev/react-hooks hook useRealtimeRun), consume metadata…

npx skills add https://github.com/triggerdotdev/skills --skill trigger-realtime

Realtime and Frontend

The consumer side of Trigger.dev's run state and streams: read live run updates, render AI/text streams, and trigger tasks from a browser. Hooks come from @trigger.dev/react-hooks; token minting and backend subscription come from @trigger.dev/sdk.

Setup

npm add @trigger.dev/react-hooks   # frontend hooks (React/Next.js/Remix)
# @trigger.dev/sdk is already installed for the backend

The flow is always: mint a scoped token in the backend, pass it to the frontend, subscribe with a hook.

// backend (API route / server action)
import { auth } from "@trigger.dev/sdk";

const publicAccessToken = await auth.createPublicToken({
  scopes: { read: { runs: ["run_1234"] } }, // a token with no scopes is useless
});
// frontend
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";

export function RunStatus({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken });
  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;
  return <div>Run: {run.status}</div>;
}

There are two token kinds: Public Access Tokens (read/subscribe, from auth.createPublicToken) and Trigger Tokens (trigger-from-browser, single-use, from auth.createTriggerPublicToken). Both default to a 15 minute expiry.

Core patterns

1. Subscribe to a run and render metadata progress

metadata is Record<string, DeserializedJson>, so nested values need a cast.

"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function Progress({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { run, error } = useRealtimeRun<typeof myTask>(runId, { accessToken: publicAccessToken });
  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;
  const progress = run.metadata?.progress as { percentage?: number } | undefined;
  return <div>{run.status}: {progress?.percentage ?? 0}%</div>;
}

Pass onComplete: (run, error) => {} to react when the run finishes.

2. Status-only subscription with skipColumns

For a badge or progress bar you do not need payload/output. Skipping them reduces wire size and avoids "Large HTTP Payload" warnings.

const { run } = useRealtimeRun(runId, {
  accessToken: publicAccessToken,
  skipColumns: ["payload", "output"],
});

You can skip any of: payload, output, metadata, startedAt, delayUntil, queuedAt, expiredAt, completedAt, number, isTest, usageDurationMs, costInCents, baseCostInCents, ttl, payloadType, outputType, runTags, error.

3. Trigger from the browser with a Trigger Token

accessToken here is a Trigger Token (auth.createTriggerPublicToken), not a Public Access Token.

"use client";
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function TriggerButton({ triggerToken }: { triggerToken: string }) {
  const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken: triggerToken,
  });
  if (handle) return <div>Run ID: {handle.id}</div>;
  return (
    <button onClick={() => submit({ foo: "bar" }, { tags: ["user:123"] })} disabled={isLoading}>
      {isLoading ? "Triggering..." : "Run"}
    </button>
  );
}

submit(payload, options?) takes the same options as a backend trigger call.

4. Trigger and subscribe in one hook

"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function Runner({ publicAccessToken }: { publicAccessToken: string }) {
  const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
    accessToken: publicAccessToken,
  });
  if (run) return <div>{run.status}</div>;
  return <button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>Run</button>;
}

Use useRealtimeTaskTriggerWithStreams<typeof myTask, STREAMS> when you also want the task's streams (it returns { submit, run, streams, error, isLoading }).

5. Consume an AI/text stream (SDK 4.1.0+, recommended)

useRealtimeStream takes a defined stream for full type safety, or a runId plus optional stream key. Returns { parts, error }.

"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "@/trigger/streams"; // a defined stream -> typed parts

export function StreamView({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken: publicAccessToken,
    timeoutInSeconds: 300, // default 60
    onData: (chunk) => console.log(chunk),
  });
  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Loading...</div>;
  return <div>{parts.join("")}</div>;
}

Without a defined stream: useRealtimeStream<string>(runId, "ai-output", { accessToken }), or omit the key to use the default stream. Other options: baseURL, startIndex, throttleInMs (default 16). The legacy useRealtimeRunWithStreams(runId, options) hook is still supported when you need both the run and all its streams at once.

6. Send input back into a running task

"use client";
import { useInputStreamSend } from "@trigger.dev/react-hooks";
import { approval } from "@/trigger/streams";

export function ApprovalForm({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { send, isLoading, isReady } = useInputStreamSend(approval.id, runId, { accessToken });
  return (
    <button disabled={!isReady || isLoading} onClick={() => send({ approved: true })}>
      Approve
    </button>
  );
}

7. Complete a wait token from React

// backend: create the token, return id + publicAccessToken to the frontend
import { wait } from "@trigger.dev/sdk";
const token = await wait.createToken({ timeout: "10m" });
return { tokenId: token.id, publicToken: token.publicAccessToken };
"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

export function Approve({ tokenId, publicToken }: { tokenId: string; publicToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken: publicToken });
  return <button onClick={() => complete({ approved: true })}>Approve</button>;
}

8. Subscribe from the backend (async iterators)

import { runs, tasks } from "@trigger.dev/sdk";
import type { myTask } from "./trigger/my-task";

const handle = await tasks.trigger("my-task", { some: "data" });
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
  console.log(run.payload.some, run.output?.some); // typed
}

runs.subscribeToRun completes when the run finishes, so the loop exits on its own.

Common mistakes

  1. CRITICAL: Triggering from the browser with a Public Access Token. The read token from createPublicToken cannot trigger tasks.

    • Wrong: useTaskTrigger("my-task", { accessToken: publicAccessTokenFromCreatePublicToken })
    • Correct: mint a single-use Trigger Token with auth.createTriggerPublicToken("my-task") and pass that.
  2. Token with no scopes. A scopeless token authorizes nothing, so every subscribe 403s.

    • Wrong: await auth.createPublicToken()
    • Correct: await auth.createPublicToken({ scopes: { read: { runs: ["run_1234"] } } })
  3. Polling with useRun/SWR for live updates. useRun is the SWR-based management-API hook (not recommended for live state); set refreshInterval: 0 to stop polling if you do use it.

    • Wrong: useRun(runId, { refreshInterval: 1000 }) to track progress
    • Correct: useRealtimeRun(runId, { accessToken }) (no polling, no WebSocket setup)
  4. Forgetting "use client". Realtime/trigger hooks cannot run in a server component.

    • Wrong: a Next.js App Router server component using useRealtimeRun
    • Correct: put "use client"; at the top of any component using these hooks.
  5. Shipping payload/output you do not render.

    • Wrong: useRealtimeRun(runId, { accessToken }) for a status badge (large payloads over the wire)
    • Correct: useRealtimeRun(runId, { accessToken, skipColumns: ["payload", "output"] })
  6. Subscribing before the handle exists.

    • Wrong: useRealtimeRun(handle, { accessToken: handle?.publicAccessToken }) with no guard
    • Correct: add enabled: !!handle so it subscribes only once the trigger returns a handle.

References

Sibling skills:

  • trigger-tasks for the task side: streams.define(), metadata.set(), and wait.createToken.
  • trigger-authoring-chat-agent and trigger-chat-agent-advanced for chat agents, which build on these realtime streams.

Reference docs ship beside this skill in the same package, read them locally (no network), pinned to your installed version. The sources: frontmatter above lists every doc this skill draws from, all under @trigger.dev/sdk/docs/. Start with:

  • @trigger.dev/sdk/docs/realtime/react-hooks/subscribe.mdx
  • @trigger.dev/sdk/docs/realtime/react-hooks/streams.mdx
  • @trigger.dev/sdk/docs/realtime/auth.mdx
  • @trigger.dev/sdk/docs/realtime/run-object.mdx (the realtime run object differs from the management-API object returned by useRun)

Version

This skill is bundled inside @trigger.dev/sdk and read directly from node_modules, so it always matches your installed SDK version (see the adjacent package.json). The full documentation for these APIs ships alongside it under @trigger.dev/sdk/docs/.

來自 triggerdotdev 的更多技能

trigger-dev-tasks
triggerdotdev
在撰寫、設計或優化 Trigger.dev 背景任務與工作流程時使用此技能。這包括建立可靠的異步任務、實作 AI…
official
trigger-authoring-chat-agent
triggerdotdev
使用 @trigger.dev/sdk/ai 中的 chat.agent 編寫並執行一個持久的 AI 聊天代理:每輪運行循環,為什麼你必須展開 ...chat.toStreamTextOptions()…
official
trigger-agents
triggerdotdev
使用 Trigger.dev 的 AI 代理模式——包括編排、並行化、路由、評估器-優化器以及人機協作。適用於構建由 LLM 驅動的任務時…
official
trigger-config
triggerdotdev
使用 trigger.config.ts 配置 Trigger.dev 專案。在為 Prisma、Playwright、FFmpeg、Python 設定建置擴充或自訂部署時使用…
official
trigger-cost-savings
triggerdotdev
分析 Trigger.dev 任務、排程與執行記錄,找出成本最佳化的機會。適用於被要求降低支出、優化成本、審核使用情況、調整規模等情境。
official
trigger-realtime
triggerdotdev
從前端和後端即時訂閱 Trigger.dev 任務運行。用於建置進度指示器、即時儀表板、串流 AI/LLM 回應等…
official
trigger-setup
triggerdotdev
在您的專案中設定 Trigger.dev。適用於首次加入 Trigger.dev、建立 trigger.config.ts 或初始化 trigger 目錄時使用。
official
trigger-tasks
triggerdotdev
使用 Trigger.dev 構建 AI 代理、工作流程和持久化的背景任務。適用於創建任務、觸發作業、處理重試、排程 cron 任務或…
official