useRealtime

The useRealtime hook subscribes to realtime messages from Inngest functions in React components. It manages the WebSocket connection, reconnection, buffering, and provides typed access to messages by topic.

import { useRealtime } from "inngest/react";
import { pipelineChannel } from "../inngest/channels";

function Pipeline({ runId }: { runId: string }) {
  const ch = pipelineChannel({ runId });

  const { status, latest, history, runStatus, result } = useRealtime({
    channel: ch,
    topics: ["status", "tokens"],
    token: () => fetch("/api/realtime-token").then((r) => r.text()),
  });

  return (
    <div>
      <p>Connection: {status} | Run: {runStatus}</p>
      {latest.status && <p>{latest.status.data.message}</p>}
    </div>
  );
}

useRealtime(options)

  • Name
    channel
    Type
    ChannelInstance | string
    Required
    optional
    Description

    The channel to subscribe to. Can be a channel instance from realtime.channel() or a plain string. When using a channel instance, topic data is automatically typed.

  • Name
    topics
    Type
    string[]
    Required
    optional
    Description

    The topics to subscribe to within the channel. Only messages for these topics will be delivered.

  • Name
    token
    Type
    string | Token | () => Promise<string | Token>
    Required
    optional
    Description

    Authentication token for the subscription. Can be a pre-minted token string, a Token object from getSubscriptionToken, or an async factory function that fetches a token. The factory pattern is recommended as it's called on mount and on reconnect.

  • Name
    enabled
    Type
    boolean
    Required
    optional
    Description

    Whether the subscription is active. Set to false to pause without unmounting. Defaults to true.

  • Name
    validate
    Type
    boolean
    Required
    optional
    Description

    Enable subscriber-side schema validation on incoming messages. Defaults to true.

  • Name
    historyLimit
    Type
    number | null
    Required
    optional
    Description

    Maximum number of messages to retain in history and data. Set to null for unbounded history. Defaults to 100.

  • Name
    bufferInterval
    Type
    number
    Required
    optional
    Description

    Milliseconds to buffer incoming messages before triggering a re-render. Useful for high-frequency streams to reduce render pressure. Defaults to 0 (immediate).

  • Name
    reconnect
    Type
    boolean
    Required
    optional
    Description

    Automatically reconnect on disconnect. Defaults to true.

  • Name
    reconnectMinMs
    Type
    number
    Required
    optional
    Description

    Minimum delay between reconnect attempts in milliseconds. Defaults to 250.

  • Name
    reconnectMaxMs
    Type
    number
    Required
    optional
    Description

    Maximum delay between reconnect attempts in milliseconds (exponential backoff cap). Defaults to 5000.

  • Name
    pauseOnHidden
    Type
    boolean
    Required
    optional
    Description

    Pause the subscription when the browser tab is hidden, resuming when it becomes visible. Defaults to true.

  • Name
    autoCloseOnTerminal
    Type
    boolean
    Required
    optional
    Description

    Automatically close the subscription when the function run reaches a terminal status (completed, failed, or cancelled). Defaults to true.

Return value

  • Name
    status
    Type
    "idle" | "connecting" | "open" | "closed" | "error"
    Required
    optional
    Description

    The WebSocket connection status.

  • Name
    runStatus
    Type
    "unknown" | "running" | "completed" | "failed" | "cancelled"
    Required
    optional
    Description

    The lifecycle status of the Inngest function run. Updated from run-level messages on the channel.

  • Name
    latest
    Type
    Record<string, Message | undefined>
    Required
    optional
    Description

    A map of the most recent message for each topic. Access typed data via latest.topicName?.data. Only includes topics specified in options.topics.

  • Name
    history
    Type
    Message[]
    Required
    optional
    Description

    All received messages in chronological order, bounded by historyLimit.

  • Name
    error
    Type
    Error | null
    Required
    optional
    Description

    The most recent connection error, or null if connected successfully.

  • Name
    result
    Type
    unknown
    Required
    optional
    Description

    The function's return value, extracted from the terminal run message when the function completes.

  • Name
    reset
    Type
    () => void
    Required
    optional
    Description

    Clears all accumulated state (history, latest, error, result) and resets to initial values.

  • Name
    data
    Type
    Message[]
    Required
    optional
    Description

    Alias for history. All received messages bounded by historyLimit.

  • Name
    latestData
    Type
    Message | null
    Required
    optional
    Description

    The single most recent message across all topics.

  • Name
    freshData
    Type
    Message[]
    Required
    optional
    Description

    Messages received since the last render cycle. Useful for animations or processing only new items.

Connection status

The status field tracks the WebSocket connection lifecycle:

StatusDescription
idleHook is mounted but hasn't started connecting (e.g., enabled: false)
connectingEstablishing the WebSocket connection
openConnected and receiving messages
closedConnection closed (manually or by autoCloseOnTerminal)
errorConnection failed. Check error for details

Run status

The runStatus field tracks the Inngest function's execution lifecycle:

StatusDescription
unknownNo run status received yet
runningFunction is actively executing
completedFunction finished successfully. result is available
failedFunction failed after exhausting retries
cancelledFunction was cancelled

When autoCloseOnTerminal is true (the default), the subscription closes automatically once runStatus reaches completed, failed, or cancelled.

Token factory pattern

The recommended approach is to pass an async factory function for token. This function is called when the hook mounts and on each reconnect, ensuring fresh tokens.

// app/actions.ts
"use server";
import { getSubscriptionToken } from "inngest/react";
import { inngest } from "../inngest/client";
import { pipelineChannel } from "../inngest/channels";

export async function getToken(runId: string) {
  const ch = pipelineChannel({ runId });
  const token = await getSubscriptionToken(inngest, {
    channel: ch,
    topics: ["status", "tokens"],
  });
  return token.key;
}

// app/page.tsx
"use client";
import { useRealtime } from "inngest/react";
import { pipelineChannel } from "../inngest/channels";
import { getToken } from "./actions";

function Pipeline({ runId }: { runId: string }) {
  const { status, latest } = useRealtime({
    channel: pipelineChannel({ runId }),
    topics: ["status", "tokens"],
    token: () => getToken(runId),
  });

  return <p>{latest.status?.data.message}</p>;
}

Typed topic access

When you pass a channel instance (not a plain string), latest is typed per-topic:

const ch = pipelineChannel({ runId });

const { latest } = useRealtime({
  channel: ch,
  topics: ["status", "tokens"],
  token: () => getToken(runId),
});

// Typed access: TypeScript knows the shape of each topic's data
latest.status?.data.message;  // string
latest.tokens?.data.token;    // string

History management

By default, history retains the last 100 messages. Adjust with historyLimit:

// Keep last 500 messages
useRealtime({ ..., historyLimit: 500 });

// Keep all messages (unbounded, use with caution)
useRealtime({ ..., historyLimit: null });

// Keep only 10 messages
useRealtime({ ..., historyLimit: 10 });

Buffering

For high-frequency streams (like token-by-token AI output), use bufferInterval to batch re-renders:

const { history } = useRealtime({
  channel: ch,
  topics: ["tokens"],
  token: () => getToken(runId),
  bufferInterval: 100, // Batch messages, re-render at most every 100ms
});

Conditional subscription

Use enabled to start or stop the subscription without unmounting the component:

const [runId, setRunId] = useState<string | null>(null);

const { status, latest } = useRealtime({
  channel: pipelineChannel({ runId: runId ?? "" }),
  topics: ["status"],
  token: () => getToken(runId!),
  enabled: runId !== null, // Only connect when we have a runId
});